Tại sao tôi gặp lỗi phân đoạn khi ghi vào chuỗi?
Dự thảo C99 N1256
Có hai cách sử dụng khác nhau của chuỗi ký tự:
Khởi tạo char[]
:
char c[] = "abc";
Đây là "nhiều phép thuật hơn" và được mô tả tại 6.7.8 / 14 "Khởi tạo":
Một mảng các kiểu ký tự có thể được khởi tạo bởi một chuỗi ký tự bằng chữ, được tùy ý đặt trong dấu ngoặc nhọn. Các ký tự liên tiếp của chuỗi ký tự bằng chữ (bao gồm ký tự null kết thúc nếu có chỗ hoặc nếu mảng có kích thước không xác định) khởi tạo các phần tử của mảng.
Vì vậy, đây chỉ là một phím tắt cho:
char c[] = {'a', 'b', 'c', '\0'};
Giống như bất kỳ mảng thông thường khác, c
có thể được sửa đổi.
Ở mọi nơi khác: nó tạo ra một:
Vì vậy, khi bạn viết:
char *c = "abc";
Điều này tương tự như:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
Lưu ý các diễn viên ngầm từ char[]
đến char *
, luôn luôn hợp pháp.
Sau đó, nếu bạn sửa đổi c[0]
, bạn cũng sửa đổi __unnamed
, đó là UB.
Điều này được ghi lại ở 6.4.5 "Chuỗi ký tự":
5 Trong giai đoạn dịch 7, một byte hoặc mã có giá trị 0 được thêm vào từng chuỗi ký tự đa dòng kết quả từ một chuỗi ký tự hoặc bằng chữ. Chuỗi ký tự đa bào sau đó được sử dụng để khởi tạo một mảng thời lượng và độ dài lưu trữ tĩnh vừa đủ để chứa chuỗi. Đối với các ký tự chuỗi ký tự, các thành phần mảng có kiểu char và được khởi tạo với các byte riêng lẻ của chuỗi ký tự đa dòng [...]
6 Không xác định được liệu các mảng này có khác biệt hay không với điều kiện các phần tử của chúng có các giá trị phù hợp. Nếu chương trình cố gắng sửa đổi một mảng như vậy, hành vi không được xác định.
6.7.8 / 32 "Khởi tạo" đưa ra một ví dụ trực tiếp:
VÍ DỤ 8: Tuyên bố
char s[] = "abc", t[3] = "abc";
định nghĩa các đối tượng mảng char "đơn giản" s
và t
có các phần tử được khởi tạo bằng chuỗi ký tự.
Tuyên bố này là giống hệt với
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Nội dung của các mảng có thể sửa đổi. Mặt khác, tuyên bố
char *p = "abc";
định nghĩa p
với kiểu "con trỏ tới char" và khởi tạo nó để trỏ đến một đối tượng có kiểu "mảng char" có độ dài 4 có các phần tử được khởi tạo với một chuỗi ký tự bằng chữ. Nếu một nỗ lực được thực hiện để sử dụng p
để sửa đổi nội dung của mảng, hành vi không được xác định.
Triển khai ELF GCC 4.8 x86-64
Chương trình:
#include <stdio.h>
int main(void) {
char *s = "abc";
printf("%s\n", s);
return 0;
}
Biên dịch và dịch ngược:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
Đầu ra chứa:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
Kết luận: GCC lưu trữ char*
nó trong .rodata
phần, không phải trong .text
.
Nếu chúng ta làm tương tự cho char[]
:
char s[] = "abc";
chúng tôi đạt được:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
vì vậy nó được lưu trữ trong ngăn xếp (liên quan đến %rbp
).
Tuy nhiên, lưu ý rằng tập lệnh liên kết mặc định đặt .rodata
và .text
trong cùng một phân đoạn, đã thực thi nhưng không có quyền ghi. Điều này có thể được quan sát với:
readelf -l a.out
trong đó có:
Section to Segment mapping:
Segment Sections...
02 .text .rodata