__attribute __ ของ gcc เป็น (บรรจุแล้ว) / #pragma ไม่ปลอดภัยหรือไม่


164

ใน C คอมไพเลอร์จะวางโครงสมาชิกของโครงสร้างตามลำดับที่ประกาศโดยมีการเติมไบต์ที่เป็นไปได้แทรกระหว่างสมาชิกหรือหลังสมาชิกรายสุดท้ายเพื่อให้แน่ใจว่าสมาชิกแต่ละคนได้รับการจัดตำแหน่งอย่างถูกต้อง

gcc จัดเตรียมส่วนขยายของภาษา__attribute__((packed))ซึ่งบอกคอมไพเลอร์ว่าไม่ให้แทรกการซ้อนทำให้สมาชิกโครงสร้างไม่ถูกต้อง ตัวอย่างเช่นถ้าระบบต้องการintวัตถุทั้งหมดที่มีการจัดตำแหน่งแบบ 4 ไบต์__attribute__((packed))สามารถทำให้intสมาชิก struct ได้รับการจัดสรรที่ออฟเซ็ตแปลก ๆ

การอ้างอิงเอกสาร gcc:

แอตทริบิวต์ `ที่บรรจุ 'ระบุว่าเขตข้อมูลตัวแปรหรือโครงสร้างควรมีการจัดตำแหน่งที่เล็กที่สุดที่เป็นไปได้ - หนึ่งไบต์สำหรับตัวแปรและหนึ่งบิตสำหรับเขตข้อมูลเว้นแต่ว่าคุณจะระบุค่าที่มากขึ้นด้วยแอตทริบิวต์` จัดชิด'

เห็นได้ชัดว่าการใช้ส่วนขยายนี้อาจส่งผลให้ข้อกำหนดด้านข้อมูลมีขนาดเล็กลง แต่มีโค้ดที่ช้ากว่าเนื่องจากคอมไพเลอร์จะต้องสร้างรหัสเพื่อเข้าถึงสมาชิกไบต์ที่ไม่ถูกต้องในแต่ละครั้ง

แต่มีกรณีใดบ้างที่สิ่งนี้ไม่ปลอดภัยหรือไม่? คอมไพเลอร์สร้างรหัสที่ถูกต้อง (แม้ว่าช้ากว่า) เสมอเพื่อเข้าถึงสมาชิกที่มีโครงสร้างไม่ตรงตามที่กำหนดหรือไม่? เป็นไปได้ไหมที่จะทำเช่นนั้นในทุกกรณี?


1
รายงานข้อผิดพลาด gcc ถูกทำเครื่องหมายเป็น FIXED ด้วยการเพิ่มคำเตือนในการกำหนดตัวชี้ (และตัวเลือกในการปิดการใช้งานคำเตือน) รายละเอียดในคำตอบของฉัน
Keith Thompson

คำตอบ:


148

ใช่__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;
      |               ^~~~~~~~~

1
อาจไม่ตรงแนวและจะสร้าง ... อะไร
Almo

5
องค์ประกอบ struct ที่ไม่ตรงแนวบน ARM ทำสิ่งแปลก ๆ : การเข้าถึงบางอย่างทำให้เกิดความผิดพลาดส่วนอื่นทำให้ข้อมูลที่ดึงมาได้รับการจัดเรียงใหม่โดยไม่โต้ตอบหรือรวมข้อมูลที่ไม่คาดคิดไว้ด้วยกัน
wallyk

8
ดูเหมือนว่าการบรรจุตัวเองนั้นปลอดภัย แต่วิธีการที่สมาชิกที่นำมาใช้นั้นไม่ปลอดภัย ซีพียูที่ใช้ ARM รุ่นเก่านั้นไม่รองรับการเข้าถึงหน่วยความจำที่ไม่ได้ลงทะเบียน แต่รุ่นที่ใหม่กว่าทำได้ แต่ฉันรู้ว่า Symbian OS ยังคงไม่อนุญาตการเข้าถึงที่ไม่ได้จัดแนวเมื่อทำงานกับเวอร์ชันใหม่เหล่านี้ (ปิดการสนับสนุน)
James

