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

Почему C не позволяет неявное преобразование из char ** в const char * const * (и С++)?

Я знаю, что неявное преобразование из char ** в const char ** не может быть выполнено и почему и что преобразование в char *const * работает. См. Ниже ссылки для объяснения этого.

Все это имеет смысл, кроме одной конкретной вещи. Поэтому у меня есть следующий код:

#include <stdio.h>

void
print(const char *const*param)
{
    printf("%s\n", param[0]);
}

int
main(int argc, char **argv)
{
    print(argv);
    return 0;
}

Если я скомпилирую это как код на С++, он компилируется довольно хорошо. Однако, если один и тот же код скомпилирован только как код C, я получаю сообщение об ошибке (ну, предупреждение, но пусть -Werror, т.е. Обрабатывать предупреждения как ошибки).

НКА:

test.c: In function ‘main’:
test.c:12:11: warning: passing argument 1 of ‘print’ from incompatible pointer type [-Wincompatible-pointer-types]
     print(argv);
           ^
test.c:4:1: note: expected ‘const char * const*’ but argument is of type ‘char **’
 print(const char *const*param)
 ^

лязг:

test.c:12:11: warning: passing 'char **' to parameter of type 'const char *const *' discards qualifiers in nested pointer types [-Wincompatible-pointer-types-discards-qualifiers]
    print(argv);
          ^~~~
test.c:4:25: note: passing argument to parameter 'param' here
print(const char *const*param)
                        ^

Оба поведения являются стандартно независимыми, а также независимыми от компилятора. Я пробовал различные стандарты с gcc и clang.

Для этого запроса есть две причины. Во-первых, я хочу понять, есть ли разница, и, во-вторых, у меня есть функция, которая ничего не делает с любым слоем указателей, и мне нужно, чтобы она работала с const char **, а также char *const * и char **. Явное выполнение каждого вызова не поддерживается. И я не знаю, как должен выглядеть прототип функции.


Это вопрос, который вызвал мое любопытство: Неявное преобразование из char ** в const char **

И вот еще одно приятное объяснение проблемы char ** => const char**: http://c-faq.com/ansi/constmismatch.html

Если ссылки запутаны в связи с этим вопросом, не стесняйтесь их редактировать.

4b9b3361

Ответ 1

C и С++ в этом отношении различны. У меня нет ответа на вопрос, почему С++ более щедрый, кроме того, что поведение С++ кажется мне правильным.

C просто не допускает косвенного преобразования const. Это консервативное, простое в использовании ограничение, с неудачным последствием того, что вы не можете предоставить char*[] функции, ожидающей char const* const*. Ограничение находится в разделе 6.3.2.3, параграф 2, и это просто не рекурсивно:

Для любого квалификатора q указатель на не-t23 > -qualified тип может быть преобразован в указатель на q -qualified версию типа; значения, хранящиеся в исходных и преобразованных указателях, сравниваются равными.

С++ допускает преобразования в соответствии с несколько сложной формулировкой в ​​разделе 4.4 [conv.qual], абзац 3. Разрешено преобразовывать

T cvn Pn-1cvn-1 … P1cv1 P0cv0T cv'n Pn-1cv'n-1 … P1cv'1 P0cv'0

(где T - тип; P1…Pn - конструкторы типа указателя/массива, и каждый cv0…cvn является, возможно, пустым подмножеством const и volatile)

при условии, что:

  • Для каждого k > 0, cvk является подмножеством cv'k (поэтому вы не можете удалить const или volatile) и

  • Если cvk и cv'k отличаются для некоторого k > 0, все следующие cv'i>k включают const.

В реальном стандарте это выражение отменяется; Я помещаю его в порядок декларации, тогда как в стандарте он находится в порядке применения конструкторов указателя/массива. Однако я не изменил направление нумерации, поэтому их пронумеровали справа налево. Я также отказался от некоторых деталей - например, не обязательно, чтобы два T были идентичными, но я думаю, что это дает представление о намерении.

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

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

За что стоит отметить в разделе раздел Обоснование спецификации Posix интерфейсов exec* об этой проблеме для этих прототипов и обходной путь, выбранный Posix, который должен использовать char*[] в качестве прототипа и текстовое примечание, что они являются постоянными: (выделено мной)

Утверждение о константах argv[] и envp[] включено, чтобы сделать явным для будущих писателей языковых привязок, что эти объекты полностью постоянные. Из-за ограничения стандарта ISO C невозможно утверждать эту идею в стандарте C. Указание двух уровней const -qualification для параметров argv[] и envp[] для Функции exec могут казаться естественным выбором, учитывая, что эти функции не изменяют ни массив указателей, ни символы, на которые указывает функция, но это будет запрещать существующий правильный код. Вместо этого только массив указателей отмечен как константа.

Там есть полезная таблица совместимости после этого параграфа, которую я не цитировал из-за ограничений форматирования этого сайта.