Почему я не могу конвертировать 'char **' в 'const char * const *' в C? - PullRequest
59 голосов
/ 17 сентября 2008

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

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

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

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

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

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

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

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

Edit: Спасибо тем, кто ответил, особенно тем, кто ответил на вопрос и / или следил за моими ответами.

Я принял ответ, что то, что я хочу сделать, не может быть сделано без приведения, независимо от того, должно ли это быть возможно.

Ответы [ 6 ]

57 голосов
/ 17 сентября 2008

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

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

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

10 голосов
/ 17 сентября 2008

Чтобы считаться совместимым, указатель источника должен быть константным в непосредственно переднем уровне косвенности. Итак, это даст вам предупреждение в 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 ++).

10 голосов
/ 17 сентября 2008

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

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

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

void foo(const int*);

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

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

char *y;

char **a = &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 = &x; // the type of *b is const char *, so set it 
         //     with &x which is const char* ..
         //     ..  so y is set to &x... oops;
*y = 43; // y == &x... so attempting to modify const 
         //     variable.  oops!  undefined behavior!
cout << x << endl;

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

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

На некоторых компиляторах будет напечатано «42», на некоторых «43», а в других программа будет аварийно завершена.

Edit-дополню:

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

Редактировать-удалить: опечатка

1 голос
/ 19 сентября 2008

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

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

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

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

0 голосов
/ 17 сентября 2008

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

const volatile int *const serial_port = SERIAL_PORT;

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

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

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

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

0 голосов
/ 17 сентября 2008

Я не могу получить ошибку при неявном приведении 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 null\n", 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 ++ -стенный тест.C

без вывода

...