สิ่งที่ป้องกันการทับซ้อนกันของสมาชิกที่อยู่ติดกันในชั้นเรียน?


12

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

class blub {
    int i;
    char c;

    blub(const blub&) {}
};

class blob {
    char s;

    blob(const blob&) {}
};

struct bla {
    blub b0;
    blob b1;
};

บนแพลตฟอร์มทั่วไปที่intมีขนาด 4 ไบต์ขนาดการจัดตำแหน่งและระยะห่างรวม1เป็นดังนี้:

  struct   size   alignment   padding  
 -------- ------ ----------- --------- 
  blub        8           4         3  
  blob        1           1         0  
  bla        12           4         6  

ไม่มีการทับซ้อนกันระหว่างที่เก็บของblubและblobสมาชิกแม้ว่าขนาด 1 blobสามารถทำได้ในหลักการ "พอดี" ในการเติมblubเต็ม

C ++ 20 แนะนำno_unique_addressคุณสมบัติซึ่งอนุญาตให้สมาชิกว่างที่อยู่ติดกันแบ่งปันที่อยู่เดียวกัน นอกจากนี้ยังช่วยให้สถานการณ์ที่อธิบายไว้ข้างต้นอย่างชัดเจนของการใช้การขยายของสมาชิกคนหนึ่งเพื่อเก็บอีก จากcppreference (เน้นที่เหมือง):

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

อันที่จริงถ้าเราใช้แอตทริบิวต์นี้ในblub b0ขนาดของblaหยดไป8ดังนั้นblobจะถูกเก็บไว้แน่นอนในblub เท่าที่เห็นใน godbolt

ในที่สุดเราก็มาถึงคำถามของฉัน:

ข้อความใดในมาตรฐาน (C ++ 11 ถึง C ++ 20) ป้องกันการซ้อนทับนี้โดยไม่มีno_unique_addressสำหรับวัตถุที่ไม่สามารถคัดลอกได้เล็กน้อย

ฉันต้องแยกวัตถุที่คัดลอกได้เล็กน้อย (TC) จากด้านบนเพราะสำหรับวัตถุ TC มันได้รับอนุญาตstd::memcpyจากวัตถุหนึ่งไปยังวัตถุอื่นรวมถึง subobjects สมาชิกและถ้าที่เก็บซ้อนทับสิ่งนี้จะทำลาย (เพราะทั้งหมดหรือบางส่วนของหน่วยเก็บข้อมูล สำหรับสมาชิกที่อยู่ติดกันจะถูกเขียนทับ) 2 .


1เราคำนวณการเติมเต็มเพียงแค่ความแตกต่างระหว่างขนาดโครงสร้างและขนาดของส่วนประกอบที่เป็นสมาชิกทั้งหมดซ้ำ

2นี่คือเหตุผลที่ผมได้สำเนาก่อสร้างที่กำหนดไว้เพื่อให้blubและblobไม่copyable นิด


ฉันยังไม่ได้ทำการวิจัย แต่ฉันคาดเดากฎ "ราวกับ" หากไม่มีความแตกต่างที่สังเกตได้ (คำที่มีความหมายเฉพาะ btw) ไปยังเครื่องนามธรรม (ซึ่งเป็นรหัสที่คุณคอมไพล์ด้วย) ผู้แปลสามารถเปลี่ยนรหัสได้ตามที่ต้องการ
Jesper Juhl

ค่อนข้างแน่ใจว่านี่เป็น
คู่หู

@JesperJuhl - ถูกต้อง แต่ฉันถามว่าทำไมถึงทำไม่ได้ไม่ใช่ทำไมถึงเป็นเช่นนั้นและกฎ "ราวกับว่า" มักใช้กับอดีต แต่ไม่สมเหตุสมผลสำหรับคนหลัง นอกจากนี้ "ราวกับว่า" ไม่ชัดเจนสำหรับการจัดวางโครงสร้างซึ่งโดยทั่วไปมักเป็นปัญหาระดับโลก ในที่สุดคอมไพเลอร์จะต้องมีชุดกฎที่สอดคล้องกันเพียงชุดเดียวสำหรับโครงร่างยกเว้นบางทีสำหรับโครงสร้างที่สามารถพิสูจน์ได้ว่า
BeeOnRope

1
@BeeOnRope ฉันไม่สามารถตอบคำถามของคุณขอโทษ นี่คือเหตุผลที่ฉันเพิ่งโพสต์ความคิดเห็นและไม่ใช่คำตอบ สิ่งที่คุณได้รับในความคิดเห็นนั้นคือการคาดเดาที่ดีที่สุดของฉันต่อคำอธิบาย แต่ฉันไม่รู้คำตอบ (อยากรู้อยากเห็นเพื่อเรียนรู้ด้วยตนเอง - ซึ่งเป็นสาเหตุที่คุณได้รับการโหวต)
Jesper Juhl

