ความหมายของวัตถุที่ทับซ้อนกันใน C คืออะไร?


25

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

struct s {
  int a, b;
};

โดยทั่วไป1 โครงสร้างนี้จะมีขนาด 8 และการจัดตำแหน่ง 4

จะเกิดอะไรขึ้นถ้าเราสร้างstruct sวัตถุสองชิ้น (แม่นยำยิ่งกว่านั้นเราเขียนลงในวัตถุสองหน่วยดังกล่าวที่จัดสรรแล้ว) โดยที่วัตถุที่สองซ้อนทับวัตถุแรก

char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4

// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};

printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);

มีอะไรเกี่ยวกับโปรแกรมนี้ที่ไม่ได้กำหนดพฤติกรรมหรือไม่ ถ้าเป็นเช่นนั้นจะไม่ได้กำหนดที่ไหน? หากไม่ใช่ UB รับประกันว่าจะพิมพ์ดังต่อไปนี้เสมอ:

o2.a=3
o2.b=4
o1.a=1
o1.b=3

โดยเฉพาะอย่างยิ่งฉันต้องการทราบว่าเกิดอะไรขึ้นกับวัตถุที่ชี้ไปo1เมื่อo2เขียนทับกัน มันยังคงได้รับอนุญาตให้เข้าถึงส่วนที่ยังไม่ได้ล็อค ( o1->a) หรือไม่? การเข้าถึงส่วนที่ถูกบดบังo1->bนั้นเหมือนกับการเข้าถึงo2->aหรือไม่?

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

จะมีอะไรเปลี่ยนแปลงไหมถ้าการเขียนครั้งที่สองเป็นประเภทที่แตกต่างกัน ถ้าสมาชิกพูดintและshortมากกว่าสองคนint?

นี่คือรูปสลักถ้าคุณต้องการเล่นกับมันที่นั่น


1คำตอบนี้นำไปใช้กับแพลตฟอร์มที่ไม่มีกรณีเช่น: บางคนอาจมีขนาด 4 และการจัดตำแหน่ง 2 บนแพลตฟอร์มที่ขนาดและการจัดตำแหน่งเหมือนกันคำถามนี้จะไม่นำมาใช้ตั้งแต่จัดเรียงวัตถุที่ทับซ้อนกัน เป็นไปไม่ได้ แต่ฉันไม่แน่ใจว่ามีแพลตฟอร์มใด ๆ เช่นนั้น


2
ฉันค่อนข้างแน่ใจว่าเป็น UB แต่ฉันจะให้นักกฎหมายเป็นผู้จัดเตรียมบทและข้อ
Barmar

ฉันคิดว่าคอมไพเลอร์ C ในระบบเวกเตอร์ Cray เก่าบังคับให้จัดตำแหน่งและขนาดเหมือนกันด้วยโมเดล ILP64 และบังคับให้จัดตำแหน่ง 64 บิต (ที่อยู่เป็นคำ 64- บิต - ไม่มีที่อยู่ไบต์) แน่นอนว่าสิ่งนี้ทำให้เกิดปัญหาอื่น ๆ อีกมากมาย ....
John D McCalpin

คำตอบ:


15

โดยทั่วไปนี่คือพื้นที่สีเทาทั้งหมดในมาตรฐาน กฎนามแฝงที่เข้มงวดระบุกรณีพื้นฐานและปล่อยให้ผู้อ่าน (และผู้จำหน่ายคอมไพเลอร์) กรอกรายละเอียด

มีความพยายามในการเขียนกฎที่ดีกว่า แต่จนถึงตอนนี้พวกเขาไม่ได้มีข้อความเชิงบรรทัดฐานและฉันไม่แน่ใจว่าสถานะของสิ่งนี้สำหรับ C2x

ตามที่ระบุไว้ในคำตอบของฉันคำถามก่อนหน้านี้ของคุณการตีความที่พบมากที่สุดคือp->qวิธีการ(*p).qและชนิดที่มีประสิทธิภาพมีผลกับทุกแม้ว่าเราแล้วไปใช้ *p.q

ภายใต้การตีความนี้printf("o1.a=%d\n", o1->a);จะทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดเนื่องจากประเภทที่มีประสิทธิภาพของสถานที่*o1นั้นไม่ได้s(เนื่องจากส่วนหนึ่งถูกเขียนทับ)

เหตุผลสำหรับการตีความนี้สามารถเห็นได้ในฟังก์ชั่นเช่น:

void f(s* s1, s* s2)
{
    s2->a = 5;
    s1->b = 6;
    printf("%d\n", s2->a);
}

ด้วยการตีความนี้บรรทัดสุดท้ายสามารถปรับให้เหมาะสมputs("5");แต่หากไม่มีคอมไพเลอร์จะต้องพิจารณาว่าการเรียกใช้ฟังก์ชันอาจเป็นไปได้f(o1, o2);ดังนั้นจึงสูญเสียผลประโยชน์ทั้งหมดที่ระบุไว้โดยกฎนามแฝงที่เข้มงวด

อาร์กิวเมนต์ที่คล้ายกันนี้นำไปใช้กับประเภทโครงสร้างที่ไม่เกี่ยวข้องสองชนิดซึ่งทั้งคู่มีintสมาชิกที่ออฟเซ็ตต่างกัน


1
ด้วยเมื่อf(s* s1, s* s2)ไม่มีrestrictคอมไพเลอร์ไม่สามารถคาดเดาs1และs2เป็นพอยน์เตอร์ที่แตกต่างกัน ฉันคิดว่าถ้าไม่มีrestrictก็ไม่สามารถคิดได้ว่าพวกเขาจะไม่ทับซ้อนกันบางส่วน IAC ฉันไม่เห็นว่าความกังวลของ OP นั้นเป็นไปตามตัวอย่างที่f()ดี ขอให้โชคดีที่ไม่นิ่ง UV สำหรับครึ่งปีแรก
chux - Reinstate Monica

@ chux-ReinstateMonica โดยไม่มีข้อ จำกัดs1 == s2จะได้รับอนุญาต แต่ไม่ทับซ้อนบางส่วน (การเพิ่มประสิทธิภาพในตัวอย่างรหัสของฉันยังคงสามารถทำได้หากs1 == s2)
MM

@ chux-ReinstateMonica คุณสามารถพิจารณาปัญหาเดียวกันด้วยintแทนที่จะเป็น structs (และระบบด้วย_Alignof(int) < sizeof(int))
MM

3
สถานะของคำถามประเภทนี้เกี่ยวกับประเภทที่มีประสิทธิภาพสำหรับ C2x ค่อนข้างเปิดกว้างและยังคงมีการอภิปรายในกลุ่มศึกษา จะมี แต่ความระมัดระวังกับอ้างเท่าเทียมและp->q (*p).qนี่อาจเป็นจริงสำหรับการตีความประเภทตามที่คุณระบุ แต่ไม่เป็นความจริงจากมุมมองการปฏิบัติงาน มันเป็นสิ่งสำคัญสำหรับการเข้าถึงพร้อมกันกับโครงสร้างเดียวกันที่การเข้าถึงของสมาชิกไม่ได้หมายความถึงการเข้าถึงของสมาชิกคนอื่น ๆ
Jens Gustedt

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