Почему я не могу преобразовать '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
}
И я хотел бы вызвать его для символа **, какой тип параметра будет правильным?
Ответы
Ответ 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
Что происходит, когда делается попытка записать в память, которая действительно является только для чтения (например, ПЗУ), вероятно, вообще не определена в стандарте.