it-swarm-ru.tech

Почему я не могу конвертировать '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
}

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

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

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

59
HappyDude

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

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

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

57
Kevin

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

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

Значение const-correctness - 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++. Возможно, стоит отключить предупреждение компилятора только для этой строки исходного кода.

Edit-delete: удалена опечатка

10
Aaron

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

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

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

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

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

1
wnoise

Я не могу получить ошибку при неявном приведении 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 ++ -Wall test.C

без вывода

0
user7116

Я почти уверен, что ключевое слово 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
Skizz