Почему я не могу преобразовать ‘char **’ в ‘const char * const *’ в C?

#c #constants

#c #указатели #константы #const-корректность

Вопрос:

Следующий фрагмент кода (правильно) выдает предупреждение на C и ошибку на C (с использованием gcc и g соответственно, протестировано с версиями 3.4.5 и 4.2.1; MSVC, похоже, не волнует):

 char **a;
const char** b = a;
  

Я могу понять и принять это.
Решение этой проблемы на C состоит в том, чтобы изменить b значение на a const char * const * , что запрещает переназначение указателей и не позволяет обойти const-корректность (C FAQ).

 char **a;
const char* const* b = a;
  

Однако в чистом C исправленная версия (с использованием const char * const * ) по-прежнему выдает предупреждение, и я не понимаю почему.
Есть ли способ обойти это без использования приведения?

Чтобы уточнить:

  1. Почему это генерирует предупреждение в C? Это должно быть полностью безопасно для const, и компилятор C , похоже, распознает его как таковой.

  2. Как правильно принять это char** в качестве параметра, заявив (и принудив компилятор), что я не буду изменять символы, на которые он указывает? Например, если бы я хотел написать функцию:

      void f(const char* const* in) {
       // Only reads the data from in, does not write to it
     }
      

И я хотел вызвать его на a char** , какой тип был бы правильным для параметра?

Комментарии:

1. Я в замешательстве относительно вашего вопроса: «Как мне заставить C не предупреждать меня?» или «Как мне заставить C не выдавать ошибку компиляции?» Или, может быть, это ни то, ни другое.

2. Я согласен с sixlettervariables, этот вопрос нуждается в дальнейшем уточнении.

3. Вопрос в том, «почему я не могу преобразовать ‘char **’ в ‘char const

4. Я имею в виду ‘char **’ в ‘const char const

5. Следует отметить, что MSVC на самом деле не является компилятором C , соответствующим стандартам.

Ответ №1:

У меня была такая же проблема несколько лет назад, и это меня бесконечно раздражало.

Правила в C сформулированы проще (т. Е. Они не перечисляют исключения, такие как преобразование char** в const char*const* ). Следовательно, это просто не разрешено. Со стандартом C они включили больше правил, разрешающих подобные случаи.

В конце концов, это просто проблема в стандарте C. Я надеюсь, что в следующем стандарте (или техническом отчете) это будет рассмотрено.

Комментарии:

1. Я, конечно, не проголосовал против этого; это самый актуальный ответ, который я получил до сих пор. Если бы у меня было 100 очков репутации, я бы проголосовал за это.

2. @Kevin: Если вы все еще рядом, мне интересно, насколько вы уверены в своем ответе, что это просто проблема в стандарте — в то время вы действительно просматривали стандарт, чтобы проверить его? Если это так, я могу в значительной степени просто закрыть этот вопрос, поскольку это тот ответ, который я собираюсь получить.

3. @HappyDude: после прочтения стандарта C после первого ‘const’ все ставки, похоже, отключены, что заставляет компилятор полагать, что есть проблемы с корректностью const. У Кевина самое правильное чтение.

4. @Kevin C11 6.3.2.3 For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal. В чем именно проблема с вышесказанным? Эти правила применяются как к указателям, так и к указателям на указатели. Указатель может быть типом, на который указывают.

5. @JonathanMee Да, ответ неверен, и я понятия не имею, как он получил так много голосов.

Ответ №2:

Чтобы считаться совместимым, указатель источника должен быть const на непосредственно предшествующем уровне косвенности. Итак, это выдаст вам предупреждение в GCC:

 char **a;
const char* const* b = a;
  

Но это не:

 const char **a;
const char* const* b = a;
  

В качестве альтернативы, вы можете использовать его:

 char **a;
const char* const* b = (const char **)a;
  

Для вызова функции f(), как вы упомянули, вам потребуется то же приведение. Насколько я знаю, в этом случае нет способа выполнить неявное преобразование (кроме как в C ).

Комментарии:

1. Пожалуйста, прочитайте вопрос более внимательно. Я знаю, что первый фрагмент кода неверен, как я говорю в строке, следующей сразу за ним. Второй, однако, должен быть в порядке (насколько я могу судить). Как я уже упоминал, MSVC не выдает предупреждения, но gcc выдает.

2. Извините, я плохо объяснил, что я хотел, а также не проверил результат в GCC, чтобы убедиться, что образцы кода были точными. Исправляю.

3. Спасибо Фабио, ваш отредактированный ответ намного понятнее. Я думаю, мне просто придется признать, что мне нужно его использовать.

Ответ №3:

Однако в чистом C это все еще выдает предупреждение, и я не понимаю, почему

Вы уже определили проблему — этот код не является корректным. «Const correct» означает, что, за исключением const_cast и удаления приведений в стиле C const , вы никогда не сможете изменить const объект с помощью этих постоянных указателей или ссылок.

Значение const -correctness — const в значительной степени предназначено для обнаружения ошибок программиста. Если вы объявляете что-то как const , вы заявляете, что, по вашему мнению, его не следует изменять — или, по крайней мере, те, у кого есть доступ const только к версии, не должны иметь возможности его изменять. Рассмотрим:

 void foo(const int*);
  

Как объявлено, foo не имеет разрешения на изменение целого числа, на которое указывает его аргумент.

Если вы не уверены, почему опубликованный вами код const неверен, рассмотрите следующий код, лишь немного отличающийся от кода HappyDude:

 char *y;

char **a = amp;y; // a points to y
const char **b = a; // now b also points to y

// const protection has been violated, because:

const char x = 42; // x must never be modified
*b = amp;x; // the type of *b is const char *, so set it 
         //     with amp;x which is const char* ..
         //     ..  so y is set to amp;x... oops;
