Адреси двох вказівників на різні символи рядків однакові


80

Коли я друкую значення двох покажчиків, це друкує одну і ту ж адресу. Чому?


66
Чому, на вашу думку, не слід? Обидва вказівники вказують на абсолютно одне і те ж. Те, що ви бачите, - це, мабуть, ефект методики оптимізації, яка називається об’єднання рядків.
Даніель Каміль Козар

2
Хоча дані однакові, але змінні різні.
seereddi sekhar

2
Змінні, звичайно, різні. Якби ви взяли адресу pі p1, то ви б помітили, що ці два вказівники зберігаються за двома різними адресами. Той факт, що їх значення однакове, - в даному випадку - не має значення.
Даніель Каміль Козар

Так, якщо я зміню значення, то адреси будуть різними.
seereddi sekhar

11
@JanHudec: Прочитайте питання ще раз. У цьому випадку (завдяки оптимізації компілятора) p == p1(вони не відрізняються), але &p != &p1(вони різняться).
MSalters

Відповіді:


87

Залежно від реалізації залежить, чи два різні рядкові літерали з однаковим вмістом розміщені в одному і тому ж місці пам'яті, або різні місця в пам'яті.

Слід завжди лікувати pіp1 як два різні вказівники (навіть якщо вони мають однаковий вміст), оскільки вони можуть або не можуть вказувати на одну адресу. Не слід покладатися на оптимізацію компілятора.

C11 Standard, 6.4.5, String literals, семантика

Не визначено, чи відрізняються ці масиви за умови, що їх елементи мають відповідні значення. Якщо програма намагається змінити такий масив, поведінка не визначена.


Формат для друку повинен бути %p:

Дивіться цю відповідь, чому.


Я використовував летючі, так що не повинно бути ніяких оптимізацій пам'яті, навіть якщо вони приймають одну і ту ж адресу. Одне питання полягає в тому, що якщо я модифікую один із покажчиків, чи будуть змінені також дані в іншому вказаному.
Megharaj

8
@Megharaj i modify one of the pointer, will the data in the other pointed also be modifiedВи можете змінити вказівник, але не літеральний рядок. Наприклад char *p="abc"; p="xyz";, цілком добре, тоді як char *p="abc"; p[0]='x';викликає невизначену поведінку . Це не має нічого спільного volatile. Незалежно від того, використовуєте ви volatileчи ні, не слід змінювати будь-яку поведінку, яка нас тут цікавить. volatileв основному змушує щоразу читати дані з пам'яті.
PP

2
@MSharathHegde Так. Тому що pвказує на рядковий літерал "abc"і p[0]='x'намагається змінити перший символ рядкового літералу. Спроба модифікувати рядовий літерал є невизначеною поведінкою в C.
PP

2
@MSharathHegde Оскільки стандарт C стверджує, що. Причина в основному історична, оскільки достандартна мова C дозволяла змінювати рядкові літерали. Пізніше стандарт C (C89) зробив його невизначеним, так що новий код не робить цього, а старий код (попередній стандарт) працює як раніше. В основному це компроміс, якщо не порушити існуючий (до-станардський) код, я вважаю. Іншою причиною є тип рядкового літералу char []в C. Тому, щоб зробити його const char*доступним лише для читання ( як це має місце в C ++), потрібно також змінити тип . [продовження]
PP

7
У додатку C у другому виданні K&R є рядок: "Strings are no longer modifiable, and so may be placed in read-only memory"історичний доказ того, що рядкові літерали раніше можна було модифікувати ;-)
PP

28

Ваш компілятор здається досить розумним, виявляючи, що обидва літерали однакові. І оскільки літерали є постійними, компілятор вирішив не зберігати їх двічі.

Здається, варто згадати, що це не обов'язково має бути так. Будь ласка , дивіться Blue Moon "S відповідь на це .


До речі: printf()Заява повинна виглядати так

як "%p"буде використовуватися для друку значень покажчика, і він визначається лише для вказівника типу void *. * 1


Також я б сказав, що код пропускає returnтвердження, але стандарт C, схоже, перебуває в процесі зміни. Інші можуть люб’язно це пояснити.


* 1: Трансляція void *сюди потрібна не для char *вказівників, а для вказівників на всі інші типи.


Дякую. Отже, висновок - оптимізація компілятора, чи не так? у головній функції C за замовчуванням повертає 0
seereddi sekhar

