พฤติกรรมที่ไม่ได้กำหนดทางเทคนิคของ "แฮ็กโครงสร้าง" หรือไม่


111

สิ่งที่ฉันกำลังถามคือเคล็ดลับ "สมาชิกคนสุดท้ายของโครงสร้างมีความยาวผันแปร" ที่รู้จักกันดี มันจะเป็นดังนี้:

struct T {
    int len;
    char s[1];
};

struct T *p = malloc(sizeof(struct T) + 100);
p->len = 100;
strcpy(p->s, "hello world");

เนื่องจากวิธีการจัดวางโครงสร้างในหน่วยความจำเราจึงสามารถซ้อนโครงสร้างบนบล็อกที่ใหญ่กว่าที่จำเป็นและปฏิบัติต่อสมาชิกตัวสุดท้ายราวกับว่ามันมีขนาดใหญ่กว่าที่1 charระบุ

คำถามคือเทคนิคนี้เป็นพฤติกรรมที่ไม่ได้กำหนดทางเทคนิคหรือไม่? . ฉันคาดหวังว่าจะเป็นเช่นนั้น แต่ก็อยากรู้ว่ามาตรฐานพูดถึงเรื่องนี้อย่างไร

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


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

2
หากคุณแนะนำคอมไพเลอร์ "ansi c" ที่ไม่รองรับการแฮ็กโครงสร้างโปรแกรมเมอร์ c ส่วนใหญ่ที่ฉันรู้จักจะไม่ยอมรับว่าคอมไพเลอร์ของคุณ "ทำงานถูกต้อง" ไม่อดทนว่าพวกเขาจะยอมรับการอ่านมาตรฐานที่เข้มงวด คณะกรรมการพลาดเพียงหนึ่งในนั้น
dmckee --- อดีตผู้ดูแลลูกแมว

4
@james การแฮ็กทำงานโดยการทำให้วัตถุมีขนาดใหญ่พอสำหรับอาร์เรย์ที่คุณหมายถึงแม้ว่าจะมีการประกาศอาร์เรย์ขั้นต่ำก็ตาม ดังนั้นคุณกำลังเข้าถึงหน่วยความจำที่จัดสรรนอกนิยามที่เข้มงวดของโครงสร้าง การเขียนเกินการจัดสรรของคุณเป็นความผิดพลาดที่เข้าใจไม่ได้ แต่แตกต่างจากการเขียนในการจัดสรรของคุณ แต่อยู่นอก "โครงสร้าง"
dmckee --- อดีตผู้ดูแลลูกแมว

2
@ เจมส์: malloc ขนาดใหญ่มีความสำคัญที่นี่ มันทำให้มั่นใจได้ว่ามีหน่วยความจำ --- หน่วยความจำที่มีที่อยู่ตามกฎหมายและและ 'เป็นเจ้าของ' โดยโครงสร้าง (กล่าวคือมันผิดกฎหมายสำหรับหน่วยงานอื่นที่จะใช้มัน) - ผ่านจุดสิ้นสุดที่ระบุของโครงสร้าง โปรดทราบว่านี่หมายความว่าคุณไม่สามารถใช้การแฮ็กโครงสร้างกับตัวแปรอัตโนมัติได้โดยจะต้องจัดสรรแบบไดนามิก
dmckee --- อดีตผู้ดูแลลูกแมว

5
@detly: การจัดสรร / ยกเลิกการจัดสรรสิ่งหนึ่งทำได้ง่ายกว่าการจัดสรร / ยกเลิกการจัดสรรสองสิ่งโดยเฉพาะอย่างยิ่งเนื่องจากสิ่งหลังมีสองวิธีในการล้มเหลวที่คุณต้องจัดการ สิ่งนี้สำคัญสำหรับฉันมากกว่าการประหยัดต้นทุน / ความเร็วเล็กน้อย
jamesdlin

คำตอบ:


52