1
@NicolBolas - คุณตอบคำถามใช่มั้ย นี่ไม่เกี่ยวกับการตรวจจับสำเนาที่ปลอดภัยหรือสิ่งอื่นใด แต่ฉันสงสัยว่าทำไมการเว้นระยะไม่สามารถใช้ซ้ำระหว่างสมาชิกได้ ไม่ว่าในกรณีใดคุณผิด: คัดลอกได้เล็กน้อยเป็นคุณสมบัติของประเภทและได้รับเสมอ อย่างไรก็ตามในการคัดลอกวัตถุอย่างปลอดภัยทั้งคู่จะต้องมีประเภท TC (คุณสมบัติของประเภท) และไม่ใช่วัตถุที่ทับซ้อนกัน (คุณสมบัติของวัตถุซึ่งฉันเดาว่าเป็นที่ที่คุณสับสน) ยังไม่รู้ว่าทำไมเรากำลังพูดถึงสำเนาอยู่ที่นี่
BeeOnRope

คำตอบ:


1

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

ก่อนอื่นมาหาสิ่งที่เป็นส่วนหนึ่งของวัตถุด้วยซ้ำ [basic.types] / 4 :

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

ดังนั้นการแสดงวัตถุb0ประกอบด้วยsizeof(blub) unsigned charวัตถุดังนั้น 8 ไบต์ บิตการแพ็ดดิ้งเป็นส่วนหนึ่งของวัตถุ

ไม่มีวัตถุใดสามารถครอบครองพื้นที่ว่างของวัตถุอื่นได้หากไม่ใช่วัตถุซ้อนอยู่ภายใน[basic.life] /1.5 :

อายุการใช้งานของวัตถุoประเภทTสิ้นสุดลงเมื่อ:

[ ... ]

(1.5) การจัดเก็บข้อมูลซึ่งวัตถุครอบครองครอบครองหรือถูกนำมาใช้ใหม่โดยวัตถุที่ไม่ได้ซ้อนอยู่ภายในo([intro.object])

ดังนั้นอายุการใช้งานของเราจะจบลงเมื่อการจัดเก็บที่ถูกครอบครองโดยมันจะถูกนำกลับมาใช้โดยวัตถุอื่นเช่นb0 b1ฉันไม่ได้ตรวจสอบ แต่ฉันคิดว่าเอกสารมาตรฐานที่ subobject ของวัตถุที่ยังมีชีวิตอยู่ควรมีชีวิตอยู่ด้วย (และฉันไม่สามารถจินตนาการได้ว่าสิ่งนี้จะทำงานแตกต่างกันอย่างไร)

ดังนั้นที่เก็บข้อมูลที่b0 ใช้งานอาจไม่สามารถใช้งานb1ได้ ฉันไม่พบคำจำกัดความของ "ครอบครอง" ในมาตรฐาน แต่ฉันคิดว่าการตีความที่สมเหตุสมผลจะเป็น "ส่วนหนึ่งของการแทนวัตถุ" ในการแสดงวัตถุอ้าง descriping คำว่า "ใช้เวลา" จะใช้1 นี่คือ 8 ไบต์ดังนั้นblaต้องมีอย่างน้อยหนึ่งb1รายการ

โดยเฉพาะอย่างยิ่งสำหรับ subobjects (ดังนั้นในบรรดาสมาชิกที่ไม่ใช่ข้อมูลคงที่) ยังมีข้อตกลง[intro.object] / 9 (แต่สิ่งนี้ถูกเพิ่มด้วย C ++ 20, ขอบคุณ @BeeOnRope)

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

(ให้ความสำคัญกับฉัน) ที่นี่อีกครั้งเรามีปัญหาที่ "ครอบครอง" ไม่ได้ถูกกำหนดและอีกครั้งที่ฉันจะโต้แย้งว่าต้องใช้ไบต์ในการแสดงวัตถุ โปรดทราบว่ามีเชิงอรรถสำหรับ[basic.memobj] / เชิงอรรถ 29

ภายใต้กฎ“ as-if” การใช้งานจะได้รับอนุญาตให้เก็บวัตถุสองรายการที่ที่อยู่เครื่องเดียวกันหรือไม่เก็บวัตถุเลยหากโปรแกรมไม่สามารถสังเกตเห็นความแตกต่าง ([intro.execution])

