จะเกิดอะไรขึ้นถ้าคุณ static_cast ค่าที่ไม่ถูกต้องใน enum class?


146

พิจารณารหัส C ++ 11 นี้:

enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(data[0]);

สมมติว่าข้อมูล [0] เป็น 100 จริง ๆ สีใดที่ตั้งค่าตามมาตรฐาน? โดยเฉพาะถ้าฉันทำในภายหลัง

switch (color) {
    // ... red and yellow cases omitted
    default:
        // handle error
        break;
}

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

แก้ไข:

เป็นมาตรฐานมาตรฐานทำรับประกันใด ๆ เกี่ยวกับเรื่องนี้ แต่มี enum ธรรมดาหรือไม่?

คำตอบ:


131

สีที่ตั้งไว้เป็นไปตามมาตรฐานคืออะไร?

ตอบด้วยเครื่องหมายคำพูดจากมาตรฐาน C ++ 11 และ C ++ 14:

[expr.static.cast] / 10

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

ลองค้นหาช่วงของค่าการแจกแจง : [dcl.enum] / 7

สำหรับการแจงนับซึ่งชนิดพื้นฐานถูกแก้ไขค่าของการแจงนับเป็นค่าของชนิดที่ขีดเส้นใต้

ก่อน CWG 1766 (C ++ 11, C ++ 14) ดังนั้นสำหรับdata[0] == 100ค่าส่งผลให้มีการระบุ (*) และไม่มีพฤติกรรมที่ไม่ได้กำหนด (UB)มีส่วนเกี่ยวข้อง โดยทั่วไปในขณะที่คุณโยนจากประเภทต้นแบบชนิดนับค่าในไม่data[0]สามารถนำไปสู่ UB static_castสำหรับ

หลังจาก CWG 1766 (C ++ 17) ดูCWG ข้อบกพร่อง 1766 ย่อหน้า [expr.static.cast] p10 มีความเข้มแข็งดังนั้นตอนนี้คุณสามารถเรียกใช้ UB ได้ถ้าคุณส่งค่าที่อยู่นอกช่วงที่สามารถแสดงค่าของ enum เป็นชนิด enum ได้ สิ่งนี้ยังใช้ไม่ได้กับสถานการณ์ในคำถามเนื่องจากdata[0]เป็นประเภทของการแจงนับ (ดูด้านบน)

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

(*) charจะต้องมีอย่างน้อย 8 บิตกว้าง unsignedแต่ไม่จำเป็นต้องเป็น ต้องมีค่าสูงสุดที่สามารถจัดเก็บได้อย่างน้อย127ต่อ Annex E ของมาตรฐาน C99


เปรียบเทียบกับ [expr] / 4

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

ก่อน CWG 1766 การแปลงชนิดหนึ่ง -> ประเภทการแจงนับสามารถผลิตมูลค่าไม่ระบุรายละเอียด คำถามคือ: ค่าที่ไม่ระบุสามารถอยู่นอกค่าที่สามารถแทนได้สำหรับประเภทของมันหรือไม่? ฉันเชื่อว่าคำตอบคือไม่ - ถ้าคำตอบคือใช่จะไม่มีความแตกต่างในการรับประกันที่คุณได้รับสำหรับการดำเนินการกับประเภทที่เซ็นชื่อระหว่าง "การดำเนินการนี้สร้างมูลค่าที่ไม่ระบุ" และ "การดำเนินการนี้มีพฤติกรรมที่ไม่ได้กำหนด"

ดังนั้นก่อนที่จะ CWG 2309 แม้static_cast<Color>(10000)จะไม่เรียก UB; แต่หลังจาก CWG 1766 มันจะเรียกใช้ UB


ตอนนี้switchคำสั่ง:

[stmt.switch] / 2

เงื่อนไขจะต้องเป็นประเภทหนึ่งประเภทการแจงนับหรือประเภทชั้นเรียน [... ] การส่งเสริมการขายแบบรวมจะดำเนินการ

[conv.prom] / 4

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

หมายเหตุ: ประเภทพื้นฐานของ enum ขอบเขต w / o enum ฐานintคือ สำหรับ enscoped enums ประเภทพื้นฐานคือการใช้งานที่กำหนด แต่จะต้องไม่ใหญ่กว่าintถ้าintสามารถมีค่าของตัวแจงนับทั้งหมด

สำหรับการแจงนับที่ไม่มีขอบเขตนี่จะนำเราไปสู่ ​​/ 1

prvalue ของจำนวนเต็มชนิดอื่นที่ไม่ใช่bool, char16_t, char32_tหรือwchar_tมีจำนวนเต็มแปลงยศ (4.13) น้อยกว่ายศintสามารถแปลงเป็น prvalue ของประเภทintถ้าintสามารถเป็นตัวแทนของค่าทั้งหมดของประเภทแหล่งที่มา มิฉะนั้น prvalue แหล่งที่สามารถแปลงเป็น prvalue unsigned intของประเภท

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