ตามคำถามที่พบบ่อยของ Cกล่าวว่า:

ไม่ชัดเจนว่าถูกกฎหมายหรือพกพาได้ แต่ค่อนข้างเป็นที่นิยม

และ:

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

เหตุผลเบื้องหลังบิต 'การปฏิบัติตามอย่างเคร่งครัด' อยู่ในข้อมูลจำเพาะส่วนJ.2 พฤติกรรมที่ไม่ได้กำหนดซึ่งรวมอยู่ในรายการพฤติกรรมที่ไม่ได้กำหนด:

  • ตัวห้อยอาร์เรย์อยู่นอกช่วงแม้ว่าวัตถุจะสามารถเข้าถึงได้ด้วยตัวห้อยที่กำหนด (เช่นเดียวกับนิพจน์ lvalue ที่a[1][7]ให้การประกาศint a[4][5]) (6.5.6)

ย่อหน้าที่ 8 ของส่วน6.5.6 ตัวดำเนินการเพิ่มเติมมีการกล่าวถึงว่าการเข้าถึงนอกเหนือขอบเขตอาร์เรย์ที่กำหนดนั้นไม่ได้กำหนดไว้:

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


1
ในรหัสของ OP p->sจะไม่ใช้เป็นอาร์เรย์ มันถูกส่งผ่านไปstrcpyซึ่งในกรณีนี้มันจะสลายตัวเป็นธรรมดาchar *ซึ่งจะชี้ไปที่วัตถุซึ่งสามารถตีความได้ตามกฎหมายว่าchar [100];อยู่ภายในวัตถุที่จัดสรร
R .. GitHub STOP HELPING ICE

3
บางทีวิธีอื่นในการดูสิ่งนี้ก็คือภาษาอาจ จำกัด วิธีที่คุณเข้าถึงตัวแปรอาร์เรย์จริงตามที่อธิบายไว้ใน J.2 แต่ไม่มีวิธีใดที่จะสร้างข้อ จำกัด ดังกล่าวสำหรับวัตถุที่จัดสรรโดยmallocเมื่อคุณแปลงเพียงแค่สิ่งที่ส่งคืนvoid *ไปยังตัวชี้ไปที่ [โครงสร้างที่มี] อาร์เรย์ ยังคงใช้ได้ในการเข้าถึงส่วนใด ๆ ของวัตถุที่จัดสรรโดยใช้ตัวชี้ไปที่char(หรือควรจะเป็นunsigned char)
R .. GitHub STOP HELPING ICE

@ ร. - ฉันเห็นว่า J2 อาจไม่ครอบคลุมสิ่งนี้ได้อย่างไร แต่ก็ไม่ครอบคลุมถึง 6.5.6 ด้วยหรือไม่?
ย้อนกลับ

1
แน่นอนว่าทำได้! ประเภทและข้อมูลขนาดอาจจะฝังตัวอยู่ในทุกตัวชี้และตัวชี้ใด ๆ ทางคณิตศาสตร์ที่ผิดพลาดได้แล้วจะทำกับดัก - ดูเช่นCCured ในระดับที่เป็นปรัชญามากขึ้นไม่สำคัญว่าการนำไปใช้งานจะไม่สามารถจับคุณได้หรือไม่ แต่ก็ยังคงเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ (มี iirc กรณีของพฤติกรรมที่ไม่ได้กำหนดซึ่งจะต้องมีคำพยากรณ์เพื่อให้ปัญหาหยุดชะงักลง - ซึ่งเป็นสาเหตุที่แน่นอน พวกเขาไม่ได้กำหนด)
zwol

4
วัตถุไม่ใช่วัตถุอาร์เรย์ดังนั้น 6.5.6 จึงไม่เกี่ยวข้อง mallocวัตถุคือบล็อกของหน่วยความจำที่จัดสรรโดย ค้นหา "วัตถุ" ในมาตรฐานก่อนที่คุณจะพ่น bs
R .. GitHub STOP HELPING ICE

