1 điểm bởi GN⁺ 2024-10-19 | 1 bình luận | Chia sẻ qua WhatsApp
  • P3477R0 là đề xuất thay đổi tiêu chuẩn nhằm không còn để kích thước byte trong C++ phụ thuộc vào giá trị do triển khai định nghĩa là CHAR_BIT, mà cố định thành chính xác 8 bit
  • Các nền tảng hiện đại trên thực tế đã hội tụ quanh giả định byte 8 bit, và GCC, LLVM, MSVC cũng đặt giá trị mặc định hoặc macro liên quan là 8
  • POSIX từ POSIX.1-2001 đã yêu cầu CHAR_BIT == 8, và xu hướng chấp nhận biểu diễn số nguyên bù hai trong C++20 và C23 cũng đi theo cùng hướng
  • Việc hỗ trợ byte không phải 8 bit để lại những ngoại lệ nhỏ trên toàn bộ ngôn ngữ, thư viện và toolchain, đồng thời tạo ra gánh nặng trường hợp biên không phù hợp với cách dùng C++ hiện đại trong thực tế
  • Vẫn có các kiến trúc ngoại lệ như PDP-10 hay một số DSP, nhưng điểm tranh luận cốt lõi là liệu tiêu chuẩn C++ mới có nên tiếp tục duy trì sự phức tạp chỉ vì các mục tiêu đó hay không

Mục tiêu thay đổi của P3477R0

  • C++ kế thừa macro CHAR_BIT từ C, và hiện tại giá trị này là một giá trị do triển khai định nghĩa biểu thị số bit trong một byte
  • P3477R0 đề xuất sửa tiêu chuẩn C++ để chính thức yêu cầu byte phải là 8 bit
  • Trong giai đoạn đầu của ngành điện toán, sự linh hoạt cho phép nhiều kích thước byte khác nhau từng có ý nghĩa, nhưng nhận định ở đây là phần cứng hiện đại gần như đã hội tụ hoàn toàn về giả định byte 8 bit

Tình trạng hiện tại của compiler và nền tảng

  • Các compiler lớn trên thực tế đã coi byte 8 bit là mặc định
    • GCC dùng giá trị mặc định 8, và không có trường hợp nào trong các target upstream thay đổi giá trị mặc định này
    • LLVM đặt __CHAR_BIT__8
    • MSVC định nghĩa CHAR_BIT8
  • Trong các trường hợp hỗ trợ trước đây của GCC, dsp16xx đã bị loại bỏ vào năm 2004, còn 1750a bị loại bỏ vào năm 2002
  • Khi tìm kiếm trên web có thể thấy một số port ngoài GCC mà BITS_PER_UNIT không phải 8, nhưng đề xuất cho rằng chúng dường như không còn liên quan tới C++ hiện đại

Xu hướng từ POSIX và biểu diễn số nguyên

  • POSIX từ POSIX.1-2001 đã yêu cầu các điều kiện sau
    • byte phải chính xác 8 bit
    • CHAR_BIT phải là 8
    • SCHAR_MAX127, SCHAR_MIN-128, UCHAR_MAX255
  • POSIX giải thích rằng việc bổ sung int8_t kéo theo yêu cầu char 8 bit và số học bù hai
  • Sau P0907r4, C++20 chỉ còn hỗ trợ cách lưu trữ bù hai, và C23 cũng đi theo hướng tương tự
  • Các hệ điều hành hiện được nêu làm ví dụ tuân thủ POSIX gồm AIX, HP-UX, INTEGRITY, macOS, OpenServer, UnixWare, VxWorks, vz/OS

Chi phí do byte không phải 8 bit để lại

  • Phần mềm dành cho byte 8 bit và phần mềm dành cho byte không phải 8 bit không tương thích với nhau; mã C/C++ cho các mục tiêu không dùng byte 8 bit trên thực tế gần như là một phương ngữ không tương thích của C và C++
  • Hỗ trợ kiến trúc byte không phải 8 bit để lại sự phức tạp nhỏ nhưng không cần thiết ở nhiều phần của ngôn ngữ và thư viện
  • Compiler và toolchain phải tiếp tục gánh các trường hợp biên không phản ánh cách sử dụng hiện đại
  • Lập trình viên mới rất dễ bị bối rối bởi những đặc tính mang tính ngoại lai như vậy của C++
  • Một số lập trình viên có kinh nghiệm bị cho là phải tốn thời gian cho “tính di động” nhằm phục vụ những nền tảng gần như không còn tồn tại