14
อีกวิธีในการแก้ไขภายใน gcc คือการใช้ระบบ type: ต้องการให้พอยน์เตอร์ไปยังสมาชิกของ struct ที่บีบอัดสามารถกำหนดให้กับพอยน์เตอร์ที่ทำเครื่องหมายด้วยตนเองว่าถูกบีบอัด (เช่นอาจไม่ได้จัดแนว) แต่จริงๆแล้ว: โครงสร้างที่เต็มไปเพียงแค่พูดว่าไม่
caf

9
@Flavius: จุดประสงค์หลักของฉันคือการได้รับข้อมูลจากที่นั่น ดูเพิ่มเติมที่meta.stackexchange.com/questions/17463/…
Keith Thompson

62

ตามที่กล่าวไว้ข้างต้นอย่านำตัวชี้ไปยังสมาชิกของ struct ที่อัดแน่น นี่เป็นเพียงการเล่นด้วยไฟ เมื่อคุณพูด__attribute__((__packed__))หรือ#pragma pack(1)สิ่งที่คุณพูดจริงๆคือ "เฮ้ gcc ฉันรู้ว่าฉันกำลังทำอะไร เมื่อปรากฎว่าคุณทำไม่ได้คุณไม่สามารถตำหนิคอมไพเลอร์ได้อย่างถูกต้อง

บางทีเราอาจตำหนิคอมไพเลอร์เพราะมันเป็นความพึงพอใจ ในขณะที่ GCC จะมี -Wcast-alignตัวเลือกที่จะไม่เปิดใช้งานโดยค่าเริ่มต้นหรือด้วยหรือ-Wall -Wextraเห็นได้ชัดว่านี่เป็นเพราะนักพัฒนา gcc เห็นว่าโค้ดประเภทนี้เป็น " สิ่งที่น่ารังเกียจ " ที่ไร้ค่าในการจัดการกับความไม่เหมาะสมในการพูดถึง - ดูถูกเหยียดหยาม แต่มันก็ไม่ได้ช่วยอะไรเมื่อโปรแกรมเมอร์มือใหม่ไม่มีประสบการณ์

พิจารณาสิ่งต่อไปนี้:

struct  __attribute__((__packed__)) my_struct {
    char c;
    int i;
};

struct my_struct a = {'a', 123};
struct my_struct *b = &a;
int c = a.i;
int d = b->i;
int *e __attribute__((aligned(1))) = &a.i;
int *f = &a.i;

ที่นี่ประเภทของaเป็นโครงสร้างที่อัดแน่น (ตามที่กำหนดไว้ข้างต้น) ในทำนองเดียวกันbเป็นตัวชี้ไปยังโครงสร้างที่บรรจุ ชนิดของนิพจน์a.iคือ (โดยทั่วไป) int -l-valueพร้อมการจัดตำแหน่ง 1 ไบต์ cและdเป็นทั้งints ปกติ เมื่ออ่านa.iคอมไพเลอร์จะสร้างรหัสสำหรับการเข้าถึงที่ไม่ได้ลงทะเบียน เมื่อคุณอ่านb->i, b's ประเภทยังคงรู้ว่ามันเต็มไปจึงไม่มีปัญหาอย่างใดอย่างหนึ่งของพวกเขา eเป็นตัวชี้ไปยัง int หนึ่งไบต์เรียงดังนั้นคอมไพเลอร์รู้วิธีการตรวจสอบว่าถูกต้องเช่นกัน แต่เมื่อคุณทำการกำหนดค่าf = &a.iคุณกำลังจัดเก็บค่าของตัวชี้ int ที่ไม่ได้จัดแนวในตัวแปรตัวชี้ int ที่จัดตำแหน่ง - นั่นคือสิ่งที่คุณทำผิด และฉันเห็นด้วย gcc ควรเปิดใช้งานคำเตือนนี้โดยค่าเริ่มต้น (ไม่ได้อยู่ใน-Wallหรือ-Wextra)


6
+1 สำหรับการอธิบายวิธีใช้พอยน์เตอร์ที่มีโครงสร้างแบบไม่จัดแนว!
Soumya