34

ฉันเชื่อว่าในทางเทคนิคแล้วมันเป็นพฤติกรรมที่ไม่ได้กำหนด มาตรฐาน (เนื้อหา) ไม่ได้กล่าวถึงโดยตรงดังนั้นจึงอยู่ภายใต้ "หรือโดยการละเว้นคำจำกัดความที่ชัดเจนของพฤติกรรม" ประโยค (§4 / 2 ของ C99, §3.16 / 2 ของ C89) ที่ระบุว่าเป็นพฤติกรรมที่ไม่ได้กำหนด

"เนื้อหา" ข้างต้นขึ้นอยู่กับคำจำกัดความของตัวดำเนินการตัวห้อยอาร์เรย์ กล่าวโดยเฉพาะว่า: "นิพจน์ postfix ตามด้วยนิพจน์ในวงเล็บเหลี่ยม [] คือการกำหนดอ็อบเจ็กต์อาร์เรย์แบบห้อยลงมา" (C89, §6.3.2.1 / 2)

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

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


2
ฉันยังสามารถจินตนาการถึงคอมไพเลอร์ซึ่งอาจตัดสินใจได้ว่าถ้าอาร์เรย์มีขนาด 1 arr[x] = y;อาจจะเขียนใหม่เป็นarr[0] = y;; สำหรับอาร์เรย์ขนาด 2 arr[i] = 4;อาจถูกเขียนใหม่ในi ? arr[1] = 4 : arr[0] = 4; ขณะที่ฉันไม่เคยเห็นคอมไพเลอร์ทำการเพิ่มประสิทธิภาพเช่นนี้ในระบบฝังตัวบางระบบอาจมีประสิทธิผลมาก บน PIC18x โดยใช้ชนิดข้อมูล 8 บิตรหัสสำหรับคำสั่งแรกจะเป็นสิบหกไบต์ที่สองสองหรือสี่และสามแปดหรือสิบสอง ไม่ใช่การเพิ่มประสิทธิภาพที่ไม่ดีหากถูกกฎหมาย
supercat

หากมาตรฐานกำหนดการเข้าถึงอาร์เรย์นอกขอบเขตอาร์เรย์เป็นพฤติกรรมที่ไม่ได้กำหนดโครงสร้างแฮ็กก็เช่นกัน อย่างไรก็ตามหากมาตรฐานกำหนดการเข้าถึงอาร์เรย์เป็นน้ำตาลเชิงสังเคราะห์สำหรับการคำนวณทางคณิตศาสตร์ของตัวชี้ ( a[2] == a + 2) ก็ไม่ได้ ถ้าฉันถูกต้องมาตรฐาน C ทั้งหมดจะกำหนดการเข้าถึงอาร์เรย์เป็นตัวชี้ทางคณิตศาสตร์

13

ใช่มันเป็นพฤติกรรมที่ไม่ได้กำหนด

รายงานข้อบกพร่องภาษาซี # 051 ให้คำตอบที่ชัดเจนสำหรับคำถามนี้:

ในขณะที่สำนวนทั่วไปไม่สอดคล้องกันอย่างเคร่งครัด

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_051.html

ในเอกสารเหตุผลของ C99 คณะกรรมการ C เพิ่ม:

ความถูกต้องของโครงสร้างนี้เป็นเรื่องที่น่าสงสัยเสมอ ในการตอบสนองต่อรายงานข้อบกพร่องหนึ่งฉบับคณะกรรมการตัดสินว่าเป็นพฤติกรรมที่ไม่ได้กำหนดไว้เนื่องจากรายการ p-> อาร์เรย์มีเพียงรายการเดียวโดยไม่คำนึงว่ามีพื้นที่ว่างหรือไม่