Các kiến trúc ngoại lệ và phương án dung hòa

  • Đề xuất cũng thừa nhận rằng các bộ xử lý không dùng byte 8 bit vẫn còn tồn tại
  • Câu hỏi cốt lõi là liệu các bộ xử lý đó có còn liên quan đến C++ hiện đại hay không, và liệu người dùng các bộ xử lý đó có thực sự dùng các phiên bản C++ mới hay không
  • Một phương án dung hòa được nêu ra là yêu cầu CHAR_BIT % 8 == 0, nhưng điều này chỉ có ý nghĩa nếu ủy ban muốn tiếp tục hỗ trợ các DSP hoặc bộ xử lý khác mà CHAR_BIT không phải 8 nhưng là bội số của 8
  • PDP-10 được nêu là đối tượng cần bàn, trong khi PDP-11 được phân biệt rõ là dùng byte 8 bit
  • Một số DSP xử lý word 24 bit hoặc 32 bit như thể đó là “byte”, và các kiến trúc như vậy từng hợp lý trong thời kỳ kích thước word rất đa dạng và khái niệm byte chưa được chuẩn hóa

Hướng sửa đổi câu chữ trong tiêu chuẩn

  • Có ý định sửa định nghĩa byte trong intro.memory để nêu rõ byte, đơn vị lưu trữ cơ bản của mô hình bộ nhớ C++, là 8 bit
  • Trong climits, đề xuất thay đổi câu chữ theo hướng đặt CHAR_BIT8
  • Trong cstdint, vì byte là 8 bit nên các kiểu số nguyên có độ rộng xác định như int8_t, uint8_t và các macro liên quan sẽ không còn là tùy chọn nữa
  • Với các kiểu dùng _N_, những trường hợp N không phải 8, 16, 32, 64 vẫn tiếp tục là tùy chọn
  • Đề xuất cũng bao gồm việc loại bỏ 4 điều khoản mandates liên quan tới CHAR_BIT == 8 trong localization

Quan hệ với tiêu chuẩn C

  • Đề xuất xem xét liệu C++ có còn nên tiếp tục gắn với các kiến trúc byte không phải 8 bit hay không
  • Ủy ban C có thể đi tới kết luận khác đối với ngôn ngữ C
  • Việc hai ủy ban đi cùng hướng là điều lý tưởng, nhưng đề xuất này đặt vấn đề theo hướng nhóm liên lạc WG14 và SG22 cung cấp thông tin cho WG21