[stmt.switch] / 5

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

defaultฉลากควรจะตี

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


เป็นมาตรฐานมาตรฐานทำรับประกันใด ๆ เกี่ยวกับเรื่องนี้ แต่มี enum ธรรมดาหรือไม่?

การenumกำหนดขอบเขตไม่ได้สร้างความแตกต่างที่นี่หรือไม่ อย่างไรก็ตามมันจะสร้างความแตกต่างว่าประเภทพื้นฐานได้รับการแก้ไขหรือไม่ ความสมบูรณ์ [decl.enum] / 7 คือ:

สำหรับการแจงนับซึ่งชนิดพื้นฐานถูกแก้ไขค่าของการแจงนับเป็นค่าของชนิดที่ขีดเส้นใต้ มิฉะนั้นการแจงนับกรณีที่อีนาทีเป็นที่เล็กที่สุดแจงนับและอีแม็กซ์ที่ใหญ่ที่สุดคือค่านิยมของการแจกแจงที่มีค่าในช่วงนาทีเพื่อb สูงสุดที่กำหนดไว้ดังต่อไปนี้: ให้Kเป็น1ตัวแทนเติมเต็มสองและ0สำหรับ ส่วนเสริมหรือการเป็นตัวแทนของขนาดสัญญาณ b maxคือค่าที่เล็กที่สุดที่มากกว่าหรือเท่ากับmax (| e min | - K, | e max |)และเท่ากับ2M - 1โดยที่Mเป็นจำนวนเต็มไม่เป็นลบ b minเป็นศูนย์ถ้า e minไม่ใช่ค่าลบและ - (b max + K) เป็นอย่างอื่น

ลองมาดูการแจงนับต่อไปนี้:

enum ColorUnfixed /* no fixed underlying type */
{
    red = 0x1,
    yellow = 0x2
}

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

โชคดีColorUnfixedที่ตัวแจงนับที่เล็กที่สุดคือred = 0x1ดังนั้นmax (| e min | - K, | e max |)เท่ากับ| e max | ในกรณีใด ๆ yellow = 0x2ซึ่งเป็น ค่าที่น้อยที่สุดซึ่งมากกว่าหรือเท่ากับ2ซึ่งเท่ากับ2 M - 1สำหรับจำนวนเต็มบวกMคือ3( 2 2 - 1 ) (ผมคิดว่าเจตนาคือการช่วยให้ช่วงที่มีขอบเขตใน 1 บิตขั้นตอน.) มันตามที่สูงสุดเป็น3และbmin0คือ

ดังนั้น100จะอยู่นอกช่วงColorUnfixedและstatic_castจะสร้างค่าที่ไม่ระบุก่อน CWG 1766 และพฤติกรรมที่ไม่ได้กำหนดหลังจาก CWG 1766


3
ประเภทพื้นฐานได้รับการแก้ไขดังนั้นช่วงของค่าการแจงนับ (§7.2 [dcl.enum] p7) คือ "ค่าของประเภทพื้นฐาน" 100 เป็นค่าแน่นอนcharดังนั้น "ค่าจะไม่เปลี่ยนแปลงหากค่าเดิมอยู่ในช่วงของค่าการแจงนับ (7.2)" มีผลบังคับใช้
Casey

2
ฉันต้องค้นหาเพื่อค้นหาความหมายของ "UB" ('พฤติกรรมที่ไม่ได้กำหนด') คำถามไม่ได้กล่าวถึงความเป็นไปได้ของพฤติกรรมที่ไม่ได้กำหนด ดังนั้นมันไม่ได้เกิดขึ้นกับฉันว่าคุณอาจพูดถึง
karadoc

2
@karadoc ฉันได้เพิ่มลิงค์เมื่อเกิดคำแรก
dyp

1
รักคำตอบนี้ สำหรับการอ่านที่รวดเร็วเกินไปโปรดทราบว่าประโยคสุดท้าย "ดังนั้น 100 จะอยู่นอกช่วง ... " จะใช้เฉพาะในกรณีที่มีการแก้ไขโค้ดเพื่อลบข้อมูลจำเพาะประเภทพื้นฐาน (อักขระในกรณีนี้) ฉันคิดว่านั่นคือสิ่งที่ตั้งใจ
Eric Seppanen

1
@Ruslan CWG 1766 (หรือความละเอียดของมัน) ไม่ได้เป็นส่วนหนึ่งของ C ++ 14 แต่ฉันคิดว่ามันจะเป็นส่วนหนึ่งของ C ++ 17 แม้จะมีกฎ C ++ 17 แต่ฉันก็ไม่เข้าใจว่าคุณหมายถึงอะไรด้วย ส่วนอื่น ๆ ของคำตอบของฉันเกี่ยวข้องกับส่วนใหญ่ว่า "ช่วงของค่าการแจงนับ" คือ expr.static.cast p10 ที่อ้างถึง
59
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.