Будет ли инициализатор строки несколько тратить память?



Чтобы инициализировать массив символов, обычно я пишу:



char string[] = "some text";


Но сегодня один из моих одноклассников сказал, что нужно использовать:

char string[] = {'s', 'o', 'm', 'e', ' ', 't', 'e', 'x', 't', ''};


Я сказал ему, что это безумие-отказаться от удобочитаемости и краткости, но он утверждал, что инициализация массива символов строкой фактически создаст две строки, одну в стеке, а другую в памяти только для чтения. При работе со встроенными устройствами это может привести к недопустимым потерям памяти.

Конечно, инициализаторы строк кажутся яснее, поэтому я буду использовать их в своих программах. Но вопрос в том, будет ли инициализатор строки создавать две одинаковые строки? Или инициализаторы строк-это просто синтаксические сахара?

558   6  

6 ответов:

char string[] = "some text";

На 100% эквивалентно

char string[] = {'s', 'o', 'm', 'e', ' ', 't', 'e', 'x', 't', '\0'};

Ваш друг в замешательстве: если string является локальной переменной, то в обоих случаях вы создаете две строки. Ваша переменная string, которая находится в стеке, и строковый литерал только для чтения, который находится в памяти только для чтения (.rodata).

Избежать хранения только для чтения невозможно, так как все данные должны быть где-то распределены. Вы не можете выделить строковые литералы в воздухе. Все, что вы можете сделать, это переместить его из одной памяти только для чтения сегмент к другому, что даст вам тот же самый размер программы в конце концов в любом случае.

Первый стиль предпочтительнее в целом, так как он более удобочитаем. Это действительно форма синтаксического сахара.

Но это также предпочтительно, потому что это может облегчить некоторую оптимизацию компилятора, известную как "объединение строк", что позволяет компилятору хранить строковый литерал "some text" более эффективными для памяти способами. Если вы инициализируете строку символ за символом, компиляция может понять или не понять, что это строковая константа, доступная только для чтения.

После редактирования между этими двумя определениями нет никакой разницы. Оба будут создавать массив из десяти символов и инициализироваться с одинаковым содержимым.

Это на самом деле легко проверить: сначала проверьте, что sizeof дает вам для двух массивов, затем вы можете использовать, например, memcmp Для сравнения обоих массивов.

вторая инициализация почти равна первой, с одним решающим отличием: второй массив не заканчивается строкой.

Первый создает массив из десяти символов (включая Терминатор), а второй создает массив из девяти символов. Если вы не используете массив в качестве строки, то да, вы сохраните один элемент со второй инициализацией.

Стандарт C имеет "особый случай", который позволяет инициализировать массив строковым литералом:

§6.7.9 / 14 массив символьного типа может быть инициализирован символом строковый литерал или строковый литерал UTF-8, необязательно заключенный в фигурные скобки. Последовательные байты строкового литерала (включая завершающий null символ, если есть место или если массив неизвестного размера) инициализируйте элементы массива.

Вот и все. Он ничего не говорит. иначе, который был бы деталью реализации платформы и компилятора. В отличие от C++, который явно дает строковому литералу статическую длительность хранения, стандарт C не. Это подразумевается. Существуют распространенные расширения, позволяющие изменять строковые литералы, то есть не гарантируется, что они будут помещены в память только для чтения.

Семантически эти две строки идентичны. Но практические последствия будут зависеть от компилятора.

Экспериментируя с http://gcc.godbolt.org/ показывает различные стратегии:

  • Заполняйте массивы по одному символу за раз, используя последовательность movb инструкций (или эквивалент) с непосредственными операндами.

  • Заполните массивы по одному двойному слову за раз, используя пары movabsq / movq, где первое имеет непосредственное двойное слово операнд.

  • Скопируйте данные в массивы из Строковой константы, хранящейся в разделе .rodata.

Разные компиляторы использовали разные стратегии для этих двух случаев. В частности, gcc Нашел оптимизацию movabsq только для случая char string[] = "string literal";, что делает стратегию вашего друга несколько громоздче (потому что сгенерированный код имеет больше байтов).

Попытка использовать различные настройки оптимизации, вероятно, привела бы к еще большим вариациям.

Все ясно. что базовые данные должны храниться где-то в программе, будь то в разделе данных или в виде серии непосредственных операндов в исполняемом коде. Поскольку нецелесообразно выяснять или предполагать, как конкретный стиль может повлиять на способность данного компилятора оптимизировать, единственным рациональным подходом является использование стиля, который легче всего читать и поддерживать. (Полезным следствием является то, что компилятор, вероятно, также будет иметь самое простое время с наиболее распространенными стиль.)

В маловероятном случае, если это действительно критично для производительности,вам придется изучить код, созданный используемым компилятором. Но сначала вы должны спросить себя, действительно ли необходим инициализированный изменяемый буфер.

  • В первом случае string[] инициализируется с помощью литеральной Строковой константы длиной 10 байт, которая будет создана в сегменте только для чтения.

  • Во втором случае string[] инициализируется с помощью постоянного массива литеральных символьных констант длиной 10 байт, который будет создан в сегменте только для чтения.

Оба случая идентичны как семантически, так и в требовании к памяти. Первое-это просто синтаксический сахар для второго (и гораздо удобнее, и менее подвержен ошибкам).

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

const char* string = "some text" ;

Это создаст указатель string на постоянную строку , и может сохранить память при сравнении с A говорят:

#define string "some text"

, который может генерировать несколько копий "некоторого текста" везде, где используется макрос string. (Хотя большинство современных цепочек инструментов компилятора / компоновщика способны удалять дублирующиеся строки в любом случае). В первом случае вы можете взять адрес string и быть уверенным, что значение будет одинаковым для всех ссылок, в то время как макрос будет отличаться для каждой ссылки, не оптимизированной. Еще одно семантическое различие заключается в том, что для const char* string, sizeof(string) это размер a указатель, где для string[] это длина инициализатора (включая нулевой Терминатор)

Будет ли инициализатор строки создавать две одинаковые строки? Или инициализаторы строк-это просто синтаксические сахара?

Эти два случая совершенно различны:

1-й случай:

char string[] = "some text";  // <-- string initialization

Этот синтаксис является строковым и не может применяться к любому другому типу данных. Он автоматически добавляет символ \0 в конце, поэтому гарантируется, что библиотечная функция, такая как printf, знает, где заканчивается вывод (с помощью управляющей строки %s).


2-й дело:

char string[] = {'s', 'o', 'm', 'e', ' ', 't', 'e', 'x', 't'};  // <--  array initialization

Этот синтаксис должен инициализировать массив , но не строку. Синтаксис может использоваться для инициализации других типов массивов (например,int, long, и т.д.). Он никогда автоматически не добавляет \0 в конец массива. Поэтому было бы неправильно printf этот массив символов, используя элемент управления %s.


Короче говоря, это два разных синтаксиса инициализации, используемых для разных целей. Если вам нужна строка , то используйте первый синтаксис, если вы используете массив символов - тогда используйте второй.

Comments

    Ничего не найдено.