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

Строковые литералы const?

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

char *foo = "bar";

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

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

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

4b9b3361

Ответ 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 с "ПОЧЕМУ МОЙ ПРОГРАММИРОВАНИЕ?"!!! вопросы.