ข้อเสนอ C++: ไบต์ต้องมี 8 บิตพอดี
(open-std.org)- P3477R0 เป็นข้อเสนอแก้ไขมาตรฐานที่ต้องการกำหนดขนาดไบต์ของ C++ ให้เป็น 8 บิตพอดี แทนที่จะปล่อยให้เป็นค่าที่ implementation-defined อย่าง
CHAR_BIT - แพลตฟอร์มสมัยใหม่ได้มาบรรจบกันบนสมมติฐานของ ไบต์ 8 บิต อยู่แล้ว และ GCC·LLVM·MSVC ก็ตั้งค่าเริ่มต้นหรือแมโครที่เกี่ยวข้องไว้เป็น 8
- POSIX กำหนดให้
CHAR_BIT == 8มาตั้งแต่ POSIX.1-2001 และกระแสการรับ การแทนค่าจำนวนเต็มแบบ two's complement ใน C++20 และ C23 ก็ไปในทิศทางเดียวกัน - มองว่าการรองรับไบต์ที่ไม่ใช่ 8 บิตทิ้งข้อยกเว้นเล็ก ๆ ไว้ทั่วทั้งภาษา ไลบรารี และ toolchain และสร้างภาระจาก edge case ที่ไม่สอดคล้องกับการใช้งาน C++ สมัยใหม่จริง
- แม้จะมีสถาปัตยกรรมข้อยกเว้นอย่าง PDP-10 หรือ DSP บางตัวอยู่จริง แต่ประเด็นสำคัญคือมาตรฐาน C++ ใหม่ควรต้องรักษาความซับซ้อนเพื่อเป้าหมายเหล่านั้นต่อไปหรือไม่
เป้าหมายการเปลี่ยนแปลงของ P3477R0
- C++ นำแมโคร
CHAR_BITของ C มาใช้ และปัจจุบันค่านี้เป็น ค่า implementation-defined ที่แสดงจำนวนบิตในหนึ่งไบต์ - P3477R0 เสนอให้เปลี่ยนมาตรฐาน C++ ให้กำหนดอย่างเป็นทางการว่าไบต์ต้องเป็น 8 บิต
- ในยุคแรกของการประมวลผล ความยืดหยุ่นที่อนุญาตให้มีขนาดไบต์หลากหลายยังมีความหมาย แต่ข้อเสนอนี้ตั้งอยู่บนการประเมินว่าฮาร์ดแวร์สมัยใหม่แทบทั้งหมดได้บรรจบกันบนสมมติฐานของไบต์ 8 บิตแล้ว
สถานะปัจจุบันของคอมไพเลอร์และแพลตฟอร์ม
- คอมไพเลอร์หลัก ๆ ปฏิบัติกับไบต์ 8 บิตเป็นความเป็นจริงพื้นฐานอยู่แล้ว
- ในกรณีการรองรับในอดีตของ GCC นั้น
dsp16xxถูกลบออกในปี 2004 และ1750aถูกลบออกในปี 2002 - จากการค้นเว็บพบพอร์ตภายนอกของ GCC บางตัวที่
BITS_PER_UNITไม่ใช่ 8 แต่ข้อเสนอมองว่าไม่น่าจะเกี่ยวข้องกับ C++ สมัยใหม่
POSIX และทิศทางของการแทนค่าจำนวนเต็ม
- POSIX กำหนดเงื่อนไขต่อไปนี้มาตั้งแต่ POSIX.1-2001
- ไบต์ต้องมี 8 บิต พอดี
CHAR_BITต้องเป็น8SCHAR_MAXต้องเป็น127,SCHAR_MINต้องเป็น-128,UCHAR_MAXต้องเป็น255
- POSIX อธิบายว่า ผลจากการเพิ่ม
int8_tทำให้ต้องกำหนดcharแบบ 8 บิตและเลขคณิตแบบ two's complement - C++20 หลังจาก P0907r4 รองรับเฉพาะ รูปแบบการจัดเก็บแบบ two's complement และ C23 ก็เดินตามทิศทางเดียวกัน
- ตัวอย่างระบบปฏิบัติการที่สอดคล้องกับ POSIX ในปัจจุบันที่ถูกระบุไว้ ได้แก่ AIX, HP-UX, INTEGRITY, macOS, OpenServer, UnixWare, VxWorks, vz/OS
ต้นทุนที่ไบต์ไม่ใช่ 8 บิตทิ้งไว้
- ซอฟต์แวร์สำหรับไบต์ 8 บิตและซอฟต์แวร์สำหรับไบต์ที่ไม่ใช่ 8 บิตไม่เข้ากัน และมองว่าโค้ด C/C++ สำหรับเป้าหมายที่ไบต์ไม่ใช่ 8 บิตนั้นในทางปฏิบัติใกล้เคียงกับ dialect ที่เข้ากันไม่ได้ ของ C และ C++
- การรองรับสถาปัตยกรรมที่ไบต์ไม่ใช่ 8 บิตทิ้งความซับซ้อนเล็ก ๆ แต่ไม่จำเป็นไว้ในหลายส่วนของภาษาและไลบรารี
- คอมไพเลอร์และ toolchain ต้องแบกรับ edge case ที่ไม่ได้สะท้อนการใช้งานสมัยใหม่ต่อไป
- โปรแกรมเมอร์ใหม่มักสับสนกับคุณลักษณะแปลกเฉพาะเหล่านี้ของ C++
- มองว่าโปรแกรมเมอร์ผู้มีประสบการณ์บางส่วนต้องเสียเวลาไปกับ “ความพกพาได้” สำหรับแพลตฟอร์มที่ไม่มีอยู่จริง
สถาปัตยกรรมข้อยกเว้นและทางประนีประนอม
- ข้อเสนอยอมรับว่ายังมีโปรเซสเซอร์ที่ไบต์ไม่ใช่ 8 บิตอยู่
- คำถามหลักคือโปรเซสเซอร์เหล่านั้นเกี่ยวข้องกับ C++ สมัยใหม่ หรือไม่ และผู้ใช้โปรเซสเซอร์เหล่านั้นจะใช้ C++ เวอร์ชันใหม่หรือไม่
- มีการเสนอทางประนีประนอมให้กำหนด
CHAR_BIT % 8 == 0ด้วย แต่สิ่งนี้จะมีความหมายก็ต่อเมื่อคณะกรรมการตัดสินใจจะยังรองรับ DSP หรือโปรเซสเซอร์อื่นที่CHAR_BITไม่ใช่ 8 แต่เป็นพหุคูณของ 8 ต่อไป - PDP-10 อยู่ในขอบเขตการอภิปราย แต่แยกให้ชัดว่า PDP-11 ใช้ไบต์ 8 บิต
- DSP บางตัวปฏิบัติต่อเวิร์ด 24 บิตหรือ 32 บิตเหมือน “ไบต์” และสถาปัตยกรรมเช่นนี้เคยสมเหตุสมผลในยุคที่ขนาดคำหลากหลายและแนวคิดเรื่องไบต์ยังไม่ได้มาตรฐาน
ทิศทางการเปลี่ยนถ้อยคำในมาตรฐาน
- ต้องการเปลี่ยนนิยามของไบต์ใน
intro.memoryเพื่อระบุให้ชัดว่าไบต์ ซึ่งเป็นหน่วยจัดเก็บพื้นฐานของ memory model ใน C++ คือ 8 บิต - ใน
climitsเสนอให้เปลี่ยนถ้อยคำไปในทิศทางที่กำหนดCHAR_BITเป็น8 - ใน
cstdintเนื่องจากไบต์เป็น 8 บิต ชนิดจำนวนเต็มที่กำหนดความกว้างไว้ เช่นint8_t,uint8_tและแมโครที่เกี่ยวข้อง จึงจะไม่เป็นตัวเลือกเสริมอีกต่อไป - สำหรับชนิดที่ใช้
_N_หากNไม่ใช่8,16,32,64จะยังคงให้เป็นตัวเลือกเสริมต่อไป - รวมถึงการเปลี่ยนแปลงเพื่อลบ mandates 4 รายการเกี่ยวกับ
CHAR_BIT == 8ในlocalization
ความสัมพันธ์กับมาตรฐาน C
- ข้อเสนอนี้พิจารณาว่า C++ ควรยังเกี่ยวข้องกับสถาปัตยกรรมที่ไบต์ไม่ใช่ 8 บิตต่อไปหรือไม่
- คณะกรรมการ C อาจได้ข้อสรุปที่แตกต่างสำหรับภาษา C
- แม้การจัดแนวกันของทั้งสองคณะกรรมการจะเป็นอุดมคติ แต่ข้อเสนอนี้วางแนวทางให้กลุ่มประสานงาน WG14 และ SG22 ให้ข้อมูลแก่ WG21
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ในซีรีส์ของ JF ที่ว่า “เรายอมรับไม่ได้หรือว่าคอมพิวเตอร์จริง ๆ ก็ทำงานแบบนี้กันหมด?” เคยมีตอน จำนวนเต็มมีเครื่องหมายเป็น two’s complement ไปแล้ว: "Signed Integers are Two’s Complement"
แต่ถ้าเป็นอย่างนั้น โค้ดคลาสสิกของเคอร์เนล Linux นี้อาจกลายเป็นของล้าสมัย: https://github.com/torvalds/linux/blob/master/include/math-emu/double.h#L29
ตอนเป็นเด็กฝึกงานในปี 1986 เคยเขียนโค้ด C บน BBN C/70 ที่ใช้ ไบต์ 10 บิต เป็นประสบการณ์ที่เลวร้ายมาก และการที่มีเครื่องแบบนั้นอยู่ตั้งแต่แรกก็เป็นอุบัติเหตุระดับจักรวาลในความหมายเชิงลบ
มันใช้ เวิร์ด 36 บิต และ ไบต์ 7 บิต และเมื่อยัดไบต์ลงในเวิร์ดก็จะมีบิตเหลืออยู่ แล้วผมยังได้รับงานให้อ่านเทปที่มีข้อมูลไบนารีในรูปแบบ 8 บิตอีก เละเทะไปหมด
แต่ถ้าเป็น C แบบ 10 บิตก็อีกเรื่องหนึ่ง
D ก้าวหน้าไปมากด้วยการกำหนดว่า: ไบต์คือ 8 บิต, short คือ 16 บิต, int คือ 32 บิต, long คือ 64 บิต, เลขคณิตเป็น two’s complement, จุดลอยตัวเป็น IEEE floating point
ช่วยประหยัดเวลามหาศาลที่เคยหมดไปกับการพยายามทำ abstraction ของสิ่งเหล่านี้แล้วสุดท้ายก็ผิดพลาด และทำให้คนหลายล้านโล่งใจ ชุดอักขระก็เป็น Unicode ไม่ใช่ EBCDIC หรือ RADIX-50
u8/i8,u16/i16,u32/i32,u64/i64และเลือกเลขคณิตอย่างชัดเจนด้วยoverflow ของ
+เป็นพฤติกรรมที่ไม่ถูกต้อง จึงหยุดใน debug และ releasesafe,+%คือ การ wrap แบบ two’s complement,+|คือเลขคณิตแบบอิ่มตัว ส่วน@addWithOverflow()ให้ทูเพิลของชนิดเดิมกับu1และstd.math.add()จะคืนข้อผิดพลาดเมื่อเกิด overflow ส่วนf16,f32,f64,f80,f128ก็เป็นชนิด IEEE floating point ตามความยาวบิตนั้น ๆ ความยาวของไบต์ไม่สำคัญ และถ้าเป็นเครื่องไบต์ 12 บิตก็ใช้u12กับi12ได้u8,i32ดีกว่ามากในทุกแง่unsignedผิด แต่เรื่อง การทำให้จำนวนบิตของชนิดพื้นฐานเป็นมาตรฐาน นั้นทำได้ถูกต้องbyte = 8 bits,short = 16,int = 32,long = 64,float = 32 bit IEEE,double = 64 bit IEEEยังมีคนที่ต้องทำงานกับ DSP อยู่: https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
ส่วนตัวกำลังเขียนเอกสารเล่น ๆ สำหรับ คอนโซลแฟนตาซี 12 บิต ที่ยังไม่ได้ทำจริง พร้อมสโลแกนว่า “มีบิตต่อไบต์มากกว่าคู่แข่ง 50%!” และใส่สิ่งประดิษฐ์อย่าง “UTF-12” ไว้ด้วย
ถามมาหลายปีแล้วแต่ยังไม่ได้คำตอบเชิงบวก มีการกล่าวถึงแค่ TI เท่านั้น จึงเพิ่มข้อมูลลงในร่างฉบับอัปเดต: https://isocpp.org/files/papers/D3477R1.html
CHAR_BIT=32ออกจากเวอร์ชันอย่าง C++30 ผมก็คงไม่ร้องไห้ประท้วงสงสัยว่า C++ จะสามารถ เลิกใช้หรือลดความซับซ้อน อะไรบางอย่างได้บ้างไหม
เป็นคำถามจริง ๆ และไม่ได้ติดตามรายละเอียดมากนัก ได้ยินมาว่า
rand()พังและแก้ไม่ได้ แต่เท่าที่รู้ล่าสุดก็ยังไม่ได้ถูกกำหนดให้เลิกใช้ด้วยซ้ำ ข้อเสนอนี้ดูเหมือนเป็นการทดสอบว่า “เราจะเลิกสนับสนุนวิธีแก้ปัญหาที่แทบไม่มีใครเจอจริง ๆ ได้ไหม?”C และ C++ สมัยใหม่ละทิ้งสิ่งนี้และกำหนดให้ต้องเป็น two's complement ในกรณีนี้ ความต่างแบบ “as if” ก็ไม่ได้สำคัญในทางปฏิบัติ และสามารถนำไปใช้กับ
CHAR_BITในลักษณะเดียวกันได้ ดังนั้นจึงมีแบบอย่างของการเปลี่ยนแปลงเช่นนี้อย่างชัดเจนrandถูกกำหนดให้เลิกใช้ พร้อมทั้งมีทางเลือกให้ด้วยนอกจากนี้ยังมี
p2809 Trivial infinite loops are not Undefined Behavior,p1152 Deprecating volatile,p0907 Signed Integers are Two's Complement,p2723 Zero-initialize objects of automatic storage duration,p2186 Removing Garbage Collection Supportดังนั้นการเปลี่ยนแปลงจึงเป็นไปได้exception specification ก็ถูกลบเช่นกัน แต่ก็มีคนที่อยากให้ฟื้นกลับมาเพื่อรองรับ value type exception
auto_ptrก็ถูกลบเพราะการออกแบบที่พัง อย่างไรก็ตาม ในแง่การลดความซับซ้อนก็ไม่ได้ดีขึ้นมากนัก เพราะยังต้องรู้วิธีเก่าอยู่ดีประมาณว่าต้องมีสัญลักษณ์ C++ ใหม่ที่ชี้ถึงไบต์ 8 บิตได้อย่างเสถียรโดยไม่ทำลายความเข้ากันได้ เช่น อาจสร้าง
unsigned byte8,signed byte8แบบ two's complement และbyte8ที่พฤติกรรมเรื่องเครื่องหมายไม่ได้กำหนดไว้ สำหรับนักบัญชีก็เพิ่มunsigned decimal byte8และsigned decimal byte8ที่จำกัดช่วงค่าเป็น 0~10, -10~+10 และสำหรับนักบัญชีที่คิดค่าบายต์ด้วยก็มีcentimal byte8ช่วง 0~100, -100~+100 รวมถึงชนิดที่พอเหมาะพอควรสำหรับฟิลด์ageในฐานข้อมูล และแน่นอนว่าต้องเพิ่มfloat byte8ด้วย—เป็นมุกตลกrand()พังตรงไหน มันสร้างค่าที่ดูเหมือนสุ่มออกมา และนั่นก็คือจุดประสงค์แน่นอนว่ามันไม่ได้สร้างเลขสุ่มที่ปลอดภัยเชิงวิทยาการเข้ารหัส และฟังก์ชันเทียบเท่าในภาษาอื่นก็เหมือนกัน ถ้าต้องการจำนวนเต็มที่คำนวณเร็วและสุ่มพอประมาณ
rand()ก็ทำงานได้ดีพอขอบคุณที่สนใจข้อเสนอนี้ และผมได้ทำร่างฉบับอัปเดตจากฟีดแบ็กที่ได้รับมาจนถึงตอนนี้แล้ว: https://isocpp.org/files/papers/D3477R1.html
โดยเฉพาะประโยคที่ว่า “ปัญหาไม่ใช่ว่ายังมีสถาปัตยกรรมที่ไบต์ไม่ใช่ 8 บิตอยู่ไหม มีอยู่! ปัญหาคือพวกเขาใส่ใจ C++ สมัยใหม่หรือไม่ และ C++ สมัยใหม่ใส่ใจพวกเขาหรือไม่” น่าประทับใจมาก
รู้สึกสองจิตสองใจกับข้อเสนอนี้ ด้านหนึ่งมันก็ถูกอย่างชัดเจน และไม่มีกรณีใช้งานที่มีความหมายสำหรับ
CHAR_BITที่ไม่ใช่ 8แต่อีกด้านหนึ่งก็รู้สึกเหมือนยอมจำนนต่อ โลกทัศน์แบบโลกยุติธรรม ที่คิดว่าโลกควรสมเหตุสมผลและอนุมานได้จากแบบจำลองภายในคอมพิวเตอร์ที่เป็นส่วนตัวและเรียบง่ายเกินไป แนวทางแบบนี้พาไปได้ไกลพอสมควร แต่สุดท้ายก็เป็นทางตัน และท้ายที่สุดต้องยอมรับว่าเราไม่รู้อะไรเลย และสิ่งที่ดีที่สุดคือข้อโต้แย้งเชิงรูปแบบว่าเราได้สร้างโปรแกรมที่ถูกต้องภายใต้เงื่อนไขว่าเอกสารถูกต้อง นี่เป็นการกระโดดทางความคิดครั้งใหญ่ และโดยส่วนตัว ยิ่งอยู่ได้นานโดยไม่ถูกบังคับให้ยอมรับเรื่องนี้ ก็ยิ่งกระโดดข้ามได้ยากในภายหลัง ถึงอย่างนั้น ช่วงนี้ดูเหมือนโปรเจกต์อิเล็กทรอนิกส์เชิงกายภาพจะได้รับความนิยมในหมู่ผู้เริ่มต้น จึงหวังว่าแทนที่จะเป็น “อ่านเอกสารสิ” มาตรฐานใหม่จะกลายเป็น “อ่าน datasheet บ้า ๆ นั่นสิ”
autoconfก็ยังเห็นมันตรวจสอบ จำนวนบิตในไบต์ แล้วเก็บไว้ในconfig.hราวกับว่ามีใครสักคนวางแผนจะทำอะไรจริง ๆ ตามค่านั้นจำเป็นต้องมีวิธีตรวจสอบโค้ดเก่าทั้งหมดในระหว่างกระบวนการคอมไพล์ว่าแมโครนี้ถูกใช้ไปแล้วหรือไม่ การเปลี่ยนแปลงที่ทำให้ของเดิมพังแบบนี้มีความเสี่ยงจะแยกภาษาออกเป็นสาย ๆ ด้วย ความยากในการทดสอบว่า codebase เดิมใช้แมโคร
CHAR_BITหรือไม่ และสามารถอัปเดตไปใช้คอมไพเลอร์ใหม่ได้หรือไม่นั้นก็ยังไม่ชัดเจน ยังมีคำถามอย่างว่าไลบรารีแบบไหนจะถือว่าพัง หรือจะเกิดปัญหาเมื่อโต้ตอบกับโค้ดอื่นที่คอมไพล์โดยใช้CHAR_BITหรือไม่ ผมเห็นด้วยว่ามันขัดสัญชาตญาณ แต่คิดว่าควรสร้างเครื่องมือแปลงก่อน และแสดงให้เห็นว่าปลอดภัยแม้ในกรณีสุดโต่ง แล้วค่อยย้ายไปดีกว่าชอบตรงที่เป็นข้อเสนอที่แทบไม่มีอะไรให้ถกเถียง แต่ก็ เผ็ดร้อน มาก
การบังคับ
int8_t == char == 8 bitsนั้นโอเคอย่างยิ่ง แต่ไม่มั่นใจเรื่องการเผยแพร่ความเข้าใจผิดว่า ไบต์คือ 8 บิตไบต์ 8 บิตเรียกว่า octet ขณะเดียวกันตั้งแต่ C++17
byteก็เป็นเหมือน “นามแฝง” ของcharอยู่แล้ว: https://en.cppreference.com/w/cpp/types/byteตลอด 45 ปีหลังจากนั้น ผมไม่เคยเห็น “byte” ถูกใช้ในความหมายอื่นเลย ดังนั้นถ้ามีคำนิยามของ “byte” ที่ไม่ใช่ 8 บิต ก็ต้องขอแหล่งอ้างอิงด้วย
นี่ไม่ใช่ข้อความเชิงพรรณนา แต่เป็น ข้อความเชิงบรรทัดฐาน
int8 == signed charstd::cout << (int8_t)32 << std::endl;ควรพิมพ์ 32 ออกมาอย่างชัดเจนแม้จะไม่เกี่ยวกับ C++ แต่ผมค่อนข้างชอบไอเดียไมโครคอมพิวเตอร์ย้อนยุคที่ใช้ ไบต์ 6 บิต อยู่เหมือนกัน ประมาณว่า 24 บิตเป็นหนึ่ง word
ไมโครคอมพิวเตอร์มักจัดการกับออบเจ็กต์จำนวนน้อย และชอบใช้อาร์เรย์มากกว่าพอยน์เตอร์ จึงช่วยประหยัดหน่วยความจำได้ VGA ใช้ 6 บิตต่อสี, สามารถสร้างตัวอักษรที่อ่านได้ด้วยเมทริกซ์ 6x4 บิต, ภาษา LISP หรือ Forth พื้นฐานก็ใส่ไว้ในชุดอักขระ 6 บิตได้ และ System/360 เดิมก็ใช้ที่อยู่แค่ 24 บิต หน่วยความจำ 12MiB ที่ระบุตำแหน่งได้อิสระทีละหน่วย 6 บิต ก็น่าจะพอสำหรับทุกคนแล้ว ถ้าไม่พอ ก็ขยาย FAT-12 เป็น FAT-24 ได้อย่างเป็นธรรมชาติ หรือใช้พอยน์เตอร์ 48 บิตที่มีประโยชน์พอ ๆ กับพอยน์เตอร์ 64 บิตก็ได้