1 bình luận

 
GN⁺ 2024-10-19
Các ý kiến trên Hacker News
  • Trong loạt bài “Chẳng phải ta có thể thừa nhận máy tính thực tế đều hoạt động như vậy sao?” của JF, đã có một phần nói rằng số nguyên có dấu là bù hai: "Signed Integers are Two’s Complement"

  • Khi thực tập năm 1986, tôi viết mã C trên BBN C/70 dùng byte 10 bit; đó là một trải nghiệm kinh khủng, và ngay từ đầu việc một cỗ máy như vậy tồn tại đã là một tai nạn mang tầm vũ trụ theo nghĩa tiêu cực

    • Tôi từng viết mã trên DECSYSTEM-20, nhưng trình biên dịch C không được hỗ trợ chính thức
      Nó dùng word 36 bitbyte 7 bit; khi nhét các byte vào word thì vẫn còn dư bit. Thế mà tôi lại được giao đọc băng chứa dữ liệu nhị phân ở định dạng 8 bit, nên đúng là một mớ hỗn độn
    • Tôi từng lập trình CPU Intel Intellivision, một cỗ máy kỳ quặc dùng decl 10 bit, và nó không đủ mạnh để chạy C
    • Tôi từng làm việc trên máy dùng byte 9 bitlệnh 81 bit, cũng như máy dùng byte 6 bit, nhưng cả hai đều không có trình biên dịch C
    • Ngày nay trên FPGA, số học 10 bit thực ra không hiếm và còn được dùng trong các sản phẩm tương đối hiện đại
      Nhưng C 10 bit thì lại là câu chuyện khác
  • D đã tiến một bước lớn khi quy định: byte là 8 bit, short là 16 bit, int là 32 bit, long là 64 bit, số học là bù hai, số thực dấu phẩy động là dấu phẩy động IEEE
    Điều đó tiết kiệm được lượng thời gian khổng lồ từng bị dùng để cố trừu tượng hóa những thứ như vậy rồi rốt cuộc vẫn sai, và khiến hàng triệu người thở phào. Bộ ký tự cũng là Unicode, không phải EBCDIC hay RADIX-50

    • Zig còn tốt hơn: kích thước được nêu rõ như u8/i8, u16/i16, u32/i32, u64/i64, và số học cũng được chọn một cách tường minh
      Tràn số của + là hành vi sai nên sẽ dừng trong debug và releasesafe; +%wrap bù hai, +| là số học bão hòa. @addWithOverflow() trả về một tuple gồm kiểu gốc và u1, còn std.math.add() trả lỗi khi tràn. f16, f32, f64, f80, f128 cũng lần lượt là các kiểu dấu phẩy động IEEE có độ dài bit tương ứng. Byte dài bao nhiêu không quan trọng; nếu là máy có byte 12 bit thì cứ dùng u12i12
    • Nói D đã có bước tiến lớn là phóng đại. Các tên kiểu có kích thước tường minh như u8, i32 tốt hơn nhiều về mọi mặt
    • Nếu “byte là 8 bit” thì bit lớn cỡ nào?
    • Có vẻ hơi tự khen khi chính ông Bright, tác giả ngôn ngữ D, tự nói vậy nhỉ :)
    • Java cũng làm đúng ở phần này. unsigned thì xử lý sai, nhưng chuẩn hóa số bit của các kiểu nguyên thủy thì làm đúng
      byte = 8 bits, short = 16, int = 32, long = 64, float = 32 bit IEEE, double = 64 bit IEEE
  • Vẫn còn những người phải làm việc với DSP: https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
    Cá nhân tôi đang đùa vui bằng cách viết tài liệu cho một console giả tưởng 12 bit chưa được triển khai, với khẩu hiệu “nhiều hơn 50% bit trên mỗi byte so với đối thủ!”, và còn nhét vào các phát minh như “UTF-12”

    • Tôi vẫn đang cố xác định những mục tiêu nào còn liên quan, và liệu họ có nhắm tới C++ hiện đại hay có kế hoạch làm vậy không
      Tôi đã hỏi suốt mấy năm nhưng chưa nhận được câu trả lời tích cực nào; thứ được nhắc đến chỉ là TI, nên tôi đã thêm thông tin vào bản thảo cập nhật: https://isocpp.org/files/papers/D3477R1.html
    • Có lẽ cứ nhắm tới C++23 trở xuống là được. Tôi có vài máy SHARC, nhưng nếu ủy ban bỏ hỗ trợ CHAR_BIT=32 trong một phiên bản như C++30 thì tôi cũng sẽ không khóc lóc phản đối đâu
    • Chẳng phải PDP-8 dùng byte 12 bit sao?
  • Tôi tự hỏi liệu C++ có thể loại bỏ hoặc đơn giản hóa thứ gì đó không
    Đây là câu hỏi thật lòng và tôi không theo dõi sát lắm. Tôi nghe nói rand() đã hỏng và không thể sửa được, nhưng lần cuối tôi biết thì nó vẫn còn chưa được lên kế hoạch loại bỏ. Đề xuất này trông như một phép thử rằng “liệu có thể bỏ cả việc hỗ trợ giải pháp cho một vấn đề mà theo nghĩa đen là chẳng ai gặp phải không?”

    • Số nguyên có dấu không nhất thiết phải là bù 2; cả ba biểu diễn gồm dấu-trị, bù 1 và bù 2 đều từng hợp lệ
      C và C++ hiện đại đã bỏ điều đó và yêu cầu bù 2. Ở đây, khác biệt “as if” thực tế cũng không quan trọng, và có thể áp dụng cùng kiểu cho CHAR_BIT, nên rõ ràng đã có tiền lệ cho thay đổi như vậy
    • Đã loại bỏ trigraph và cũng đã đưa rand vào diện dự kiến loại bỏ, đồng thời cung cấp phương án thay thế
      Ngoài ra còn có 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. Vì vậy việc thay đổi là có thể
    • GC API của C++11 đã bị loại bỏ trong C++23, và điều đó cũng dễ hiểu vì nó không được thiết kế xét đến nhu cầu của các biến thể lớn có hỗ trợ GC như Unreal C++ và C++/CLI
      Đặc tả ngoại lệ cũng đã bị loại bỏ, dù có những người muốn khôi phục nó vì ngoại lệ kiểu giá trị. auto_ptr cũng bị loại bỏ vì thiết kế hỏng. Tuy nhiên, xét về mặt đơn giản hóa thì cũng không tốt hơn mấy, vì vẫn phải biết cách cũ
    • Nghe như một lời châm biếm rằng đừng phá vỡ sự hoàn hảo, mà hãy tích lũy thêm nhiều sự hoàn hảo nữa
      Kiểu như cần một ký hiệu C++ mới để chỉ byte 8-bit một cách ổn định mà không phá vỡ tương thích. Ví dụ có thể tạo unsigned byte8, signed byte8 bù 2, và cả byte8 với hành vi dấu không được định nghĩa. Rồi đùa rằng hãy thêm unsigned decimal byte8signed decimal byte8 giới hạn miền giá trị 0~10, -10~+10 cho kế toán; centimal byte8 0~100, -100~+100 cho kế toán tính đến cả chi phí từng byte; một kiểu đủ dùng đại khái cho trường age trong cơ sở dữ liệu; và tất nhiên cả float byte8 nữa
    • Tôi không rõ vì sao rand() lại hỏng. Nó tạo ra các giá trị trông có vẻ ngẫu nhiên, và đó là mục đích của nó
      Việc nó không tạo số ngẫu nhiên an toàn về mật mã là đương nhiên, và các hàm tương đương trong ngôn ngữ khác cũng vậy. Nếu cần một số nguyên khá ngẫu nhiên được tính nhanh thì rand() hoạt động đủ tốt
  • Cảm ơn vì đã quan tâm đến đề xuất; dựa trên phản hồi nhận được đến nay, tôi đã tạo bản nháp cập nhật: https://isocpp.org/files/papers/D3477R1.html

    • Tôi thích văn phong châm biếm của bản đề xuất
      Đặc biệt câu “vấn đề không phải là liệu còn kiến trúc nào mà byte không phải 8 bit hay không. Có! Vấn đề là liệu chúng có quan tâm đến C++ hiện đại không, và C++ hiện đại có quan tâm đến chúng không” rất ấn tượng
  • Tôi có cảm xúc lẫn lộn về đề xuất này. Một mặt, nó rõ ràng là đúng, và không có ứng dụng có ý nghĩa nào cho việc CHAR_BIT khác 8
    Mặt khác, nó cũng có cảm giác như đang khuất phục trước một thế giới quan công bằng, rằng thế giới phải hợp lý và có thể suy luận chỉ dựa trên mô hình cá nhân, quá đơn giản hóa về bên trong máy tính. Cách tiếp cận này đưa ta đi khá xa, nhưng cuối cùng là ngõ cụt; đến cuối cùng phải thừa nhận rằng ta chẳng biết gì cả, và điều tốt nhất là một lập luận hình thức rằng chương trình ta xây dựng là đúng, với điều kiện tài liệu là đúng. Đây là một bước nhảy nhận thức lớn, và với cá nhân tôi, càng lâu không bị buộc phải thừa nhận điều đó thì về sau càng khó vượt qua hơn. Dù vậy, có vẻ các dự án điện tử vật lý đang phổ biến trong giới mới học ngày nay, nên tôi mong “hãy đọc tài liệu” sẽ được thay bằng chuẩn mới là “đọc cái datasheet chết tiệt đi”

    • Thế nhưng mỗi lần chạy script autoconf, tôi vẫn thấy nó kiểm tra số bit trong một byte và lưu vào config.h. Như thể thật sự có ai đó định hành động dựa trên giá trị đó vậy
    • Một ngôn ngữ được dùng rộng rãi rốt cuộc sẽ gặp vấn đề COBOL. Phần lớn trường hợp thì ổn, nhưng trong một hệ thống nào đó bị buộc cập nhật, đột nhiên hệ thống điều khiển giao thông có thể dừng hoặc máy bay có thể rơi
      Cần có cách kiểm tra toàn bộ mã cũ trong quá trình biên dịch để xem macro này đã được dùng chưa. Những thay đổi phá vỡ như vậy cũng có nguy cơ làm ngôn ngữ bị phân nhánh. Việc kiểm thử xem codebase hiện có có dùng macro CHAR_BIT hay không, và có thể cập nhật sang compiler mới hay không, cũng không rõ khó đến mức nào. Cũng nảy sinh các câu hỏi như thư viện nào sẽ bị xem là hỏng, hay liệu có vấn đề khi tương tác với mã khác được biên dịch có dùng CHAR_BIT không. Tôi đồng ý là nó trái trực giác, nhưng tốt hơn là nên làm công cụ chuyển đổi trước, chứng minh rằng nó an toàn cả trong các trường hợp cực đoan, rồi hẵng chuyển đổi
  • Tôi thích vì đây là một đề xuất không thể tranh cãi mà vẫn cực kỳ cay

  • Việc buộc int8_t == char == 8 bits thì hoàn toàn ổn, nhưng tôi không chắc về việc lan truyền hiểu lầm rằng byte là 8 bit
    Byte 8-bit được gọi là octet. Đồng thời, từ C++17, byte vốn đã là một thứ giống như “bí danh” của char: https://en.cppreference.com/w/cpp/types/byte

    • Tôi bắt đầu tiếp xúc với máy tính 45 năm trước, và ngay khi đó “byte” đã được định nghĩa là lượng 8 bit
      Trong 45 năm sau đó, tôi chưa từng thấy “byte” được dùng theo nghĩa khác, nên nếu có định nghĩa “byte” không phải 8 bit thì cần nguồn dẫn
    • Các RFC về mạng ngay từ đầu đã luôn dùng cách diễn đạt octet
    • Không, byte là 8 bit
      Đây không phải là phát biểu mô tả mà là phát biểu quy phạm
    • Cá nhân tôi không thích int8 == signed char
      std::cout << (int8_t)32 << std::endl; hiển nhiên phải in ra 32
  • Không liên quan đến C++, nhưng tôi khá thích ý tưởng về một microcomputer retro dùng byte 6 bit. Kiểu như 24 bit sẽ trở thành một word
    Microcomputer thường xử lý số lượng đối tượng ít và chuộng mảng hơn con trỏ, nên có thể tiết kiệm bộ nhớ. VGA từng dùng 6 bit cho mỗi màu, có thể tạo bảng chữ cái đọc được bằng ma trận bit 6x4, các ngôn ngữ LISP hoặc Forth cơ bản cũng có thể nhét vào bảng chữ cái 6 bit, và System/360 ban đầu cũng chỉ dùng địa chỉ 24 bit. 12MiB bộ nhớ với đơn vị 6 bit có thể định địa chỉ độc lập hẳn là đủ cho bất kỳ ai. Nếu thiếu thì có thể mở rộng FAT-12 một cách tự nhiên thành FAT-24, hoặc dùng con trỏ 48 bit hữu dụng chẳng kém gì con trỏ 64 bit

    • Hoặc cứ dùng byte 8 bitword 3 byte là được. Dù sao vẫn là 24 bit