Являются ли строковые литералы константными? - PullRequest
21 голосов
/ 20 декабря 2010

И GCC, и Clang не жалуются, если я присваиваю строковый литерал для char*, даже когда используется множество педантичных опций (-Wall -W -pedantic -std=c99):

char *foo = "bar";

пока они (конечно) жалуются, если я присваиваю const char* char*.

Означает ли это, что строковые литералы считаются типа char*? Разве они не должны быть const char*? Не определено поведение, если они изменяются!

И (некоррелированный вопрос), как насчет параметров командной строки (т.е.: argv): он считается массивом строковых литералов?

Ответы [ 8 ]

21 голосов
/ 20 декабря 2010

Они имеют тип char[N], где N - это количество символов, включая завершающий \0.Так что да, вы можете назначить их char*, но вы по-прежнему не можете писать в них (эффект будет неопределенным).

Wrt argv: указывает на массив указателей на строки.Эти строки являются явно модифицируемыми.Вы можете изменить их, и они должны содержать последнее сохраненное значение.

8 голосов
/ 09 сентября 2013

Для полноты изложения проект стандарта C99 ( C89 и C11 имеют аналогичную формулировку ) в разделе 6.4.5 Строковые литералы, параграф 5 гласит:

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

Таким образом, это говорит о том, что строковый литерал имеет статическую продолжительность хранения ( длится время жизнизапрограммируйте ), а его тип - char[] (не char *), а его длина равна размеру строкового литерала с добавленным нулем.* Пункт 6` гласит:

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

Итак, попытка изменить строковый литерал - это неопределенное поведение независимо от того, что они не const.

Относительно argv в разделе 5.1.2.2.1 Пункт запуска программы 2 говорит:

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

[...]

- Параметры argc иargv и строки, на которые указывает массив argv, должны изменяться программой и сохранять свои последние сохраненные значения между запуском программы и завершением программы.

Так что argv не считается массивомстроковые литералы, и это нормально, чтобы изменить содержимое argv.

6 голосов
/ 20 декабря 2010

Используя опцию -Wwrite-strings, вы получите:

warning: initialization discards qualifiers from pointer target type

Независимо от этой опции, GCC помещает литералы в раздел памяти только для чтения, если не указано иное с помощью -fwritable-strings (однако эта опция была удалена из последних версий GCC).

Параметры командной строки не являются константными, они обычно находятся в стеке.

3 голосов
/ 30 октября 2013

(Извините, я только что заметил, что этот вопрос помечен как c, а не c++. Возможно, мой ответ в конце концов не имеет отношения к этому вопросу!)

Строковые литералы не совсем const или not-const, есть специальное странное правило для литералов.

( Сводка : Литералы могут быть взяты по ссылке на массив как foo( const char (&)[N]) и не могут быть приняты как неконстантный массив. Они предпочитают распадаться до const char *. Пока что создается впечатление, что они const. Но существует специальное устаревшее правило, которое позволяет литералам уменьшаться до char *. См. Эксперименты ниже.)

(После экспериментов, проведенных на clang3.3 с -std=gnu++0x. Возможно, это проблема C ++ 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")) работает через неконстантный вызов.

Итак, литералы "более постоянны", чем arr_c (потому что они предпочитают распадаться до const char *, в отличие от arr_c. Но литералы "менее постоянны", чем 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, а неконстантная версия используется 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 * литералом.

2 голосов
/ 20 декабря 2010

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

По поводу вашего вопроса о argv:

Параметры argc и argv и строки, на которые указывает массив argv должны быть изменены программой, и сохранить их последние сохраненные значения между запуском программы и программой завершение.

1 голос
/ 20 декабря 2010

Строковые литералы имеют формальный тип char [], но семантический тип const char []. Пуристы ненавидят это, но это, как правило, полезно и безвредно, за исключением того, что мы приносим в SO много новичков с надписью «ПОЧЕМУ МОЯ ПРОГРАММА ЛИШИТСЯ?!?!» вопросы.

1 голос
/ 20 декабря 2010

Они являются const char *, но есть специальное исключение для присвоения им char * для устаревшего кода, существовавшего до const. И аргументы командной строки определенно не являются литеральными, они создаются во время выполнения.

1 голос
/ 20 декабря 2010

И в C89, и в C99 строковые литералы имеют тип char * (по историческим причинам, насколько я понимаю). Вы правы, что попытка изменить одно приводит к неопределенному поведению. GCC имеет специальный флаг предупреждения -Wwrite-strings (который не является частью -Wall), который предупредит вас, если вы попытаетесь это сделать.

Что касается argv, аргументы копируются в адресное пространство вашей программы и могут быть безопасно изменены в вашей функции main().

РЕДАКТИРОВАТЬ : К сожалению, -Wno-write-strings скопировано случайно. Обновлено с правильной (положительной) формой флага предупреждения.

...