2
+1 สำหรับการค้นหาสิ่งนี้ แต่ฉันยังคงอ้างว่ามันขัดแย้งกัน ตัวชี้สองตัวไปยังวัตถุเดียวกัน (ในกรณีนี้คือไบต์ที่กำหนด) มีค่าเท่ากันและตัวชี้หนึ่งตัวไปยังวัตถุนั้น (ตัวชี้ในอาร์เรย์การแสดงของวัตถุทั้งหมดที่ได้รับโดยmalloc) นั้นถูกต้องในการเพิ่มดังนั้นตัวชี้ที่เหมือนกันจะทำได้อย่างไร ได้รับจากเส้นทางอื่นจะไม่ถูกต้องในการเพิ่ม? แม้ว่าพวกเขาต้องการอ้างว่าเป็น UB แต่ก็ไม่มีความหมายเลยเพราะไม่มีวิธีการคำนวณสำหรับการใช้งานเพื่อแยกความแตกต่างระหว่างการใช้งานที่กำหนดไว้อย่างดีและการใช้งานที่ไม่ได้กำหนดไว้
R .. GitHub STOP HELPING ICE

แย่เกินไปที่คอมไพเลอร์ C เริ่มห้ามการประกาศอาร์เรย์ที่มีความยาวเป็นศูนย์ หากไม่ใช่ข้อห้ามนั้นคอมไพเลอร์จำนวนมากจะไม่ต้องจัดการพิเศษใด ๆ เพื่อให้ทำงานได้ตามที่ "ควร" แต่ก็ยังสามารถใช้โค้ดกรณีพิเศษสำหรับอาร์เรย์องค์ประกอบเดียวได้ (เช่นหาก*fooมี อาร์เรย์องค์ประกอบเดียวbozนิพจน์foo->boz[biz()*391]=9;สามารถทำให้ง่ายขึ้นเป็นbiz(),foo->boz[0]=9;) น่าเสียดายที่อาร์เรย์องค์ประกอบศูนย์การปฏิเสธของคอมไพเลอร์หมายความว่าโค้ดจำนวนมากใช้อาร์เรย์องค์ประกอบเดียวแทนและจะถูกทำลายโดยการเพิ่มประสิทธิภาพนั้น
supercat

11

วิธีดำเนินการดังกล่าวไม่ได้กำหนดไว้อย่างชัดเจนในมาตรฐาน C ใด ๆ แต่ C99 จะรวม "การแฮ็กโครงสร้าง" เป็นส่วนหนึ่งของภาษา ใน C99 สมาชิกตัวสุดท้ายของโครงสร้างอาจเป็น "สมาชิกอาร์เรย์แบบยืดหยุ่น" ซึ่งประกาศเป็นchar foo[](ไม่ว่าคุณต้องการจะเป็นประเภทใดก็ตามchar)


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

7

ไม่ใช่พฤติกรรมที่ไม่ได้กำหนดไม่ว่าใครก็ตามจะพูดอย่างเป็นทางการหรืออย่างอื่นก็ตามเพราะมันถูกกำหนดโดยมาตรฐาน p->sยกเว้นเมื่อใช้เป็น lvalue, (char *)p + offsetof(struct T, s)ประเมินตัวชี้ไปเหมือนกัน โดยเฉพาะอย่างยิ่งนี่คือcharตัวชี้ที่ถูกต้องภายในอ็อบเจ็กต์ malloc'd และมี 100 (หรือมากกว่านั้นขึ้นอยู่กับการพิจารณาการจัดตำแหน่ง) ที่อยู่ต่อเนื่องตามหลังมันซึ่งใช้ได้เช่นกันเป็นcharอ็อบเจ็กต์ภายในอ็อบเจ็กต์ที่จัดสรร ความจริงที่ว่าพอยน์เตอร์ได้มาจากการใช้->แทนการเพิ่มออฟเซ็ตให้กับตัวชี้ที่ส่งกลับโดยmallocโยนไปอย่างชัดเจนchar *นั้นไม่เกี่ยวข้อง

