Где определяется const-продвижение - PullRequest
0 голосов
/ 07 июня 2019

Я ищу, где продвижение const определено в c / c ++. Это неявное преобразование, но я не могу найти какую-либо документацию по нему.

Это работает на g ++ при использовании флага --pedantic

// prototypes for example
void foo(char*);
void bar(const char*);

char buffer[8];
snprintf(buffer,sizeof(buffer), "hello");

// note: string literals are of type const char*

foo(buffer);
foo("hello"); // works, but why
bar(buffer);
bar("hello");

Представленное выше поведение - это ожидаемое поведение. Однако я ищу документацию для этого поведения. Я посмотрел (наброски) стандарта c ++ 98 и переполнения стека в поисках «продвижения» и «неявного преобразования» и не нашел ответа.

Если этот вопрос слишком широкий, я использую C ++ 98, поэтому мы можем обратиться к нему по этому стандарту.

1 Ответ

3 голосов
/ 07 июня 2019

Этот ответ для C, а не C ++.

Символьные строковые литералы (отличающиеся от строковых литералов UTF-8 или широких строковых литералов) являются массивами char, в соответствии с C 2018 6.4.5 6 1 . По историческим причинам они не являются массивами const char, но программисты должны воспринимать их как const, поскольку, если программа пытается записать строковый литерал, поведение не определяется стандартом C.

Как массив, строковый литерал автоматически преобразуется в char *, указывающий на его первый элемент, если только он не является операндом sizeof или унарным & или не используется для инициализации массива.

Таким образом, в foo(buffer) и foo("hello") мы имеем аргумент char *, переданный параметру char *, и преобразование не требуется.

В bar(buffer) и bar("hello") у нас есть аргумент char *, переданный параметру const *. Объяснение этому следующее.

Для вызовов функций, в которых виден прототип, аргументы преобразуются в типы параметров, как если бы они были назначены, согласно C 2018 6.5.2.2 7:

Если выражение, обозначающее вызываемую функцию, имеет тип, который включает в себя прототип, аргументы неявно преобразуются, как если бы путем присваивания, в типы соответствующих параметров, принимая тип каждого параметра за неквалифицированную версию его заявленного типа.…

(Обратите внимание, что «неквалифицированная версия объявленного типа» означает, что параметр const int или char * const будет int или char * соответственно, а не параметр const char * будет char *. )

6.5.16.1 2 говорит:

В простом присваивании (=) значение правого операнда преобразуется в тип выражения присваивания…

Тип выражения присваивания - тип левого операнда, 6.5.16 3:

… Типом выражения присваивания является тип, который будет иметь левый операнд после преобразования lvalue.…

Итак, теперь мы знаем, что char * преобразуется в const char *. Это также удовлетворяет ограничениям для присвоения в 6.5.16.1 1:

Должно быть выполнено одно из следующих действий:… левый операнд имеет атомарный, квалифицированный или неквалифицированный тип указателя и (учитывая тип, который левый операнд будет иметь после преобразования lvalue) оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов и тип, на который указывает слева, содержит все квалификаторы типа, на который указывает справа;…

И преобразование указателя указано в 6.3.2.3 2:

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

Для вызова snprintf аргумент "hello" передается в месте, соответствующем ... в параметрах. Для этого мы обратимся к остальной части 6.5.2.2 7, которая продолжается с первой части, указанной выше:

… Многоточие в деклараторе прототипа функции останавливает преобразование типа аргумента после последнего объявленного параметра. Повышение аргументов по умолчанию выполняется на конечных аргументах.

Значения по умолчанию для аргументов приведены в 6.5.2.2 6:

… целочисленные преобразования выполняются для каждого аргумента, а аргументы с типом float удваиваются. Они называются продвижениями аргументов по умолчанию .

Эти рекламные акции не влияют на указатели, поэтому указатель передается с неизменным типом. Это интересно, потому что мы могли бы передать здесь либо char *, либо const char *. Спецификация для snprintf относится к fprintf, что для спецификации %s указано в 7.21.6.1 8:

… аргумент должен быть указателем на начальный элемент массива символьного типа.…

Так что просто требуетсяs указатель на «тип символа», а не конкретный тип, такой как char или const char или volatile char.

(Мы могли бы также поинтересоваться, сработает ли наша собственная функция, такая как snprintf, и для ее использования <stdarg.h>, сработает ли передача аргумента char * и обработка его с помощью вызова макроса va_arg(ap, const char *). Мое первоначальное прочтение спецификации va_arg в 7.16.1.1 2 говорит о том, что типы должны быть совместимы, но char * и const char * несовместимы, но я не изучал это полностью.)

Сноска

1 Технически, строковый литерал - это вещь в исходном коде или его представление на этапах трансляции C, и он используется для создания массива char. Для простоты я буду ссылаться на массив как строковый литерал.

...