@ Soumya ขอบคุณสำหรับคะแนน! :) โปรดทราบว่า__attribute__((aligned(1)))เป็นส่วนขยาย gcc และไม่ใช่แบบพกพา สำหรับความรู้ของฉันวิธีเดียวที่พกพาได้อย่างแท้จริงในการเข้าถึงแบบไม่ จำกัด ใน C (พร้อมชุดคอมไพเลอร์ / ฮาร์ดแวร์) คือการคัดลอกหน่วยความจำแบบไบต์ (memcpy หรือคล้ายกัน) ฮาร์ดแวร์บางตัวไม่มีคำแนะนำสำหรับการเข้าถึงที่ไม่ได้จัดแนว ความเชี่ยวชาญของฉันอยู่ที่แขนและ x86 ซึ่งสามารถทำได้ทั้งสองอย่าง ดังนั้นหากคุณจำเป็นต้องทำสิ่งนี้ด้วยประสิทธิภาพสูงคุณจะต้องดมกลิ่นฮาร์ดแวร์และใช้เทคนิคเฉพาะส่วน
Daniel Santos

4
@Soumya น่าเศร้า__attribute__((aligned(x)))ตอนนี้ดูเหมือนจะถูกละเว้นเมื่อใช้สำหรับตัวชี้ :( ฉันยังไม่มีรายละเอียดครบถ้วน แต่การใช้__builtin_assume_aligned(ptr, align)ดูเหมือนว่าจะได้รับ gcc เพื่อสร้างรหัสที่ถูกต้องเมื่อฉันมีคำตอบที่กระชับมากขึ้น (และหวังว่าจะเป็นรายงานข้อผิดพลาด) ฉันจะอัปเดตคำตอบของฉัน
Daniel Santos

@DanielSantos: คอมไพเลอร์คุณภาพที่ฉันใช้ (Keil) รู้จักตัวระบุ "แบบเต็ม" สำหรับพอยน์เตอร์ หากโครงสร้างถูกประกาศว่า "อัดแน่น" การรับแอดเดรสของuint32_tสมาชิกจะทำให้ a uint32_t packed*; พยายามที่จะอ่านจากตัวชี้เช่นบน Cortex-M0 จะ IIRC เรียกรูทีนย่อยซึ่งจะใช้เวลา ~ 7x ตราบเท่าที่การอ่านปกติถ้าตัวชี้ไม่ได้อยู่ในแนวเดียวกันหรือ ~ 3x ตราบเท่าที่อยู่ในแนวเดียวกัน แต่จะทำงานตามที่คาดการณ์ไว้ [โค้ดในบรรทัดจะใช้เวลา 5 เท่าไม่ว่าจะอยู่ในแนวเดียวกันหรือไม่สอดคล้องกัน]
supercat


49

มันปลอดภัยอย่างสมบูรณ์แบบตราบใดที่คุณเข้าถึงค่าผ่านทาง struct ผ่านทาง.(dot) หรือ->เครื่องหมาย

สิ่งที่ไม่ปลอดภัยคือการใช้ตัวชี้ของข้อมูลที่ไม่ได้จัดแนวจากนั้นเข้าถึงข้อมูลโดยไม่คำนึงถึงสิ่งนั้น

นอกจากนี้แม้ว่าแต่ละไอเท็มใน struct จะทราบว่าไม่มีการจัดแนวมันเป็นที่ทราบกันว่าไม่มีการจัดแนวในทางใดทางหนึ่งดังนั้นการวางคอมโพสิตโดยรวมต้องถูกจัดตำแหน่งตามที่คอมไพเลอร์คาดหวังหรือจะมีปัญหา ในอนาคตหากมีการคิดค้นวิธีการใหม่เพื่อเพิ่มประสิทธิภาพการเข้าถึงที่ไม่ได้จัดแนว)


อืมฉันสงสัยว่าจะเกิดอะไรขึ้นถ้าคุณใส่โครงสร้างที่บรรจุหนึ่งเข้าไปในโครงสร้างที่บรรจุอื่นซึ่งการจัดตำแหน่งจะแตกต่างกันอย่างไร คำถามที่น่าสนใจ แต่ไม่ควรเปลี่ยนคำตอบ
ams

GCC จะไม่จัดแนวโครงสร้างเสมอ ตัวอย่างเช่น: struct foo {int x; ถ่านค; } __attribute __ ((บรรจุ)); struct บาร์ {ถ่าน c; struct foo f; }; ฉันพบว่าแถบ :: f :: x จะไม่สอดคล้องกันอย่างน้อยก็ใน MIPS รสชาติบางอย่าง
Anton

