Этот ответ для 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
. Для простоты я буду ссылаться на массив как строковый литерал.