Чи можна змінити рядок char в C?


81

Я кілька годин боровся з усілякими підручниками та книгами, пов’язаними з покажчиками, але що я справді хочу знати, чи можливо змінити вказівник на символ після його створення.

Це те, що я спробував:

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

Відповіді:


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 = "привіт."; або char b [] = "привіт." ;, тоді збірка виводить "LC0: .ascii" Привіт. \ 0 "LC1: .ascii" Привіт. \ 0 "" обидва знаходяться у виконуваній пам'яті ... Коли це не так ?
Карсон Майерс

1
Щойно спробувавши GCC 4.4, він поміщає буквальні рядки в .rodata (дані лише для читання). Я перевірив objdump та список складання. Я не думаю, що стандарт вимагає, щоб буквальні рядки були лише для читання, тому я думаю, що їх можна навіть помістити в .data.
Бастієн Леонар

Крім того, я не бачу жодної переваги в тому, щоб не кваліфікувати вказівник як const. Це може приховати помилки, якщо пізніше ви вирішите змінити рядок.
Bastien Léonard

29

Ні, ви не можете змінити його, оскільки рядок може зберігатися в пам’яті лише для читання. Якщо ви хочете його змінити, ви можете замість нього використовувати масив, наприклад

Або ж ви можете розподілити пам’ять, використовуючи malloc, наприклад


5
Для заповнення коду буде добре, якщо ви також можете додати дзвінок free ().
Naveen

15

Багато людей плутаються у різниці між символами char * та char [] у поєднанні з рядковими літералами на C. Коли ви пишете:

... ви фактично вказуєте foo на постійний блок пам'яті (насправді те, що робить компілятор із "привіт світом" у цьому випадку, залежить від реалізації.)

Натомість використання char [] повідомляє компілятору, що ви хочете створити масив і заповнити його вмістом, "привіт світ". foo - це вказівник на перший індекс масиву char. Вони обидва є вказівниками на символи, але лише char [] вказуватиме на локально виділений та змінний блок пам'яті.


7

Ви не виділяєте пам’ять для a & b. Компілятор може вільно вибрати місце для читання лише для читання символів. Отже, якщо ви спробуєте змінити це може призвести до помилки сегмента. Тому я пропоную вам створити масив символів самостійно. Щось на зразок:char a[10]; strcpy(a, "Hello");


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" та ініціалізує його, щоб вказати на об'єкт типу "масив 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.