ซึ่งอาจทำให้คอมไพเลอร์ทำลายสิ่งนี้หากพิสูจน์ได้ว่าไม่มีผลข้างเคียงที่สังเกตได้ ฉันคิดว่ามันค่อนข้างซับซ้อนสำหรับสิ่งพื้นฐานเช่นการจัดวางวัตถุ อาจเป็นเพราะเหตุนี้การปรับให้เหมาะสมนี้จะเกิดขึ้นก็ต่อเมื่อผู้ใช้ให้ข้อมูลว่าไม่มีเหตุผลใดที่จะทำให้วัตถุเข้าด้วยกันโดยการเพิ่ม[no_unique_address]คุณสมบัติ

tl; dr: การขยายส่วนอาจเป็นส่วนหนึ่งของวัตถุและสมาชิกจะต้องแยกจากกัน


1ฉันไม่สามารถต้านทานการเพิ่มการอ้างอิงที่ครอบครองอาจหมายถึงการใช้: พจนานุกรม Unabridged ฉบับปรับปรุงของเว็บสเตอร์, G. & C. Merriam, 1913 (เหมืองที่เน้น)

  1. ที่จะถือหรือเติมขนาดของ; ที่จะขึ้นห้องหรือพื้นที่ของ; เพื่อปกปิดหรือเติม ในขณะที่ค่ายครอบครองพื้นที่ห้าเอเคอร์ เซอร์เจเฮอร์เชล

การรวบรวมข้อมูลมาตรฐานใดที่จะสมบูรณ์หากไม่มีการรวบรวมข้อมูลพจนานุกรม


2
ส่วน "ครอบครอง disjoint bytes ของหน่วยเก็บข้อมูล" จากในนั้นพื้นที่จัดเก็บก็เพียงพอแล้วสำหรับฉัน - แต่ถ้อยคำนี้ถูกเพิ่มใน C ++ 20 เท่านั้นซึ่งเป็นส่วนหนึ่งของการเปลี่ยนแปลงที่เพิ่มเข้าno_unique_addressมา ทำให้สถานการณ์ก่อน C ++ 20 ชัดเจนน้อยลง ฉันไม่เข้าใจเหตุผลของคุณที่นำไปสู่ ​​"ไม่มีวัตถุใดสามารถครอบครองพื้นที่ว่างของวัตถุอื่นได้หากไม่ใช่สิ่งซ้อนกันอยู่ภายใน" จากพื้นฐานพื้นฐาน life/1.5 โดยเฉพาะอย่างยิ่งว่าจะได้รับจาก "พื้นที่เก็บข้อมูลที่มีวัตถุว่าง" ถึง "ไม่มีวัตถุใดสามารถครอบครองพื้นที่ของวัตถุอื่น"
BeeOnRope

1
ฉันเพิ่มความกระจ่างเล็กน้อยในย่อหน้านั้น ฉันหวังว่าจะทำให้เข้าใจได้มากขึ้น ไม่งั้นฉันจะดูอีกครั้งพรุ่งนี้ตอนนี้มันสายไปแล้ว
n314159

"วัตถุสองชิ้นที่มีอายุการใช้งานที่ทับซ้อนกันซึ่งไม่ใช่บิตฟิลด์อาจมีที่อยู่เดียวกันหากวัตถุหนึ่งซ้อนกันอยู่ภายในวัตถุอื่นหรืออย่างน้อยหนึ่งวัตถุย่อยที่มีขนาดเป็นศูนย์และเป็น วัตถุประเภทอื่น"ประเภทเดียวกันที่มีที่อยู่เดียวกัน
ทนายความภาษา

ขออภัยคุณสามารถอธิบายรายละเอียดเพิ่มเติม? คุณกำลังอ้างอิงข้อความอ้างอิงมาตรฐานจากคำตอบของฉันและนำตัวอย่างที่ขัดแย้งกับเรื่องนั้น ฉันไม่แน่ใจว่านี่เป็นความคิดเห็นในคำตอบของฉันหรือไม่และถ้าเป็นสิ่งที่ควรบอกฉัน เกี่ยวกับตัวอย่างของคุณฉันจะบอกว่าต้องพิจารณาส่วนอื่น ๆ ของมาตรฐาน (มีย่อหน้าเกี่ยวกับอาร์เรย์ char ที่ไม่ได้ลงนามซึ่งจัดเก็บสำหรับวัตถุอื่นสิ่งที่เกี่ยวกับการเพิ่มประสิทธิภาพฐานศูนย์ขนาดและยังควรดูว่าตำแหน่งใหม่มี ค่าเผื่อพิเศษทุกสิ่งที่ฉันไม่คิดว่าเกี่ยวข้องกับตัวอย่าง
OPs

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