ทำไมฉันถึงได้รับข้อผิดพลาดการแบ่งส่วนเมื่อเขียนไปยังสตริง?
C99 N1256 ฉบับร่าง
มีการใช้ตัวอักษรสตริงของอักขระสองแบบ:
เริ่มต้นchar[]
:
char c[] = "abc";
นี่คือ "มายากลเพิ่มเติม" และอธิบายไว้ที่ 6.7.8 / 14 "การเริ่มต้น":
อาเรย์ของประเภทตัวละครอาจเริ่มต้นได้โดยสตริงตัวอักษรตัวเลือกล้อมรอบด้วยวงเล็บปีกกา อักขระต่อเนื่องของสตริงอักขระตามตัวอักษร (รวมถึงอักขระ null สิ้นสุดหากมีที่ว่างหรือถ้าอาร์เรย์มีขนาดไม่ทราบค่า) เริ่มต้นองค์ประกอบของอาร์เรย์
ดังนั้นนี่เป็นเพียงทางลัดสำหรับ:
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 ไบต์หรือรหัสของค่าศูนย์จะถูกผนวกเข้ากับลำดับอักขระหลายไบต์ที่เป็นผลมาจากสตริงตัวอักษรหรือตัวอักษร ลำดับอักขระมัลติไบต์จะถูกใช้เพื่อเริ่มต้นอาร์เรย์ของระยะเวลาการจัดเก็บแบบคงที่และความยาวเพียงพอที่จะมีลำดับ สำหรับตัวอักษรสตริงตัวอักษรองค์ประกอบอาเรย์มีประเภทถ่านและเริ่มต้นด้วยแต่ละไบต์ของลำดับอักขระหลายไบต์ [... ]
6 มันไม่ได้ระบุว่าอาร์เรย์เหล่านี้แตกต่างกันหรือไม่หากองค์ประกอบของพวกเขามีค่าที่เหมาะสม หากโปรแกรมพยายามปรับเปลี่ยนอาร์เรย์ลักษณะการทำงานจะไม่ได้กำหนด
6.7.8 / 32 "การเริ่มต้น" ให้ตัวอย่างโดยตรง:
ตัวอย่างที่ 8: การประกาศ
char s[] = "abc", t[3] = "abc";
กำหนดวัตถุอาร์เรย์ถ่าน "ธรรมดา" s
และt
องค์ประกอบที่จะเริ่มต้นด้วยตัวอักษรสตริงตัวอักษร
คำประกาศนี้เหมือนกัน
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
เนื้อหาของอาร์เรย์สามารถแก้ไขได้ ในทางกลับกันการประกาศ
char *p = "abc";
กำหนดp
ด้วยประเภท "ตัวชี้ไปที่ถ่าน" และเริ่มต้นให้ชี้ไปที่วัตถุที่มีประเภท "อาร์เรย์ของถ่าน" ที่มีความยาว 4 ซึ่งองค์ประกอบจะเริ่มต้นด้วยตัวอักษรสตริงตัวอักษร หากมีความพยายามที่จะใช้p
ในการปรับเปลี่ยนเนื้อหาของอาร์เรย์พฤติกรรมจะไม่ได้กำหนด
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