Можно ли изменить строку символа в C?

81

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

Вот что я пробовал:

Так есть ли способ изменить значения внутри строк, а не адреса указателя?

Мэтью Стопа
источник

Ответы:

158

Когда вы пишете «строку» в исходном коде, она записывается непосредственно в исполняемый файл, потому что это значение должно быть известно во время компиляции (существуют инструменты, позволяющие разобрать программное обеспечение и найти в них все простые текстовые строки). Когда вы пишете char *a = "This is a string", местоположение «Это строка» находится в исполняемом файле, а местоположение, на которое aуказывает, находится в исполняемом файле. Данные в исполняемом образе доступны только для чтения.

Что вам нужно сделать (как указывали другие ответы), так это создать эту память в месте, которое не только для чтения - в куче или в кадре стека. Если вы объявляете локальный массив, то в стеке выделяется пространство для каждого элемента этого массива, а строковый литерал (который хранится в исполняемом файле) копируется в это пространство в стеке.

вы также можете скопировать эти данные вручную, выделив некоторую память в куче, а затем используя strcpy()для копирования строкового литерала в это пространство.

Всякий раз, когда вы выделяете место с помощью, не malloc()забудьте позвонить, free()когда вы закончите с ним (читай: утечка памяти).

По сути, вы должны отслеживать, где находятся ваши данные. Всякий раз, когда вы пишете строку в своем источнике, эта строка доступна только для чтения (в противном случае вы потенциально изменили бы поведение исполняемого файла - представьте, если бы вы написали, char *a = "hello";а затем изменили a[0]на 'c'. Затем написали где-то еще printf("hello");. Если бы вам разрешили изменить первый символ "hello", и ваш компилятор сохранил его только один раз (должен), а затем printf("hello");выведет cello!)

Карсон Майерс
источник
12
В последнем разделе я многое объяснил, почему это нужно только для чтения. Спасибо.
CDR
1
-1: не указывает использовать const char *, и ничто не гарантирует, что буквальные строки хранятся в исполняемой памяти.
Bastien Léonard
Я разве вам не нужна const для двух решений, которые я дал - также, если строка известна во время компиляции и скомпилирована в исполняемый файл - где еще она могла бы храниться? В gcc, если я напишу char * a = "hallo."; или char b [] = "hello.";, тогда сборка выводит "LC0: .ascii" Hallo. \ 0 "LC1: .ascii" Hello. \ 0 "" оба находятся в исполняемой памяти ... Когда это не так ?
Carson Myers
1
Только что попробовал с GCC 4.4, он помещает буквальные строки в .rodata (данные только для чтения). Я проверил с помощью objdump и листинга сборки. Я не думаю, что стандарт требует, чтобы буквальные строки были доступны только для чтения, поэтому я думаю, что их можно даже поместить в .data.
Bastien Léonard
Кроме того, я не вижу никаких преимуществ в том, что указатель не квалифицируется как const. Это может скрыть ошибки, если позже вы решите изменить строку.
Bastien Léonard
29

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

Или, альтернативно, вы можете выделить память с помощью malloc, например

Джонатан Мэддисон
источник
5
Для завершения кода было бы хорошо, если бы вы также могли добавить вызов free ().
Naveen
15

Многие люди не понимают разницу между char * и char [] в сочетании со строковыми литералами в C. Когда вы пишете:

... вы фактически указываете foo на постоянный блок памяти (на самом деле, то, что компилятор делает с "hello world" в этом случае, зависит от реализации.)

Использование char [] вместо этого сообщает компилятору, что вы хотите создать массив и заполнить его содержимым, «привет, мир». foo - это указатель на первый индекс массива char. Оба они являются указателями char, но только char [] будет указывать на локально выделенный и изменяемый блок памяти.

Джефф Обер
источник
7

Память для a и b не выделяется вами. Компилятор может выбрать доступную только для чтения область памяти для хранения символов. Поэтому, если вы попытаетесь изменить, это может привести к ошибке сегмента. Поэтому я предлагаю вам создать массив символов самостоятельно. Что-то вроде:char a[10]; strcpy(a, "Hello");

Naveen
источник
1
Проблема с символьными массивами заключается в том, что я передаю указатель массива char функции, чтобы я мог манипулировать строкой там, а затем снова отправить ее. Похоже, мне, к сожалению, приходится использовать malloc.
Мэтью Стопа,
1
Нет, вы все еще можете использовать объект, выделенный в стеке. Например, если у вас есть функция void f (char * p); тогда из main () вы можете передать f (a). Это передаст в функцию адрес первого символа. Кроме того, если вы решите использовать malloc (), не забудьте освободить память с помощью free ().
Naveen
5

Кажется, что на ваш вопрос ответили, но теперь вы можете задаться вопросом, почему char * a = "String" хранится в постоянной памяти. Что ж, на самом деле он не определен стандартом c99, но большинство компиляторов выбирают его таким образом для таких случаев, как:

Стандарт c99 (pdf) [стр. 130, раздел 6.7.8]:

Декларация:

определяет "простые" объекты массива символов s и t, элементы которых инициализируются литералами символьной строки. Это объявление идентично char

Содержимое массивов можно изменять. С другой стороны, декларация

определяет p с типом «указатель на char» и инициализирует его, чтобы указать на объект с типом «массив символов» с длиной 4, элементы которого инициализируются литералом строки символов. Если попытаться использовать p для изменения содержимого массива, поведение не определено.

Суини
источник
4

Вы также можете использовать strdup:

Например:

Максим Шерами
источник
Не ответ на вопрос, но все же очень удобная функция, спасибо!
mknaf
1
+1 за то, что научил меня strdup. Я не уверен, когда я захочу его использовать.
Z-бозон
Когда вы делаете что-то вроде var = malloc(strlen(str) + 1); strcpy(var, str);, тогда вам, вероятно, следует использовать strdupвместо.
Максим Шерами,
3

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

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

Надеюсь, поможет. Удачи!


источник
Обратите внимание, что изменение строкового литерала является неопределенным поведением.
Steohan
0

Вам нужно скопировать строку в другой буфер памяти, а не только для чтения, и изменить ее там. Используйте strncpy () для копирования строки, strlen () для определения длины строки, malloc () и free () для динамического выделения буфера для новой строки.

Например (псевдокод типа C ++):

острый зуб
источник
0
Натан Феллман
источник
6
Для malloc требуется еще 1 байт. Не забывайте завершающий символ NULL, который ожидает strcpy и тоже будет копировать. Это очень частая ошибка.
xcramps