Evg |
СОДЕРЖАНИЕ
Строковые литералы в Си/Си++
Метки c++
ВНИМАНИЕ! Вопросы по существу обсуждаемого вопроса просьба задавать здесь или создать тему на форуме и кинуть на неё ссылку в блог или мне в личку.
Объясняю почему
Причин для этого несколько. Я, как и любой другой автор, всегда могу упустить интересный момент обсуждаемой темы (что подтвердилось на практике). А потому задаваемый вопрос может закрывать пробел в статье. Ответ на конкретный вопрос, как правило, дать несложно. Сложнее его аккуратно сформулировать так, чтобы ответ являлся законченной частью статьи. Поэтому, как правило, на первых порах я ограничиваюсь конкретным ответом на конкретный вопрос, а в статью временно вставляю ссылку на пост, где был дан ответ. А когда дойдут руки, то вместо ссылки пишу нормальное пояснение. Технические возможности блога не позволяют в комментариях пользоваться широкими возможностями, доступными на форуме (то как выделение текста жирным, вставка фрагментов исходников в удобном для чтения виде и т.п.), поэтому будет удобнее, если вопрос и ответ будут опубликованы на форуме Любая статья является изложением знаний в общем случае. У многих людей мышление устроено так, что прочтя на форуме конкретный вопрос и конкретный ответ на этот вопрос, у них появится бОльшее понимание, чем после прочтения теоретических выкладок (даже если они подкреплены конкретными примерами). Ссылки на такие обсуждения я, как правило, включаю в последний раздел статьи. Начинающие, как правило, поиск ответов на свои вопросы ведут именно в форуме, а не в блогах. А потому конкретный вопрос и конкретный ответ для них будет более удобным и полезным именно на форуме. Многие люди умеют работать методом тыка, лишь бы был конкретный пример в качестве образца. А потому такое обсуждение будет им полезным даже без прочтения статьи Исторически сложилось, что раньше (когда ещё не было блога) статьи располагались на форуме и представлены были в виде двух тем. Первая тема создавалась в специально отведённой свалке и представляла собой черновик, который со временем дорабатывался до законченной статьи. После этого статья переезжала во вторую тему в тематическом разделе. А первая тема оставалась дополнительной свалкой для замечаний и мелких вопросов по теме. Ссылку на старое местоположение данной свалки я помещаю в начале статьи. Вопросы, по возможности, прошу создавать в отдельных темах, но если вопрос действительно мелкий, то можно его задать и в указанной свалке.
1. Общие сведения Ещё одна конструкция языков Си и Си++, которая часто приводит в затруднение начинающих - это строковой литерал. Начинающим, как правило, трудно понять, чем принципиально отличаются две следующие конструкции: C char str1[] = "abc"; char *str2 = "abc"; Семантика строкового литерала в языках C/C++ имеет двоякий смыл и зависит от того, в каком месте кода он (строковой литерал) встретился. И эти два различных случая отражены в вышеидущем примере. 2. Строковой литерал на позиции инициализатора массива char'ов Первая возможность использования строкового литерала - это инициализация массива char'ов. Данная конструкция возникает только в тех случаях, когда описывается переменная, являющаяся массивом char'ов и через знак "=" написана строка, которая инициализирует данный массив. В таком случае строковой литерал следует трактовать как константный массив-инициализатор, состоящих из элементов типа char. Т.е. запись C char str[] = "abc"; C char str[] = { 'a', 'b', 'c', '\0' }; В первом комментарии к статье мне любезно сообщили об ошибке. Поведение для Си и для Си++ немного различается. А потому нижеидущий текст (до конца раздела 2) гарантированно справедлив для языка Си, но требует небольшой доработки для языка Си++ При этом есть один очень хитрый момент, касающийся включения в этот инициализатор элемента '\0'. И этот момент иногда вводит в заблуждение даже тех, кто имеет хороший опыт программирования. Если массив задан без указания размера (т.е. с пустыми квадратными скобками), то в инициализатор включается хвостовой символ '\0', как это было указано в вышеидущем примере. Т.е., условно говоря, sizeof от инициализатора (и, соответственно, переменной типа массив) будет равен количеству символов в строке плюс единица. Однако если у массива указан размер, то хвостовой '\0' в инициализатор НЕ включается. Таким образом, если мы напишем C char str[4] = "abc"; C char str[4] = { 'a', 'b', 'c' }; C char str[4] = { 'a', 'b', 'c', '\0' }; Если мы напишем такой пример: C #include <stdio.h> char a[3] = "abc"; char b[3] = "def"; char c[] = "ghi"; int main (void) { printf ("%s\n", a); return 0; } Конструкция C char str[2] = "abc"; В случае с инициализацией многомерных массивов char'ов имеем всё ровно то же самое: C char a[3][6] = { "abc", "defg", "hijklm" }; C char a[3][6] = { { 'a', 'b', 'c' }, { 'd', 'e', 'f', 'g' }, { 'h', 'i', 'j', 'k', 'l', 'm' } }; C char a[3][6] = { { 'a', 'b', 'c', '\0', '\0', '\0' }, { 'd', 'e', 'f', 'g', '\0', '\0' }, { 'h', 'i', 'j', 'k', 'l', 'm' } }; 3. Строковой литерал внутри оператора sizeof Работу оператора sizeof следует трактовать примерно как: вычислить размер переменной, которая потребовалась бы для хранения выражения, являющегося аргументом sizeof. Исходя из этого C a = sizeof ("abcdefgh"); C char __tmp_obj[] = "abcdefgh"; a = sizeof (__tmp_obj); 4. Строковой литерал во всех прочих случаях Во всех прочих случаях строковой литерал трактуется как НЕявно заведённый статический константный объект типа массив char'ов, инициализированный символами данного строкового литерала с включением неявного завершающего нуля, и далее взятие адреса на нулевой элемент данного объекта. Теперь всё это же самое, выраженное в виде программы: C "abc" C static char const __tmp_obj[] = "abc"; &__tmp_obj[0]
C static char const __tmp_obj1[] = "abc"; static char const __tmp_obj2[] = "def"; char *str1 = &__tmp_obj1[0]; char str2[256]; strcpy (str2, &__tmp_obj2[0]); C char *s[3] = { "abc", "defg", "hijklm" }; C static char const __tmp_obj1[] = "abc"; static char const __tmp_obj2[] = "defg"; static char const __tmp_obj3[] = "hijklm"; char *s[3] = { &__tmp_obj1[0], &__tmp_obj2[0], &__tmp_obj3[0] }; C static char const __tmp_obj1[4] = { 'a', 'b', 'c', '\0' }, static char const __tmp_obj2[5] = { 'd', 'e', 'f', 'g', '\0' }, static char const __tmp_obj3[7] = { 'h', 'i', 'j', 'k', 'l', 'm', '\0' } char *s[3] = { &__tmp_obj1[0], &__tmp_obj2[0], &__tmp_obj3[0] }; 5. Какие из всего этого следуют выводы 5.1. Хранение больших массивов строк Посмотрите на коды, которые требуются для хранения массива строк, которые мы использовали в конце раздела 2 и в конце раздела 4. Образ памяти, который получаются в примере из раздела 2 (хранение через двумерный массив char'ов) будет отличаться от образа памяти, полученного в примере из раздела 4 (хранение через массив указателей). При хранении через массив указателей каждая отдельно взятая строка потребляет ровно столько памяти, сколько требуется для её хранения. А в примере с хранением через двумерный массив каждая строка (которая является элементом массива) потребляет одинаковое количество памяти, определяемое последней размерностью массива (фактически самой длинной строкой). Т.е. если мы имеем 1000 строк, 999 из которых занимают 3 байта, а одна строка занимает 500 байт, то в варианте с двумерным массивом мы будем иметь массив размером 500.000 байт, а в варианте работы через массив указателей будем иметь 999*3 + 500 байт, которые потребуются для хранения непосредственно строк, плюс 1000 указателей на строки. И таким образом, вариант хранения через указатели будет более экономным по памяти. А если у нас 1000 строк и все занимают по 500 байт, то в обоих случаях нам потребуется 500.000 байт на хранение непосредственно строк, плюс дополнительные 1000 указателей в варианте хранения через указатели (которые не нужны в варианте с двумерным массивом). Поэтому с точки зрения экономии памяти выбор между двумя вариантами осуществляется на основании сбалансированности строк: если строки равные по длине, то их эффективнее хранить в двумерном массиве. Если строки сильно различаются по длине, то эффективнее хранить их через массив указателей 5.2. Когда можно, а когда нельзя модифицировать строку После прочтения разделов 2 и 4, как мне кажется, данный вопрос должен отпасть сам собой. Но для закрепления материала всё-таки пройдусь по этому моменту, т.к. не раз видел, что он вызывает затруднение у начинающих. C char str[] = "abc"; str[0] = 'q'; C char *str = "abc"; str[0] = 'q'; При распределении переменных в память многие современные компиляторы константные объекты складывает в отдельные сегменты памяти, которые при исполнении попадают в read-only память и запись в такие переменные будет запрещена средствами операционной системы. То же самое касается и неявных константных объектов, возникающих из строковых литералов. Так, например, при компиляции второго примера из данного раздела компилятором gcc под linux'ом мы получим слом на исполнении из-за попытки записать в read-only память. В то время как при использовании borland'овского компилятора под windows данный пример успешно работает. В мире всё ещё используется софт, исходники которого написаны очень давно, когда ещё и стандарт Си толком не появился (напомню, что стандарт Си появился лишь через несколько лет после появления языка), когда компиляторы были слабыми, а операционные системы однозадачными. И в таком софте зачастую присутствует код, модифицирующий память, отведённую под строковой литерал в константном случае. Для таких случаев, например, у компилятора gcc есть опция -fwritable-strings, по которой строки от строковых литералов НЕ помещаются в read-only сегменты. Т.е. у gcc есть два режима работы, но по умолчанию выставлен более строгий (и более правильный) режим. Возможно, что и у упомянутого borland'овского компилятора есть два режима, но по умолчанию выставлен менее строгий режим. Но ковыряться в настройках лениво. FIXME надо бы с этим вопросом разобраться 5.3. Возврат строки из функции FIXME написать Демонстрирующий пример тут 5.4. Переиспользование памяти под неявные объекты для строк Как уже говорилось выше, данный код C char *str1 = "abc"; char *str2 = "abc"; C static char const __tmp_obj1[] = "abc"; static char const __tmp_obj2[] = "abc"; char *str1 = &__tmp_obj1[0]; char *str2 = &__tmp_obj2[0]; C static char const __tmp_obj[] = "abc"; char *str1 = &__tmp_obj[0]; char *str2 = &__tmp_obj[0]; FIXME написать Речь идёт о выражениях типа "abc"[i] 6. Склеивание строковых литералов компилятором FIXME написать Имелось в виду то, что написано тут в разделе 3.3.4 по части конкатенации строковых литералов 7. Ссылки на темы, где обсуждался данный вопрос |
Всего комментариев 8
Комментарии
-
Запись от D.E.S.P.E.R.O. размещена 25.06.2013 в 15:53
Обновил(-а) D.E.S.P.E.R.O. 25.06.2013 в 15:54 -
Запись от D.E.S.P.E.R.O. размещена 25.06.2013 в 16:20 -
Запись от Evg размещена 10.07.2013 в 15:09 -
Не по теме:
то при исполнении на многих компиляторах мы получим код, который при исполнении напечатает нам "abcdefghi"
а можем получить и abc и abcXdefXghi"где X любой символ может и не печатный
все дело в выравнивании
компилятор может выравнивать адреса структур на кратное степени двойки значение( для 32 обычно кратно 4)
и все дело где объявлен массив если в глобальной памяти где память обнуляется то "хвост" будет содержать 0 что равнозначно концу строки, а если в автоматической памяти то там будет скорее всего мусор
пример
но скомпилировать чтобы проверить не смогC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
char a[3]="abc"; char b[3]="def"; char c[]="qwe"; int main() { char al[3]="abc"; char bl[3]="def"; char cl[]="qwe"; printf("%s",a); printf("\n----------------------\n"); printf("%s",al); return 0; }
все мои компиляторы дают ошибку что размер массива меньшеЗапись от ValeryS размещена 28.01.2014 в 09:28 -
удалось скомпилировать под онлайн компилятор
http://codepad.org/vYvo3ACw
но результат прямо противополжный
вот модифицированый код
Qt и VS дают абсолютно разные результатыC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.h> char a[3]={'a','b','c'}; char b[3]={'d','e','f'}; char c[]={'q','w','e','\0'}; int main() { char al[3]={'a','b','c'}; char bl[3]={'d','e','f'}; char cl[]={'q','w','e','\0'}; printf("%s",a); printf("\n----------------------\n"); printf("%s",al); return 0; }
это я к тому писал что поправь размер на 4( кратно двойке) тогда результат будет похож
по крайней мере в области глобальныхC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.h> char a[4]={'a','b','c','1'}; char b[4]={'d','e','f','2'}; char c[]={'q','w','e','\0'}; int main() { char al[4]={'a','b','c','1'}; char bl[4]={'d','e','f','2'}; char cl[]={'q','w','e','\0'}; printf("%s",a); printf("\n----------------------\n"); printf("%s",al); return 0; }
локальные тоже по разному высвечиваютсяЗапись от ValeryS размещена 28.01.2014 в 09:45 -
> это я к тому писал что поправь размер на 4( кратно двойке) тогда результат будет похож
Не "результат будет похож", а "будет таким же с гораздо большей вероятностью". Тест-то некорректный, а потому тут нету "правильного" результата. Размер массива 4 приведёт к тому, что увеличится вероятность того, что пример на разных компиляторах выдаст один и тот же результат.
На самом деле по честному надо не только в этом тесте заменить 3 на 4, но и во всех вышеидущих примерах, потому что везде идёт объяснение на примере строки из трёх символов. Чтобы не было незаметного перехода от трёх к четырём, из-за которого у многих возникнут непонятки на ровном месте, из-за того, что этот переход не заметили. Поэтому надо аккуратно сесть и по всей статье симметрично всё заменить. Надо будет этим занятьсяЗапись от Evg размещена 30.01.2014 в 18:47 -
А в чем отличие
отC++ 1
char *str;
?C++ 1
char* str;
Запись от laby размещена 14.07.2016 в 09:04 -
Запись от Nameless One размещена 17.07.2016 в 11:06