3
@antonm: ใช่โครงสร้างภายใน struct ที่รวบรวมไว้อาจไม่ได้จัดแนว แต่อีกครั้งคอมไพเลอร์รู้ว่าการจัดแนวของแต่ละฟิลด์คืออะไรและปลอดภัยอย่างสมบูรณ์ตราบใดที่คุณไม่พยายามใช้พอยน์เตอร์เข้าในโครงสร้าง คุณควรนึกภาพ struct ภายใน struct ว่าเป็นชุดข้อมูลเดียวที่มีชื่อพิเศษเพื่อให้สามารถอ่านได้
am

6

การใช้คุณลักษณะนี้ไม่ปลอดภัยอย่างแน่นอน

สิ่งหนึ่งที่แตกเป็นพิเศษคือความสามารถของ a unionซึ่งประกอบด้วย struct สองตัวขึ้นไปเพื่อเขียนสมาชิกหนึ่งคนและอ่านอีกอันหนึ่งถ้า structs มีลำดับเริ่มต้นทั่วไปของสมาชิก ส่วนที่ 6.5.2.3 ของสถานะมาตรฐาน C11 :

6มีการรับประกันพิเศษหนึ่งข้อเพื่อให้การใช้สหภาพง่ายขึ้น: หากสหภาพมีโครงสร้างหลายอย่างที่มีลำดับเริ่มต้นร่วมกัน (ดูด้านล่าง) และหากวัตถุสหภาพมีหนึ่งในโครงสร้างเหล่านี้จะได้รับอนุญาตให้ตรวจสอบ ส่วนเริ่มต้นทั่วไปของพวกเขาใด ๆ ที่ใดก็ตามที่การประกาศของสหภาพที่สมบูรณ์ประเภทสามารถมองเห็นได้ โครงสร้าง Tw o แบ่งปันลำดับเริ่มต้นทั่วไปหากสมาชิกที่เกี่ยวข้องมีประเภทที่เข้ากันได้ (และสำหรับบิตฟิลด์ความกว้างเท่ากัน) สำหรับลำดับของสมาชิกเริ่มต้นหนึ่งรายขึ้นไป

...

9ตัวอย่าง 3 ต่อไปนี้เป็นส่วนที่ถูกต้อง:

union {
    struct {
        int    alltypes;
    }n;
    struct {
        int    type;
        int    intnode;
    } ni;
    struct {
        int    type;
        double doublenode;
    } nf;
}u;
u.nf.type = 1;
u.nf.doublenode = 3.14;
/*
...
*/
if (u.n.alltypes == 1)
if (sin(u.nf.doublenode) == 0.0)
/*
...
*/

เมื่อ__attribute__((packed))มีการเปิดตัวมันจะทำลายสิ่งนี้ ตัวอย่างต่อไปนี้ทำงานบน Ubuntu 16.04 x64 โดยใช้ gcc 5.4.0 เมื่อปิดการใช้งานการปรับให้เหมาะสม:

#include <stdio.h>
#include <stdlib.h>

struct s1
{
    short a;
    int b;
} __attribute__((packed));

struct s2
{
    short a;
    int b;
};

union su {
    struct s1 x;
    struct s2 y;
};

int main()
{
    union su s;
    s.x.a = 0x1234;
    s.x.b = 0x56789abc;

    printf("sizeof s1 = %zu, sizeof s2 = %zu\n", sizeof(struct s1), sizeof(struct s2));
    printf("s.y.a=%hx, s.y.b=%x\n", s.y.a, s.y.b);
    return 0;
}

เอาท์พุท:

sizeof s1 = 6, sizeof s2 = 8
s.y.a=1234, s.y.b=5678

แม้ว่าstruct s1และstruct s2มี "ลำดับเริ่มต้นทั่วไป" การบรรจุที่ใช้กับวิธีการเดิมที่สมาชิกที่เกี่ยวข้องไม่ได้อยู่ที่ไบต์เดียวกันชดเชย ผลลัพธ์คือค่าที่เขียนถึงสมาชิกx.bไม่เหมือนกับค่าที่อ่านจากสมาชิกy.bแม้ว่ามาตรฐานบอกว่าพวกเขาควรจะเหมือนกัน


หนึ่งอาจโต้แย้งว่าถ้าคุณแพ็คหนึ่งของโครงสร้างและอื่น ๆ แล้วคุณจะไม่คาดหวังว่าพวกเขาจะมีรูปแบบที่สอดคล้องกัน แต่ใช่นี่เป็นข้อกำหนดมาตรฐานอื่นที่สามารถละเมิด
Keith Thompson

1