ในทางเทคนิคp->s[0]เป็นองค์ประกอบเดียวของcharอาร์เรย์ภายในโครงสร้างองค์ประกอบสองสามรายการถัดไป (เช่นp->s[1]ผ่านp->s[3]) มีแนวโน้มที่จะเพิ่มไบต์ภายในโครงสร้างซึ่งอาจเสียหายได้หากคุณทำการกำหนดให้กับโครงสร้างโดยรวม แต่ไม่ใช่ถ้าคุณเพียงแค่เข้าถึงแต่ละรายการ สมาชิกและองค์ประกอบที่เหลือคือพื้นที่เพิ่มเติมในออบเจ็กต์ที่จัดสรรซึ่งคุณมีอิสระที่จะใช้ตามที่คุณต้องการตราบใดที่คุณปฏิบัติตามข้อกำหนดการจัดตำแหน่ง (และcharไม่มีข้อกำหนดในการจัดตำแหน่ง)

หากคุณกังวลว่าความเป็นไปได้ของการทับซ้อนกับไบต์ช่องว่างในโครงสร้างอาจทำให้เกิดปีศาจจมูกได้คุณสามารถหลีกเลี่ยงสิ่งนี้ได้โดยการแทนที่1อิน[1]ด้วยค่าที่ทำให้แน่ใจว่าไม่มีช่องว่างที่ส่วนท้ายของโครงสร้าง วิธีง่ายๆ แต่สิ้นเปลืองในการทำเช่นนี้คือการสร้างโครงสร้างที่มีสมาชิกที่เหมือนกันยกเว้นไม่มีอาร์เรย์ต่อท้ายและใช้s[sizeof struct that_other_struct];สำหรับอาร์เรย์ จากนั้นp->s[i]มีการกำหนดไว้อย่างชัดเจนเป็นองค์ประกอบของอาร์เรย์ที่ struct สำหรับi<sizeof struct that_other_structและเป็นวัตถุถ่านที่อยู่ต่อไปนี้ในตอนท้ายของ struct i>=sizeof struct that_other_structสำหรับ

แก้ไข:จริงๆแล้วในเคล็ดลับข้างต้นเพื่อให้ได้ขนาดที่เหมาะสมคุณอาจต้องใส่ยูเนี่ยนที่มีทุกประเภทก่อนอาร์เรย์เพื่อให้แน่ใจว่าอาร์เรย์นั้นเริ่มต้นด้วยการจัดตำแหน่งสูงสุดแทนที่จะอยู่ตรงกลางของช่องว่างภายในขององค์ประกอบอื่น ๆ . อีกครั้งฉันไม่เชื่อว่าสิ่งเหล่านี้เป็นสิ่งจำเป็น แต่ฉันเสนอสิ่งนี้สำหรับนักกฎหมายภาษาที่หวาดระแวงที่สุด

แก้ไข 2:การทับซ้อนกับไบต์ของช่องว่างภายในไม่ใช่ปัญหาอย่างแน่นอนเนื่องจากส่วนอื่นของมาตรฐาน C กำหนดให้ถ้าโครงสร้างสองอย่างเห็นพ้องกันในการเริ่มต้นขององค์ประกอบต่อมาองค์ประกอบเริ่มต้นทั่วไปสามารถเข้าถึงได้ผ่านทางตัวชี้ไปยังประเภทใดประเภทหนึ่ง ดังนั้นหากมีการประกาศโครงสร้างที่เหมือนกันstruct Tแต่มีอาร์เรย์สุดท้ายที่ใหญ่กว่าองค์ประกอบs[0]จะต้องตรงกับองค์ประกอบs[0]ในstruct Tและการมีอยู่ขององค์ประกอบเพิ่มเติมเหล่านี้ไม่สามารถส่งผลกระทบหรือได้รับผลกระทบจากการเข้าถึงองค์ประกอบทั่วไปของโครงสร้างที่ใหญ่กว่า struct Tโดยใช้ตัวชี้ไปยัง


