คัดลอก structs กับสมาชิกที่ไม่ได้กำหนดค่าเริ่มต้น


29

มันถูกต้องหรือไม่ที่จะคัดลอก struct ที่สมาชิกบางคนไม่ได้เริ่มต้น?

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

เช่นนี้ถูกต้องหรือไม่

struct Data {
  int a, b;
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

ฉันจำได้ว่าเคยเห็นคำถามที่คล้ายกันมาพักหนึ่งแล้ว แต่หาไม่เจอ นี้คำถามที่เกี่ยวข้องตามที่เป็นอยู่นี้
1201ProgramAlarm

คำตอบ:


23

ใช่ถ้าสมาชิกที่ไม่ได้กำหนดค่าเริ่มต้นไม่ใช่ตัวอักษรแบบแคบที่ไม่ได้ลงชื่อหรือstd::byteจากนั้นการคัดลอกโครงสร้างที่มีค่าไม่แน่นอนนี้ด้วยตัวสร้างสำเนาที่กำหนดโดยปริยายคือพฤติกรรมที่ไม่ได้กำหนดทางเทคนิคเนื่องจากเป็นการคัดลอกตัวแปรที่มีค่าไม่แน่นอน ของ[dcl.init] / 12

นี้นำไปใช้ที่นี่เพราะตัวสร้างสำเนาสร้างขึ้นโดยปริยายคือยกเว้นunions ที่กำหนดไว้เพื่อคัดลอกสมาชิกแต่ละคนเป็นรายบุคคลเช่นถ้าโดยตรงเริ่มต้นดู[class.copy.ctor] / 4

และนี่ก็เป็นเรื่องของการใช้งานปัญหา CWG 2264

ฉันคิดว่าในทางปฏิบัติคุณจะไม่มีปัญหากับสิ่งนั้นแม้ว่า

ถ้าคุณต้องการที่จะแน่ใจ 100% การใช้งานstd::memcpyจะมีพฤติกรรมที่กำหนดไว้อย่างดีเสมอถ้าประเภทนั้นสามารถคัดลอกได้เล็กน้อยแม้ว่าสมาชิกจะมีค่าไม่แน่นอน


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

struct Data {
  int a{}, b{};
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

ดี .. struct นั่นไม่ได้เป็น POD (ข้อมูลเก่าธรรมดา) ใช่ไหม นั่นหมายความว่าสมาชิกจะเริ่มต้นด้วยค่าเริ่มต้นหรือไม่ เป็นข้อสงสัย
Kevin Kouketsu

นี่เป็นสำเนาตื้น ๆ ในกรณีนี้หรือไม่ สิ่งที่ผิดพลาดกับสิ่งนี้หากไม่ได้รับการกำหนดค่าเริ่มต้นให้เป็นสมาชิกในโครงสร้างที่คัดลอก
TruthSeeker

@KevinKouketsu ฉันได้เพิ่มเงื่อนไขสำหรับกรณีที่จำเป็นต้องใช้ประเภทเล็กน้อย / POD
วอลนัท

@TruthSeeker มาตรฐานบอกว่ามันเป็นพฤติกรรมที่ไม่ได้กำหนด เหตุผลที่มันเป็นพฤติกรรมที่ไม่ได้กำหนดไว้สำหรับตัวแปร (ไม่ใช่สมาชิก) โดยทั่วไปจะอธิบายในคำตอบโดย AndreySemashev โดยทั่วไปจะรองรับการเป็นตัวแทนกับหน่วยความจำที่ไม่ได้กำหนดค่าเริ่มต้น ไม่ว่าสิ่งนี้มีวัตถุประสงค์เพื่อนำไปใช้กับการสร้างสำเนาโดยนัยของ structs เป็นคำถามของปัญหา CWG ที่เชื่อมโยง
วอลนัท

@TruthSeeker ตัวสร้างสำเนาโดยนัยถูกกำหนดให้คัดลอกสมาชิกแต่ละรายแยกราวกับว่าโดยการเริ่มต้นโดยตรง ไม่ได้ถูกกำหนดให้คัดลอกการแสดงวัตถุราวกับว่าโดยmemcpyแม้สำหรับประเภทที่คัดลอกได้เล็กน้อย memcpyยกเว้นอย่างเดียวที่มีสหภาพแรงงานที่ตัวสร้างสำเนานัยไม่คัดลอกตัวแทนวัตถุเช่นถ้าโดย
วอลนัท

11

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

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

การส่งสัญญาณ NaNs เป็นไปได้สำหรับประเภทจุดลอยตัวและในจำนวนเต็มบางแพลตฟอร์มอาจมีการแทนแทร็บ

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


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

การเตรียมใช้งานข้อมูลนั้นไม่ได้ผล แต่ช่วยป้องกัน UB ไม่ว่าจะเกิดจากการเป็นตัวแทนกับดักหรือโดยใช้ข้อมูลที่ไม่ได้กำหนดค่าเริ่มต้นในภายหลัง การ zeroing 64 ไบต์ (1 หรือ 2 บรรทัดแคช) ไม่แพงเท่าที่ควร และถ้าคุณมีโครงสร้างขนาดใหญ่ที่มีราคาแพงคุณควรคิดให้รอบคอบก่อนคัดลอก และฉันค่อนข้างแน่ใจว่าคุณจะต้องเริ่มต้นพวกเขาต่อไปในบางจุด
Andrey Semashev

การทำงานของรหัสเครื่องซึ่งไม่สามารถส่งผลกระทบต่อพฤติกรรมของโปรแกรมนั้นไร้ประโยชน์ ความคิดที่ว่าการกระทำใด ๆ ที่มีลักษณะเป็น UB โดยมาตรฐานจะต้องหลีกเลี่ยงค่าใช้จ่ายทั้งหมดแทนที่จะบอกว่า [ในคำพูดของคณะกรรมการมาตรฐาน C] UB "ระบุพื้นที่ที่มีความเป็นไปได้ในการใช้ภาษาเสริม" ในขณะที่ฉันไม่ได้เห็นเหตุผลที่เผยแพร่สำหรับมาตรฐาน C ++ แต่เป็นการยกเว้นเขตอำนาจศาลของสิ่งที่โปรแกรม "อนุญาต" ทำโดยปฏิเสธที่จะจัดหมวดหมู่โปรแกรมว่าสอดคล้องหรือไม่สอดคล้องซึ่งหมายความว่ามันจะช่วยให้ส่วนขยายที่คล้ายกัน
supercat

-1

ในบางกรณีเช่นที่อธิบายไว้มาตรฐาน C ++ อนุญาตให้คอมไพเลอร์ประมวลผลการสร้างไม่ว่าลูกค้าของพวกเขาจะพบว่ามีประโยชน์มากที่สุดโดยไม่จำเป็นต้องคาดเดาพฤติกรรมนั้น กล่าวอีกนัยหนึ่งการสร้างดังกล่าวเรียกว่า "พฤติกรรมที่ไม่ได้กำหนด" อย่างไรก็ตามนั่นไม่ได้หมายความว่าการสร้างดังกล่าวมีความหมายว่า "ต้องห้าม" เนื่องจาก C ++ Standard สละเขตอำนาจศาลอย่างชัดเจนในสิ่งที่โปรแกรมที่มีรูปแบบดี "อนุญาต" ให้ทำ ในขณะที่ฉันไม่รู้เอกสาร Rationale ใด ๆ ที่ตีพิมพ์สำหรับ C ++ Standard ความจริงที่ว่ามันอธิบายพฤติกรรมที่ไม่ได้กำหนดเหมือน C89 จะแนะนำความหมายที่ตั้งใจไว้คล้ายกัน: "พฤติกรรมที่ไม่ได้กำหนดให้สิทธิ์ใช้งานของผู้ใช้งาน เพื่อวินิจฉัย

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

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

struct q { unsigned char dat[256]; } x,y;

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
    temp.dat[arr[i]] = i;
  x=temp;
  y=temp;
}

หากรหัสดาวน์สตรีมจะไม่สนใจค่าขององค์ประกอบใด ๆ ของx.datหรือy.datดัชนีที่ไม่อยู่ในรายการarrรหัสอาจถูกปรับให้เหมาะสมกับ:

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
  {
    int it = arr[i];
    x.dat[index] = i;
    y.dat[index] = i;
  }
}

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

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

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


