Подтвердить что ты не робот

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

Следующий фрагмент кода (правильно) выдает предупреждение на 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
}

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

void foo(const int*);

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

Если вы не уверены, почему код, который вы разместили, не const -correct, рассмотрите следующий код, только немного отличающийся от кода 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;

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

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

На некоторых компиляторах это напечатает 42, на некоторых 43 и другие, программа потерпит крах.

Edit-дополню:

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

Ответ 3

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

Ответ 4

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

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

const char *bi = *a;
const char * const * b = &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 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

Вывод с компиляцией в виде кода на С++: 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

нет вывода

Ответ 6

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

const volatile int *const serial_port = SERIAL_PORT;

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

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

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

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