4
คุณคิดถูกที่ลักษณะของการคำนวณทางคณิตศาสตร์ของตัวชี้นั้นไม่เกี่ยวข้อง แต่คุณคิดผิดเกี่ยวกับการเข้าถึงเกินขนาดที่ประกาศไว้ของอาร์เรย์ ดูN1494 (ร่าง C1x สาธารณะล่าสุด) ส่วน 6.5.6 ย่อหน้า 8 - คุณไม่ได้รับอนุญาตให้ทำการเพิ่มเติมที่นำตัวชี้มากกว่าหนึ่งองค์ประกอบมาเกินขนาดที่ประกาศไว้ของอาร์เรย์และคุณไม่สามารถหักล้างได้แม้ว่า มันเป็นเพียงองค์ประกอบหนึ่งที่ผ่านมา
zwol

1
@ แซ็ค: นั่นเป็นความจริงถ้าวัตถุเป็นอาร์เรย์ ไม่เป็นความจริงหากวัตถุนั้นเป็นวัตถุที่จัดสรรโดยmallocที่เข้าถึงเป็นอาร์เรย์หรือถ้าเป็นโครงสร้างขนาดใหญ่ที่เข้าถึงผ่านตัวชี้ไปยังโครงสร้างที่เล็กกว่าซึ่งองค์ประกอบเป็นส่วนย่อยเริ่มต้นขององค์ประกอบของโครงสร้างที่ใหญ่กว่า กรณี
R .. GitHub STOP HELPING ICE

6
+1 หากmallocไม่จัดสรรช่วงของหน่วยความจำที่สามารถเข้าถึงได้ด้วยเลขคณิตของตัวชี้จะใช้อะไรได้บ้าง? และถ้าp->s[1]ถูกกำหนดโดยมาตรฐานเป็นน้ำตาลวากยสัมพันธ์สำหรับการคำนวณทางคณิตศาสตร์ของตัวชี้คำตอบนี้เป็นเพียงการยืนยันว่าmallocมีประโยชน์ มีอะไรเหลือให้พูดคุย :)
Daniel Earwicker

3
คุณสามารถโต้แย้งว่ามีการกำหนดไว้อย่างชัดเจนมากเท่าที่คุณต้องการ แต่ไม่ได้เปลี่ยนความจริงที่ว่ามันไม่ 1มาตรฐานมีความชัดเจนมากเกี่ยวกับการเข้าถึงเกินขอบเขตของอาร์เรย์และผูกพันของอาร์เรย์นี้อยู่ มันง่ายอย่างนั้น
Lightness Races ในวงโคจร

3
@R .. , ฉันคิดว่าการสันนิษฐานของคุณที่ว่าพอยน์เตอร์สองตัวที่เปรียบเทียบเท่ากับต้องมีพฤติกรรมเหมือนกันนั้นผิด พิจารณาint m[1]; int n[1]; if(m+1 == n) m[1] = 0;สมมติว่าifมีการป้อนสาขา นี่คือ UB (และไม่รับประกันว่าจะเริ่มต้นn) ตาม 6.5.6 p8 (ประโยคสุดท้าย) ตามที่ฉันอ่าน ที่เกี่ยวข้อง: 6.5.9 p6 พร้อมเชิงอรรถ 109. (อ้างอิงถึง C11 n1570) [... ]
mafso

7

ใช่มันเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ในทางเทคนิค

โปรดทราบว่ามีอย่างน้อยสามวิธีในการใช้งาน "struct hack":