-2

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


คุณสามารถสนับสนุนสิ่งนี้โดยอ้างอิงถึงมาตรฐานได้หรือไม่? ลิงก์ที่ให้บริการโดย @walnut แสดงว่านี่เป็นพฤติกรรมที่ไม่ได้กำหนด มีข้อยกเว้นสำหรับ POD ในมาตรฐานหรือไม่
Tomek Czajka

แม้ว่าต่อไปนี้จะไม่เชื่อมโยงไปยังมาตรฐานยัง: en.cppreference.com/w/cpp/language/… "สามารถคัดลอกวัตถุ TriviallyCopyable โดยคัดลอกการเป็นตัวแทนวัตถุด้วยตนเองเช่น std :: memmove ชนิดข้อมูลทั้งหมดที่เข้ากันได้กับ C ภาษา (ประเภท POD) สามารถคัดลอกได้เล็กน้อย "
ivan.ukr

"พฤติกรรมที่ไม่ได้กำหนด" เพียงอย่างเดียวในกรณีนี้คือเราไม่สามารถทำนายค่าของตัวแปรสมาชิกที่ไม่ได้กำหนดค่าเริ่มต้น แต่การคอมไพล์โค้ดและทำงานได้สำเร็จ
ivan.ukr

1
ส่วนที่คุณพูดถึงเกี่ยวกับพฤติกรรมของ memmove แต่มันไม่เกี่ยวข้องกันจริงๆที่นี่เพราะในรหัสของฉันฉันใช้ตัวสร้างการคัดลอกไม่ใช่ memmove คำตอบอื่น ๆ บ่งบอกว่าการใช้ตัวสร้างการคัดลอกส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนด ฉันคิดว่าคุณเข้าใจผิดว่าคำว่า "พฤติกรรมที่ไม่ได้กำหนด" หมายความว่าภาษาไม่รับประกันอะไรเลยเช่นโปรแกรมอาจขัดข้องหรือข้อมูลเสียหายแบบสุ่มหรือทำอะไรก็ได้ ไม่ได้หมายความว่าคุณค่าบางอย่างนั้นไม่อาจคาดเดาได้ว่าจะเป็นพฤติกรรมที่ไม่ระบุ
Tomek Czajka

@ ivan.ukr มาตรฐาน C ++ ระบุว่าตัวสร้างการคัดลอก / ย้ายโดยนัยทำหน้าที่สมาชิกที่ชาญฉลาดราวกับว่าโดยการกำหนดค่าเริ่มต้นโดยตรงให้ดูลิงก์ในคำตอบของฉัน ดังนั้นโครงสร้างการคัดลอกจึงไม่ได้ทำการคัดลอกทีละบิต " " คุณเป็นเพียงที่ถูกต้องสำหรับประเภทสหภาพซึ่งตัวสร้างสำเนาโดยปริยายจะstd::memcpyระบุให้คัดลอกตัวแทนวัตถุเช่นถ้าโดยคู่มือการใช้งาน ไม่มีการป้องกันโดยใช้หรือstd::memcpy std::memmoveมันป้องกันเฉพาะการใช้ตัวสร้างการคัดลอกโดยนัย
วอลนัท
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.