@seereddisekhar: Так, це свого роду оптимізація.
alk

2
@seereddisekhar Але будьте обережні, це не означає, що вам слід порівнювати два рядки (навіть покажчик), ==використовуючи strcmpy()функцію. Оскільки інший компілятор може не використовувати оптимізацію (це до компілятора - реалізація залежала), як Алк відповів PS: Blue Moon щойно додав про це.
Grijesh Chauhan

2
Шановний @Megharaj! Чи можу я люб'язно попросити поставити окреме запитання з цього приводу? Ви можете розмістити тут посилання на це нове запитання як коментар.
alk

1
@Megharaj: Ви не можете змінити значення рядкового літералу. Як я вже згадував у своєму питанні, воно постійне.
alk

18

Ваш компілятор зробив щось, що називається "об'єднання рядків". Ви вказали, що хочете два вказівники, обидва вказують на один і той самий літеральний рядок - отже, він створив лише одну копію літералу.

Технічно: він повинен був скаржитися на вас за те, що ви не робите покажчиків "const"

Можливо, це тому, що ви використовуєте Visual Studio або використовуєте GCC без -Wall.

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

Тут ви прямо заявляєте, що вам потрібні два масиви символів з рядком, а не два вказівники на символи.

Увага: об’єднання рядків - це функція компілятора / оптимізатора, а не аспект мови. Оскільки такі різні компілятори в різних середовищах вироблятимуть різну поведінку залежно від таких речей, як рівень оптимізації, прапори компілятора та чи знаходяться рядки в різних одиницях компіляції.


1
gcc (Debian 4.4.5-8) 4.4.5не скаржиться (попереджає), хоча використовує -Wall -Wextra -pedantic.
alk

1
Так, станом на V4.8.1 gcc за замовчуванням не попереджає про невикористання constдля рядкових літералів. Попередження вмикається опцією -Wwrite-strings. Це, мабуть, не вмикається жодним іншим варіантом (наприклад -Wall, -Wextraабо -pedantic).
sleske

1
Як GCC 4.4.7, так і 4.7.2 дають мені попередження з-стіною або без неї. pastebin.com/1DtYEzUN
kfsone

15

Як казали інші, компілятор помічає, що вони мають однакове значення, і тому вирішує, що вони надаватимуть спільний доступ до даних у остаточному виконуваному файлі. Але це стає приємніше: коли я компілюю наступне зgcc -O

це друкує 4195780 4195783для мене. Тобто p1починається через 3 байти p, тому GCC побачив загальний суфікс def(включаючи \0термінатор) і здійснив аналогічну оптимізацію до тієї, яку ви показали.

(Це відповідь, тому що це занадто довго, щоб бути коментарем.)


3

Рядкові літерали в коді зберігаються в сегменті даних, лише для читання коду. Коли ви записуєте рядовий літерал на зразок "abc", він насправді повертає "const char *", і якби у вас були всі попередження компілятора, це означало б, що ви кастуєте на той момент. Вам не дозволено змінювати ці рядки з тієї причини, на яку ви вказали у цьому питанні.


2

Коли ви створюєте рядковий літерал ("abc"), він зберігається в пам'яті, яка містить рядкові літерали, а потім він використовується повторно, якщо ви посилаєтесь на той самий рядковий літерал, таким чином обидва вказівники вказують на те саме місце, де " abc "рядковий літерал зберігається.

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


2

Це насправді залежить від того, який компілятор ви використовуєте .

У моїй системі з TC ++ 3.5 він друкує два різні значення для двох покажчиків, тобто дві різні адреси .

Ваш компілятор розроблений для того, щоб перевірити наявність будь-якого значення в пам'яті, і залежно від його існування він переприсвоїть або використає одне і те ж посилання на раніше збережене значення, якщо йдеться про те саме значення.

Тому не думайте надто над цим, оскільки це залежить від способу синтаксичного аналізу коду.

ЦЕ ВСЕ...


1

оскільки рядок "abc" сам по собі є адресою в пам'яті. коли ти знову пишеш "abc", він зберігає ту саму адресу


1

Це оптимізація компілятора, але забудьте про оптимізацію для перенесення. Інколи скомпільовані коди є більш читабельними, ніж фактичні коди.


0

ви використовуєте рядковий літерал,

коли компілятор ловить два однакові рядкові літерали,

він дає одне і те ж місце в пам'яті, тому відображає те саме розташування вказівника. /

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