Яка різниця між масивом char та char pointer у C?
C99 N1256 тяга
Існує дві різні можливості використання буквених рядків символів:
Ініціалізувати char[]
:
char c[] = "abc";
Це "більше магія", і описано в 6.7.8 / 14 "Ініціалізація":
Масив типів символів може бути ініціалізований літеральним рядком символів, необов'язково укладеним у дужки. Послідовні символи літерального рядка символів (включаючи закінчуючий нульовий символ, якщо є місце або якщо масив невідомого розміру) ініціалізують елементи масиву.
Отже, це лише ярлик для:
char c[] = {'a', 'b', 'c', '\0'};
Як і будь-який інший регулярний масив, c
може бути змінений.
Скрізь: це створює:
Отже, коли ви пишете:
char *c = "abc";
Це схоже на:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
Зверніть увагу на неявне приведення від char[]
до char *
, який завжди законно.
Потім, якщо ви модифікуєте c[0]
, ви також модифікуєте __unnamed
, що є UB.
Це задокументовано в 6.4.5 "Строкові літерали":
5 На етапі 7 перекладу байт або код з нульовим значенням додається до кожної багатобайтової послідовності символів, яка є результатом рядкового літералу або літералу. Потім послідовність багатобайтових символів використовується для ініціалізації масиву статичної тривалості зберігання та довжини, достатньої для утримання послідовності. Для літеральних рядків символів елементи масиву мають тип char та ініціалізуються окремими байтами багатобайтової послідовності символів [...]
6 Не визначено, чи відрізняються ці масиви, якщо їх елементи мають відповідні значення. Якщо програма намагається змінити такий масив, поведінка не визначена.
6.7.8 / 32 "Ініціалізація" дає прямий приклад:
ПРИКЛАД 8: Декларація
char s[] = "abc", t[3] = "abc";
визначає "прості" об'єкти масиву char s
та t
елементи яких ініціалізуються літеральними рядками символів.
Ця декларація ідентична
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Вміст масивів може змінюватися. З іншого боку, декларація
char *p = "abc";
визначає p
з типом "покажчик на char" та ініціалізує його для вказівки на об'єкт типу "масив char" довжиною 4, елементи якого ініціалізовані літеральним рядком символів. Якщо буде здійснена спроба p
змінити вміст масиву, поведінка не визначена.
Реалізація ELF GCC 4.8 x86-64
Програма:
#include <stdio.h>
int main(void) {
char *s = "abc";
printf("%s\n", s);
return 0;
}
Складіть і декомпілюйте:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
Вихід містить:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
Висновок: GCC зберігає char*
його в .rodata
розділі, а не в .text
.
Якщо ми робимо те саме для char[]
:
char s[] = "abc";
ми отримуємо:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
тому він зберігається в стеку (відносно %rbp
).
Однак зауважте, що сценарій посилання за замовчуванням розміщує .rodata
і .text
в тому ж сегменті, який має виконати, але не має дозволу на запис. Це можна спостерігати за:
readelf -l a.out
який містить:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
char p[3] = "hello";
рядок ініціалізатора занадто довгий для розміру оголошеного масиву. Друкарська помилка?