(ต่อไปนี้เป็นตัวอย่างที่ประดิษฐ์ขึ้นมาเพื่อแสดงให้เห็น) การใช้งานที่สำคัญของโครงสร้างแบบบีบอัดคือการที่คุณมีกระแสข้อมูล (พูด 256 ไบต์) ที่คุณต้องการให้ความหมาย ถ้าฉันใช้ตัวอย่างเล็กลงสมมติว่าฉันมีโปรแกรมที่ทำงานบน Arduino ซึ่งส่งผ่านแพ็คเก็ตแบบอนุกรมขนาด 16 ไบต์ซึ่งมีความหมายดังนี้

0: message type (1 byte)
1: target address, MSB
2: target address, LSB
3: data (chars)
...
F: checksum (1 byte)

จากนั้นฉันก็สามารถประกาศสิ่งที่ชอบ

typedef struct {
  uint8_t msgType;
  uint16_t targetAddr; // may have to bswap
  uint8_t data[12];
  uint8_t checksum;
} __attribute__((packed)) myStruct;

แล้วฉันสามารถอ้างถึง targetAddr ไบต์ผ่าน aStruct.targetAddr แทนที่จะเล่นซอกับเลขคณิตตัวชี้

ขณะนี้มีสิ่งจัดเรียงเกิดขึ้นการใช้ตัวชี้ void * ในหน่วยความจำไปยังข้อมูลที่ได้รับและการส่งไปยัง myStruct * จะไม่ทำงานจนกว่าคอมไพเลอร์จะถือว่า struct เป็นที่บรรจุ (นั่นคือมันเก็บข้อมูลตามลำดับที่ระบุ ไบต์สำหรับตัวอย่างนี้) มีบทลงโทษด้านประสิทธิภาพสำหรับการอ่านที่ไม่ได้จัดแนวดังนั้นการใช้ struct แบบบีบอัดสำหรับข้อมูลที่โปรแกรมของคุณกำลังทำงานอยู่นั้นไม่จำเป็นต้องเป็นความคิดที่ดี แต่เมื่อโปรแกรมของคุณมาพร้อมกับรายการไบต์โครงสร้างแบบบีบอัดทำให้ง่ายต่อการเขียนโปรแกรมที่เข้าถึงเนื้อหา

มิฉะนั้นคุณจะจบลงด้วยการใช้ C ++ และเขียนคลาสด้วยวิธีการเข้าถึงและสิ่งต่าง ๆ ที่ใช้ตัวชี้ทางคณิตศาสตร์อยู่เบื้องหลัง กล่าวโดยย่อคือโครงสร้างที่รวบรวมไว้สำหรับการจัดการอย่างมีประสิทธิภาพกับข้อมูลที่รวบรวมและข้อมูลที่บรรจุอาจเป็นสิ่งที่โปรแกรมของคุณมอบให้เพื่อทำงานกับ ส่วนใหญ่โค้ดของคุณควรอ่านค่าจากโครงสร้างทำงานกับมันและเขียนมันกลับมาเมื่อทำเสร็จแล้ว สิ่งอื่นควรทำนอกโครงสร้างที่บรรจุไว้ ส่วนหนึ่งของปัญหาคือสิ่งที่อยู่ในระดับต่ำที่ C พยายามซ่อนตัวจากโปรแกรมเมอร์และการกระโดดแบบห่วงนั้นเป็นสิ่งจำเป็นหากสิ่งเหล่านี้มีความสำคัญต่อโปรแกรมเมอร์จริงๆ (คุณต้องการโครงสร้าง 'data layout' ที่แตกต่างกันในภาษาเพื่อให้คุณสามารถพูดว่า 'สิ่งนี้มีความยาว 48 ไบต์, foo อ้างถึงข้อมูล 13 ไบต์ในและควรแปลความหมาย' และโครงสร้างข้อมูลแยกต่างหาก


สิ่งนี้จะไม่ตอบคำถาม คุณให้เหตุผลว่าโครงสร้างการบรรจุนั้นสะดวก (ซึ่งก็คือ) แต่คุณไม่ได้ตอบคำถามว่าปลอดภัยหรือไม่ นอกจากนี้คุณยืนยันว่าการลงโทษสำหรับการอ่านที่ไม่ได้จัดแนวนั้น มันเป็นเรื่องจริงสำหรับ x86 แต่ไม่ใช่สำหรับทุกระบบตามที่ฉันแสดงในคำตอบ
Keith Thompson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.