(1) การประกาศอาร์เรย์ต่อท้ายด้วยขนาด 0 (วิธีที่ "เป็นที่นิยมมากที่สุดในโค้ดดั้งเดิม) เห็นได้ชัดว่าเป็น UB เนื่องจากการประกาศอาร์เรย์ขนาดศูนย์มักผิดกฎหมายใน C แม้ว่าจะคอมไพล์ แต่ภาษาก็ไม่รับประกันพฤติกรรมของโค้ดที่ละเมิดข้อ จำกัด ใด ๆ

(2) การประกาศอาร์เรย์ที่มีขนาดกฎหมายน้อยที่สุด - 1 (กรณีของคุณ) ในกรณีนี้ความพยายามใด ๆ ที่จะนำตัวชี้ไปp->s[0]และใช้สำหรับการคำนวณทางคณิตศาสตร์ของตัวชี้ที่นอกเหนือไปจากp->s[1]พฤติกรรมที่ไม่ได้กำหนดไว้ p->s[1]ยกตัวอย่างเช่นการดำเนินการแก้จุดบกพร่องที่ได้รับอนุญาตในการผลิตตัวชี้พิเศษมีข้อมูลหลากหลายที่ฝังตัวซึ่งดักประสงค์ทุกครั้งที่คุณพยายามที่จะสร้างเกินตัวชี้

(3) การประกาศอาร์เรย์ที่มีขนาด "ใหญ่มาก"เช่น 10000 เป็นต้น แนวคิดคือขนาดที่ประกาศควรจะใหญ่กว่าสิ่งที่คุณอาจต้องการในการปฏิบัติจริง วิธีนี้ไม่มี UB สำหรับช่วงการเข้าถึงอาร์เรย์ อย่างไรก็ตามในทางปฏิบัติแน่นอนเราจะจัดสรรหน่วยความจำในปริมาณที่น้อยลงเสมอ (เท่าที่จำเป็นจริงๆ) ฉันไม่แน่ใจเกี่ยวกับความถูกต้องตามกฎหมายของสิ่งนี้กล่าวคือฉันสงสัยว่าการจัดสรรหน่วยความจำให้กับวัตถุน้อยกว่าขนาดที่ประกาศของวัตถุนั้นถูกกฎหมายอย่างไร (สมมติว่าเราไม่เคยเข้าถึงสมาชิกที่ "ไม่จัดสรร")


1
ใน (2) s[1]ไม่ใช่พฤติกรรมที่ไม่ได้กำหนด มันเหมือนกับ*(s+1)ซึ่งเหมือนกับ*((char *)p + offsetof(struct T, s) + 1)ซึ่งเป็นตัวชี้ที่ถูกต้องไปยัง a charในวัตถุที่จัดสรร
R .. GitHub STOP HELPING ICE

ในทางกลับกันฉันเกือบแน่ใจว่า (3) เป็นพฤติกรรมที่ไม่ได้กำหนด เมื่อใดก็ตามที่คุณดำเนินการใด ๆ ซึ่งขึ้นอยู่กับโครงสร้างที่อยู่ในแอดเดรสนั้นคอมไพเลอร์มีอิสระในการสร้างรหัสเครื่องซึ่งอ่านจากส่วนใดส่วนหนึ่งของโครงสร้าง อาจไม่มีประโยชน์หรืออาจเป็นคุณลักษณะด้านความปลอดภัยสำหรับการตรวจสอบการจัดสรรอย่างเข้มงวด แต่ไม่มีเหตุผลใดที่การนำไปใช้งานไม่สามารถทำได้
R .. GitHub STOP HELPING ICE

R: หากอาร์เรย์ถูกประกาศว่ามีขนาด (ไม่ใช่แค่foo[]น้ำตาลเชิงไวยากรณ์สำหรับ*foo) การเข้าถึงใด ๆ ที่เกินขนาดที่ประกาศไว้ที่เล็กกว่าและขนาดที่จัดสรรคือ UB ไม่ว่าจะคำนวณเลขคณิตของตัวชี้อย่างไร
zwol

1
@ แซ็คคุณผิดหลายเรื่อง foo[]ในโครงสร้างไม่ใช่น้ำตาลวากยสัมพันธ์สำหรับ*foo; เป็นสมาชิกอาร์เรย์ที่ยืดหยุ่น C99 สำหรับส่วนที่เหลือดูคำตอบและความคิดเห็นของฉันเกี่ยวกับคำตอบอื่น ๆ
R .. GitHub STOP HELPING ICE

6
ปัญหาคือสมาชิกบางคนของคณะกรรมการต้องการให้ "แฮ็ค" นี้เป็น UB อย่างยิ่งเพราะพวกเขาจินตนาการถึงแดนสวรรค์บางแห่งที่การใช้งาน C สามารถบังคับใช้ขอบเขตตัวชี้ได้ อย่างไรก็ตามเพื่อให้ดีขึ้นหรือแย่ลงการทำเช่นนั้นจะขัดแย้งกับส่วนอื่น ๆ ของมาตรฐานเช่นความสามารถในการเปรียบเทียบพอยน์เตอร์เพื่อความเท่าเทียมกัน (หากขอบเขตถูกเข้ารหัสในตัวชี้) หรือข้อกำหนดที่ว่าวัตถุใด ๆ สามารถเข้าถึงได้ผ่านunsigned char [sizeof object]อาร์เรย์ที่ซ้อนทับในจินตนาการ. ฉันยืนยันว่าสมาชิกอาร์เรย์ที่ยืดหยุ่น "แฮ็ก" สำหรับ pre-C99 มีพฤติกรรมที่กำหนดไว้อย่างชัดเจน
R .. GitHub STOP HELPING ICE

3

มาตรฐานค่อนข้างชัดเจนว่าคุณไม่สามารถเข้าถึงสิ่งต่างๆข้างท้ายอาร์เรย์ได้ (และการใช้พอยน์เตอร์ไม่ได้ช่วยอะไรเนื่องจากคุณไม่ได้รับอนุญาตให้เพิ่มพอยน์เตอร์เลยแม้แต่อันเดียวหลังจากสิ้นสุดอาร์เรย์)

และสำหรับ "การทำงานในทางปฏิบัติ". ฉันเคยเห็นเครื่องมือเพิ่มประสิทธิภาพ gcc / g ++ โดยใช้ส่วนนี้ของมาตรฐานจึงสร้างรหัสผิดเมื่อตรงกับ C ที่ไม่ถูกต้องนี้


ช่วยยกตัวอย่างได้ไหม
Tal

1

หากคอมไพเลอร์ยอมรับสิ่งที่ต้องการ

typedef struct {
  int len;
  ถ่าน dat [];
};

ฉันคิดว่าค่อนข้างชัดเจนว่าจะต้องพร้อมที่จะยอมรับตัวห้อยใน 'dat' ที่เกินความยาว ในทางกลับกันหากมีคนเขียนโค้ดบางอย่างเช่น:

typedef struct {
  int อะไรก็ได้;
  ถ่าน dat [1];
} MY_STRUCT;

จากนั้นจึงเข้าถึง somestruct-> dat [x]; ฉันไม่คิดว่าคอมไพเลอร์อยู่ภายใต้ภาระผูกพันใด ๆ ในการใช้โค้ดการคำนวณที่อยู่ซึ่งจะทำงานกับค่า x จำนวนมาก ฉันคิดว่าถ้าใครอยากปลอดภัยจริงๆกระบวนทัศน์ที่เหมาะสมจะเป็นเช่นนี้มากกว่า:

# กำหนด LARGEST_DAT_SIZE 0xF000
typedef struct {
  int อะไรก็ได้;
  ถ่าน dat [LARGEST_DAT_SIZE];
} MY_STRUCT;

จากนั้นทำ malloc ที่มีขนาด (sizeof (MYSTRUCT) -LARGEST_DAT_SIZE + ที่ต้องการ _array_length) ไบต์ (โปรดจำไว้ว่าหากความยาวคลื่นที่ต้องการมีขนาดใหญ่กว่า LARGEST_DAT_SIZE ผลลัพธ์อาจไม่ได้กำหนดไว้)

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.