ใช่__attribute__((packed))
อาจไม่ปลอดภัยในบางระบบ อาการอาจไม่แสดงบน x86 ซึ่งทำให้เกิดปัญหาร้ายกาจยิ่งขึ้น การทดสอบระบบ x86 จะไม่เปิดเผยปัญหา (บน x86 มีการจัดการการเข้าถึงที่ไม่ตรงแนวในฮาร์ดแวร์หากคุณตรวจสอบint*
ตัวชี้ที่ชี้ไปยังที่อยู่แปลก ๆ มันจะช้ากว่าการจัดตำแหน่งอย่างถูกต้องเล็กน้อย แต่คุณจะได้ผลลัพธ์ที่ถูกต้อง)
ในบางระบบอื่น ๆ เช่น SPARC การพยายามเข้าถึงint
วัตถุที่ไม่ตรงแนวทำให้เกิดข้อผิดพลาดของบัสทำให้ระบบขัดข้อง
นอกจากนี้ยังมีระบบที่การเข้าถึงที่ไม่ตรงแนวโดยไม่สนใจบิตที่อยู่ในลำดับต่ำของที่อยู่ทำให้เกิดการเข้าถึงหน่วยความจำอันไม่ถูกต้อง
พิจารณาโปรแกรมต่อไปนี้:
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %d\n", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
printf("arr[0].x = %d\n", arr[0].x);
printf("arr[1].x = %d\n", arr[1].x);
printf("p0 = %p\n", (void*)p0);
printf("p1 = %p\n", (void*)p1);
printf("*p0 = %d\n", *p0);
printf("*p1 = %d\n", *p1);
return 0;
}
บน x86 Ubuntu ที่มี gcc 4.5.2 จะสร้างเอาต์พุตต่อไปนี้:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
บน SPARC Solaris 9 ที่มี gcc 4.5.1 จะสร้างสิ่งต่อไปนี้:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
gcc packed.c -o packed
ในทั้งสองกรณีโปรแกรมจะรวบรวมกับไม่มีตัวเลือกพิเศษเพียง
(โปรแกรมที่ใช้โครงสร้างเดียวแทนที่จะเป็นอาเรย์ไม่สามารถแสดงปัญหาได้อย่างน่าเชื่อถือเนื่องจากคอมไพเลอร์สามารถจัดสรรโครงสร้างบนที่อยู่แปลก ๆ เพื่อให้x
สมาชิกได้รับการจัดตำแหน่งอย่างเหมาะสมด้วยอาร์เรย์ของstruct foo
วัตถุสองชิ้นอย่างน้อยหนึ่งรายการ จะมีx
สมาชิกที่ไม่ตรงแนว)
(ในกรณีนี้p0
ชี้ไปยังที่อยู่ที่ไม่ถูกต้องเนื่องจากจะชี้ไปยังint
สมาชิกที่บรรจุหลังจากchar
สมาชิกp1
เกิดขึ้นเพื่อจัดตำแหน่งอย่างถูกต้องเนื่องจากชี้ไปที่สมาชิกเดียวกันในองค์ประกอบที่สองของอาร์เรย์ดังนั้นจึงมีchar
วัตถุสองชิ้นอยู่ข้างหน้า - และบน SPARC Solaris อาเรย์arr
จะถูกจัดสรรตามที่อยู่ที่เป็นเลขคู่ แต่ไม่ใช่ผลคูณของ 4)
เมื่ออ้างถึงสมาชิกx
ของstruct foo
ชื่อคอมไพเลอร์รู้ว่าx
อาจไม่ถูกต้องและจะสร้างรหัสเพิ่มเติมเพื่อเข้าถึงอย่างถูกต้อง
เมื่อที่อยู่ของarr[0].x
หรือarr[1].x
ถูกเก็บไว้ในวัตถุตัวชี้ไม่คอมไพเลอร์หรือโปรแกรมที่กำลังทำงานรู้ว่ามันชี้ไปที่int
วัตถุที่ไม่ตรงแนว เพียงแค่สมมติว่ามีการจัดตำแหน่งอย่างถูกต้องส่งผล (ในบางระบบ) ในข้อผิดพลาดของบัสหรือความล้มเหลวอื่น ๆ ที่คล้ายกัน
ฉันเชื่อว่าการแก้ไขนี้ใน gcc จะไม่สามารถทำได้ วิธีแก้ปัญหาทั่วไปนั้นต้องการสำหรับแต่ละครั้งที่พยายามอ้างถึงตัวชี้ไปยังชนิดใด ๆ ที่มีข้อกำหนดการจัดตำแหน่งที่ไม่สำคัญเช่น (a) ที่พิสูจน์ในเวลาคอมไพล์ที่ตัวชี้ไม่ได้ชี้ไปยังสมาชิกที่ไม่ตรงแนวของโครงสร้าง struct หรือ การสร้างรหัสที่มากขึ้นและช้าลงที่สามารถจัดการวัตถุที่ถูกจัดตำแหน่งหรือไม่ตรงแนว
ฉันได้ส่งรายงานข้อผิดพลาด GCC อย่างที่ฉันพูดฉันไม่เชื่อว่ามันใช้งานได้จริงในการแก้ไข แต่เอกสารควรพูดถึงมัน
ปรับปรุง : ตั้งแต่วันที่ 2018-12-20 ข้อผิดพลาดนี้ถูกทำเครื่องหมายเป็นคง แพตช์จะปรากฏใน gcc 9 พร้อมกับการเพิ่ม-Waddress-of-packed-member
ตัวเลือกใหม่โดยเปิดใช้งานตามค่าเริ่มต้น
เมื่อที่อยู่ของสมาชิกที่เป็นสมาชิกของ struct หรือ union ถูกนำมาใช้อาจส่งผลให้ค่าตัวชี้ไม่ตรงแนว แพทช์นี้จะเพิ่ม -Waddress-of-packed-member เพื่อตรวจสอบการจัดตำแหน่งที่การกำหนดตัวชี้และเตือนที่อยู่ที่ไม่ได้ลงนามรวมถึงตัวชี้ที่ไม่ได้จัดแนว
ฉันเพิ่งสร้าง gcc เวอร์ชันนั้นจากแหล่งที่มา สำหรับโปรแกรมด้านบนมันสร้างการวินิจฉัยเหล่านี้:
c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
10 | int *p0 = &arr[0].x;
| ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
11 | int *p1 = &arr[1].x;
| ^~~~~~~~~