Строковые литералы const?
Оба GCC и Clang не жалуются, если я присваиваю строковый литерал char*
, даже если вы используете множество педантичных опций (-Wall -W -pedantic -std=c99
):
char *foo = "bar";
пока они (конечно) жалуются, если я назначаю const char*
a char*
.
Означает ли это, что строковые литералы считаются типом char*
? Разве они не должны быть const char*
? Это не определено поведение, если они модифицируются!
И (некоррелированный вопрос) о параметрах командной строки (например: argv
): считается ли это массивом строковых литералов?
Ответы
Ответ 1
Они имеют тип char[N]
, где N
- количество символов, включая завершающий \0
. Поэтому да, вы можете назначить их char*
, но вы все равно не можете писать им (эффект будет undefined).
Wrt argv
: он указывает на массив указателей на строки. Эти строки явно изменяемы. Вы можете изменить их, и они должны удерживать последнее сохраненное значение.
Ответ 2
Используя параметр -Wwrite-strings
, вы получите:
warning: initialization discards qualifiers from pointer target type
Независимо от этой опции, GCC помещает литералы в раздел памяти только для чтения, если не сказать иначе, используя -fwritable-strings
(однако эта опция была удалена из последних версий GCC).
Параметры командной строки не являются константами, они обычно находятся в стеке.
Ответ 3
В целях полноты C99 проект стандарта (C89 и C11 имеют схожие формулировки) в разделе 6.4.5
Строковые литералы в параграфе 5 гласят:
[...] к каждой многобайтовой последовательности символов, полученной из строкового литерала или литералов, добавляется байт или код значения значение. Последовательность многобайтовых символов затем используется для инициализации массива статической продолжительности хранения и длины, достаточной для того, чтобы содержать последовательность. Для символьных строковых литералов элементы массива имеют тип char, и инициализируются отдельными байтами многобайтовой последовательности символов, [...]
Итак, это говорит о том, что строковый литерал имеет статическую продолжительность хранения (длится время жизни программы), и он имеет тип char[]
(not char *
), а его длина - это размер строкового литерала с добавленным нулем. * В пункте 6 говорится:
Если программа пытается изменить такой массив, поведение undefined.
Таким образом, попытка изменить строковый литерал undefined поведение, независимо от того, что они не const
.
Относительно argv
в разделе 5.1.2.2.1
В пункте 2 запуска программы говорится:
Если они объявлены, параметры главной функции должны подчиняться следующим Ограничения:
[...]
- Параметры argc и argv и строки, на которые указывает массив argv, должны быть модифицированы программой и сохраняют их последние сохраненные значения между программой запуск и завершение программы.
Итак, argv
не считается массивом строковых литералов, и нормально модифицировать содержимое argv
.
Ответ 4
(Извините, я только что заметил, что этот вопрос отмечен как c
, а не c++
. Возможно, мой ответ не так важен для этого вопроса!)
Строковые литералы не совсем const
или not-const
, для литералов существует особое странное правило.
( Сводка: литералы могут быть взяты с помощью ссылки на массив как foo( const char (&)[N])
и не могут быть приняты как неконстантный массив. Они предпочитают распадаться на const char *
. Пока что, что это похоже на то, что они const
. Но существует специальное правило устаревания, которое позволяет литералам распадаться на char *
. См. эксперименты ниже.)
(После экспериментов, выполненных на clang3.3 с -std=gnu++0x
. Возможно, это проблема С++ 11 или конкретная для clang? В любом случае, происходит что-то странное.)
Сначала литералы выглядят как const
:
void foo( const char * ) { std::cout << "const char *" << std::endl; }
void foo( char * ) { std::cout << " char *" << std::endl; }
int main() {
const char arr_cc[3] = "hi";
char arr_c[3] = "hi";
foo(arr_cc); // const char *
foo(arr_c); // char *
foo("hi"); // const char *
}
Оба массива ведут себя так, как ожидалось, демонстрируя, что foo
может сказать нам, является ли указатель const
или нет. Затем "hi"
выбирает версию const
foo
. Так кажется, что это решает: литералы const
... не так ли?
Но, если вы удалите void foo( const char * )
, тогда это станет странным. Во-первых, вызов foo(arr_c)
завершается с ошибкой во время компиляции. Это ожидается. Но буквальный вызов (foo("hi")
) работает через неконстантный вызов.
Итак, литералы "больше const", чем arr_c
(потому что они предпочитают распадаться на const char *
, в отличие от arr_c
. Но литералы "меньше const", чем arr_cc
, потому что они готовы распадаться на char *
при необходимости.
(Clang дает предупреждение, когда оно распадается на char *
).
Но как насчет распада? Позвольте избежать этого для простоты.
Вместо этого возьмем массивы по ссылке в foo. Это дает нам более "интуитивные" результаты:
void foo( const char (&)[3] ) { std::cout << "const char (&)[3]" << std::endl; }
void foo( char (&)[3] ) { std::cout << " char (&)[3]" << std::endl; }
Как и раньше, литерал и массив const (arr_cc
) используют версию const, а версия non-const используется arr_c
. И если мы удалим foo( const char (&)[3] )
, мы получим ошибки как с foo(arr_cc);
, так и с foo("hi");
. Короче говоря, если мы избежим распада указателя и будем использовать ссылку на массив, то литералы будут вести себя так, как будто они const
.
Шаблоны
В шаблонах система выведет const char *
вместо char *
, и вы "застряли" с этим.
template<typename T>
void bar(T *t) { // will deduce const char when a literal is supplied
foo(t);
}
Таким образом, буквально ведет себя как const
во все времена, за исключением конкретного случая, когда вы непосредственно инициализируете char *
литералом.
Ответ 5
Ответ Йоханнеса верен относительно типа и содержания. Но в дополнение к этому, да, поведение undefined заключается в изменении содержимого строкового литерала.
Относительно вашего вопроса о argv
:
Параметры argc и argv и строки, на которые указывает массив argv могут быть изменены программой, и сохранить их сохраненные значения между запуском программы и программой прекращение.
Ответ 6
В обоих C89 и C99 строковые литералы имеют тип char *
(по историческим причинам, насколько я понимаю). Вы правы, что пытаетесь изменить один результат в undefined. GCC имеет специальный предупреждающий флаг - Wwrite-strings (который не является частью -Wall
), который предупредит вас, если вы попытаетесь сделать поэтому.
Что касается argv
, аргументы копируются в адресное пространство вашей программы и могут быть безопасно изменены в вашей функции main()
.
EDIT: Упс, если -Wno-write-strings
скопирован случайно. Обновлен с правильной (положительной) формой предупреждающего флага.
Ответ 7
Они const char *, но существует специальное исключение для назначения их char * для устаревшего кода, существовавшего до выполнения const. И аргументы командной строки определенно не являются буквальными, они создаются во время выполнения.
Ответ 8
Строковые литералы имеют формальный тип char []
, но семантический тип const char []
. Пуристы ненавидят его, но это вообще полезно и безвредно, за исключением того, что приносят много новичков к SO с "ПОЧЕМУ МОЙ ПРОГРАММИРОВАНИЕ?"!!! вопросы.