*y = 43; // y == amp;x... so attempting to modify const 
         //     variable.  oops!  undefined behavior!
cout << x << endl;
  

Не- const типы могут преобразовываться в типы const только определенными способами, чтобы предотвратить любой обход типа const данных без явного приведения.

Первоначально объявленные объекты const являются особенно особенными — компилятор может предположить, что они никогда не меняются. Однако, если b может быть присвоено значение a без приведения, то вы можете непреднамеренно попытаться изменить const переменную. Это не только нарушило бы проверку, которую вы просили выполнить компилятор, чтобы запретить вам изменять значение этой переменной — это также позволило бы вам нарушить оптимизацию компилятора!

В некоторых компиляторах это будет напечатано 42 , в некоторых 43 и других программа завершится сбоем.

Редактировать-добавить:

HappyDude: ваш комментарий попал в точку. Либо язык C, либо компилятор C, который вы используете, обрабатывают const char * const * его принципиально иначе, чем язык C . Возможно, рассмотрите возможность отключения предупреждения компилятора только для этой исходной строки.

Комментарии:

1. То, что вы сказали, правильно, но это не решает вопрос. Как я уже сказал, я понимаю, почему преобразование из char ** в const char ** неверно. Чего я не понимаю, так это почему преобразование из char ** в const char * const * неверно. Я обновил свой вопрос, чтобы прояснить.

2. Интересно, что за этот ответ продолжают голосовать. Я думаю, многие люди не читают дальше первых двух строк вопроса, что я буду иметь в виду на будущее. Хотя это довольно хорошо написанный пост по относительно неясному вопросу, я просто хотел бы, чтобы это было актуально: P.

3. @Aaron: ваш пост не имеет особого отношения к теме, но, тем не менее, хорошо написан.

Ответ №4:

Это раздражает, но если вы хотите добавить еще один уровень перенаправления, вы часто можете сделать следующее, чтобы перейти к указателю на указатель:

 char c = 'c';
char *p = amp;c;
char **a = amp;p;

const char *bi = *a;
const char * const * b = amp;bi;
  

Это имеет немного другое значение, но обычно оно выполнимо и не использует приведение.

Ответ №5:

Я не могу получить ошибку при неявном преобразовании char ** в const char * const *, по крайней мере, в MSVC 14 (VS2k5) и g 3.3.3. GCC 3.3.3 выдает предупреждение, в котором я не совсем уверен, правильно ли это.

test.c:

 #include <stdlib.h> 
#include <stdio.h>
void foo(const char * const * bar)
{
    printf("bar %s nulln", bar ? "is not" : "is");
}

int main(int argc, char **argv) 
{
    char **x = NULL; 
    const char* const*y = x;
    foo(x);
    foo(y);
    return 0; 
}
  

Вывод с компиляцией в виде кода на C: cl / TC / W4 / Wp64 test.c

 test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter
  

Вывод с компиляцией в виде кода C : cl / TP / W4 / Wp64 test.c

 test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter
  

Вывод с помощью gcc: gcc -Wall test.c

 test2.c: In function `main':
test2.c:11: warning: initialization from incompatible pointer type
test2.c:12: warning: passing arg 1 of `foo' from incompatible pointer type
  

Вывод с помощью g : g -Wall test.C

нет вывода

Комментарии:

1. Извините, я должен был уточнить, что я компилирую с помощью gcc. Я обновил свой вопрос, чтобы прояснить это. К счастью (или, возможно, нет?) Я редко сталкиваюсь со многими трудными для обработки предупреждениями в msvc.

2. @HappyDude: я обновил свои выводы из GCC. И теперь я понимаю, что вы говорите, но я пока не согласен с предупреждением компилятора. Я собираюсь раскрыть свою спецификацию C и посмотреть, что я найду.

3. @sixlettervariables: Спасибо за ответ! Дайте мне знать, если вы что-то найдете, но я подозреваю, что то, что сказал Кевин, было правильным, и это просто не подпадает под стандарт.

Ответ №6:

Я почти уверен, что ключевое слово const не подразумевает, что данные не могут быть изменены / являются постоянными, только то, что данные будут обрабатываться как доступные только для чтения. Рассмотрим это:

 const volatile int *const serial_port = SERIAL_PORT;
  

который является допустимым кодом. Как могут сосуществовать volatile и const? Просто. volatile сообщает компилятору всегда считывать память при использовании данных, а const сообщает компилятору создать ошибку при попытке записи в память с использованием указателя serial_port .

Помогает ли const оптимизатору компилятора? Нет. Вовсе нет. Поскольку константность может быть добавлена к данным и удалена из них посредством приведения, компилятор не может определить, действительно ли данные const являются постоянными (поскольку приведение может быть выполнено в другой единице преобразования). В C у вас также есть ключевое слово mutable, чтобы еще больше усложнить ситуацию.

 char *const p = (char *) 0xb000;
//error: p = (char *) 0xc000;
char **q = (char **)amp;p;
*q = (char *)0xc000; // p is now 0xc000
  

Что происходит, когда предпринимается попытка записи в память, которая действительно доступна только для чтения (например, ROM), вероятно, вообще не определено в стандарте.

Комментарии:

1. На самом деле это не решает вопрос. Насколько я понимаю, удаление const с помощью приведения технически является неопределенной операцией в соответствии со стандартом C, хотя большинство компиляторов разрешают это и «делают правильные вещи». Но это совсем не то, о чем я спрашиваю.

2. @HappyDude отбрасывание const четко определено. Это будет неопределенным, только если вы действительно попытаетесь выполнить запись в объект, объявленный как const (или иным образом недоступный для записи, например, строковый литерал).