В C можно использовать строковый литерал в объявлении, например:
char s[] = "hello";
или вот так:
char *s = "hello";
Так в чем же разница? Я хочу знать, что на самом деле происходит с точки зрения продолжительности хранения, как во время компиляции, так и во время выполнения.
Ответы:
Разница здесь в том, что
поместит
"Hello world"
в доступные для чтения части памяти , иs
указатель на него сделает любую операцию записи в этой памяти недопустимой.При этом:
помещает буквенную строку в постоянную память и копирует строку во вновь выделенную память в стеке. Таким образом, делая
законны.
источник
"Hello world"
находится в «доступной только для чтения части памяти» в обоих примерах. Пример с массивом указывает на него, пример с массивом копирует символы в элементы массива.char msg[] = "hello, world!";
строку, заканчивающуюся в разделе инициализированных данных. Когда объявлено,char * const
чтобы закончить в разделе данных только для чтения. gcc-4.5.3Во-первых, в аргументах функции они абсолютно эквивалентны:
В других контекстах
char *
выделяет указатель, аchar []
выделяет массив. Вы спрашиваете, где находится строка в первом случае? Компилятор тайно выделяет статический анонимный массив для хранения строкового литерала. Так:Обратите внимание, что вы никогда не должны пытаться изменить содержимое этого анонимного массива с помощью этого указателя; эффекты не определены (часто означают сбой):
Использование синтаксиса массива напрямую выделяет его в новую память. Таким образом, модификация безопасна:
Однако массив живет только столько времени, сколько его охватывающая область, поэтому, если вы делаете это в функции, не возвращайте и не пропускайте указатель на этот массив - вместо этого сделайте копию с помощью
strdup()
или аналогичным образом. Если массив размещен в глобальной области видимости, конечно, проблем нет.источник
Эта декларация:
Создает один объект -
char
массив размером 6, называемыйs
, инициализированный значениями'h', 'e', 'l', 'l', 'o', '\0'
. От того, где этот массив размещен в памяти, и как долго он живет, зависит от того, где появляется объявление. Если объявление находится внутри функции, оно будет жить до конца блока, в котором оно объявлено, и почти наверняка будет размещено в стеке; если он находится вне функции, он, вероятно, будет храниться в «инициализированном сегменте данных», который загружается из исполняемого файла в доступную для записи память при запуске программы.С другой стороны, это объявление:
Создает два объекта:
char
с, содержащий значения'h', 'e', 'l', 'l', 'o', '\0'
, который не имеет имени и имеет статическую продолжительность хранения (что означает, что он живет в течение всей жизни программы); а такжеs
, которая инициализируется расположением первого символа в этом безымянном массиве только для чтения.Безымянный массив только для чтения обычно находится в «текстовом» сегменте программы, что означает, что он загружается с диска в постоянную память вместе с самим кодом. Расположение
s
переменной-указателя в памяти зависит от того, где появляется объявление (как в первом примере).источник
char s[] = "hello"
случае,"hello"
это просто инициализатор, сообщающий компилятору, как должен быть инициализирован массив. Это может приводить или не приводить к соответствующей строке в текстовом сегменте - например, еслиs
имеет статическую продолжительность хранения, тогда, вероятно, что единственный экземпляр"hello"
будет в инициализированном сегменте данных - сам объектs
. Даже если онs
имеет автоматическую продолжительность хранения, он может быть инициализирован последовательностью литеральных хранилищ, а не копией (например,movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
)..rodata
, который скрипт линкера затем сбрасывает в том же сегменте,.text
. Смотри мой ответ .char s[] = "Hello world";
строка помещается в постоянную память и копируется в новую выделенную память в стеке. Но ваш ответ говорит только о строковом положить в памяти только для чтения и пропускает вторую часть предложения , которая говорит:copies the string to newly allocated memory on the stack
. Итак, является ли ваш ответ неполным, если не указать вторую часть?char s[] = "Hellow world";
является только инициализатором и вовсе не обязательно хранится как отдельная копия только для чтения. Еслиs
имеет статическую длительность хранения, то единственная копия строки, скорее всего, будет находиться в сегменте чтения-записи в местеs
, и даже если нет, то компилятор может выбрать инициализацию массива с помощью инструкций немедленной загрузки или аналогичного, а не копирования из строки только для чтения. Дело в том, что в этом случае сама строка инициализатора не присутствует во время выполнения.Учитывая декларации
предположим следующую гипотетическую карту памяти:
Строковый литерал
"hello world"
представляет собой массив из 12 элементовchar
(const char
в C ++) со статической продолжительностью хранения, что означает, что память для него выделяется при запуске программы и остается выделенной до ее завершения. Попытка изменить содержимое строкового литерала вызывает неопределенное поведение.Линия
определяет
s0
как указатель наchar
с автоматическим хранением (то есть переменнаяs0
существует только для области, в которой она объявлена) и копирует в нее адрес строкового литерала (0x00008000
в этом примере). Следует отметить , что , посколькуs0
указывает на строку литерала, она не должна быть использована в качестве аргумента для любой функции , которая будет пытаться изменить его (например,strtok()
,strcat()
,strcpy()
и т.д.).Линия
определяет
s1
как массив из 12 элементовchar
(длина берется из строкового литерала) с продолжительностью автоматического хранения и копирует содержимое литерала в массив. Как видно из карты памяти, у нас есть две копии строки"hello world"
; разница в том, что вы можете изменить строку, содержащуюся вs1
.s0
иs1
взаимозаменяемы в большинстве контекстов; Вот исключения:Вы можете переназначить переменную так,
s0
чтобы она указывала на другой строковый литерал или другую переменную. Вы не можете переназначить переменную так,s1
чтобы она указывала на другой массив.источник
Тяга C99 N1256
Существует два различных варианта использования строковых литералов символов:
Инициализировать
char[]
:Это «больше волшебства», и описано в 6.7.8 / 14 «Инициализация»:
Так что это просто ярлык для:
Как и любой другой обычный массив,
c
может быть изменен.Везде: генерирует:
Поэтому, когда вы пишете:
Это похоже на:
Обратите внимание на неявное приведение от
char[]
кchar *
, которое всегда допустимо.Затем, если вы измените
c[0]
, вы также измените__unnamed
, что является UB.Это описано в 6.4.5 «Строковые литералы»:
6.7.8 / 32 «Инициализация» приводит прямой пример:
GCC 4.8 x86-64 ELF реализация
Программа:
Компилировать и декомпилировать:
Выход содержит:
Вывод: GCC хранит
char*
его в.rodata
разделе, а не в.text
.Однако обратите внимание, что скрипт компоновщика по умолчанию помещает
.rodata
и.text
в тот же сегмент , который имеет исполняемый файл, но не имеет разрешения на запись. Это можно наблюдать с:который содержит:
Если мы сделаем то же самое для
char[]
:мы получаем:
поэтому он сохраняется в стеке (относительно
%rbp
).источник
объявляется
s
массивом,char
который достаточно длинный, чтобы содержать инициализатор (5 + 1char
с), и инициализирует массив, копируя члены данного строкового литерала в массив.объявляет
s
указатель на один или несколько (в данном случае больше)char
и указывает его непосредственно на фиксированное (только для чтения) местоположение, содержащее литерал"hello"
.источник
s
указатель наconst char
.Здесь
s
находится массив символов, который может быть перезаписан, если мы хотим.Строковый литерал используется для создания этих блоков символов где-то в памяти, на которую
s
указывает этот указатель . Здесь мы можем переназначить объект, на который он указывает, изменив его, но пока он указывает на строковый литерал, блок символов, на который он указывает, не может быть изменен.источник
В дополнение, считают , что, как и для целей только для чтения использование обоих одинаково, вы можете получить доступ к полукокса путем индексации или с
[]
или*(<var> + <index>)
формат:А также:
Очевидно, если вы попытаетесь сделать
Вы, вероятно, получите ошибку сегментации, так как вы пытаетесь получить доступ к постоянной памяти.
источник
x[1] = 'a';
что будет происходить и с segfault (конечно, в зависимости от платформы).Просто добавлю: вы также получите разные значения для их размеров.
Как уже упоминалось выше, для массива
'\0'
будет выделен последний элемент.источник
Выше указано, что str указывает на буквальное значение «Hello», которое жестко запрограммировано в двоичном изображении программы, которое помечено как доступное только для чтения в памяти, означает, что любое изменение в этом строковом литерале является недопустимым, и это приведет к ошибкам сегментации.
копирует строку во вновь выделенную память в стеке. Таким образом, любое изменение в нем разрешено и законно.
изменит улицу на "Мелло".
Для более подробной информации, пожалуйста, перейдите к аналогичному вопросу:
Почему я получаю ошибку сегментации при записи в строку, инициализированную "char * s", но не "char s []"?
источник
На случай, если:
x является lvalue - его можно назначить. Но в случае:
x не lvalue, это rvalue - вы не можете присвоить ему.
источник
x
это неизменяемое значение lvalue. Тем не менее, почти во всех контекстах он будет указывать на свой первый элемент, и это значение является значением r.источник
В свете комментариев здесь должно быть очевидно, что: char * s = "hello"; Это плохая идея, и ее следует использовать в очень узких рамках.
Это может быть хорошей возможностью указать, что «правильная константность» - это «хорошая вещь». Всегда и везде Вы можете использовать ключевое слово «const» для защиты своего кода от «расслабленных» вызывающих или программистов, которые обычно наиболее «расслаблены», когда в игру вступают указатели.
Достаточно мелодрамы, вот чего можно добиться, украсив указатели «const». (Примечание. Нужно читать объявления указателей справа налево.) Вот 3 различных способа защитить себя при игре с указателями:
То есть объект DBJ нельзя изменить с помощью p.
То есть вы можете изменить объект DBJ через p, но вы не можете изменить сам указатель p.
То есть вы не можете изменить сам указатель p, а также не можете изменить объект DBJ через p.
Ошибки, связанные с попытками мутаций const-ant, обнаруживаются во время компиляции. Для const нет места во время выполнения или скорости.
(Предполагается, что вы используете компилятор C ++, конечно?)
--DBJ
источник