ASCII, Unicode và UTF-8: đếm byte hay đếm ký tự?
Tiên quyết
- Module A0: Bức tranh toàn cảnh: Đang tải
- Binary, hex và BCD: cùng bit, khác cách biểu diễn: Đang tải
Sau bài này bạn sẽ
- Phân biệt giá trị số, mã ASCII và Unicode code point
- Tính số byte của các chuỗi UTF-8 cụ thể
- Nhận ra giới hạn của so byte và độ dài string
Một ký tự nhìn thấy chưa chắc là một byte
Ký tự “A” thường chiếm một byte trong UTF-8. Chữ “ộ” dạng dựng sẵn chiếm ba byte; một biểu tượng như 😀 chiếm bốn byte. Nếu giao diện ghi “độ dài”, cần hỏi đang đếm byte, code point hay ký tự người đọc nhìn thấy.
Các bộ đếm độ dài dùng để cấp phát buffer hoặc truyền dữ liệu phải theo đơn vị của giao thức. Không lấy số ký tự trên màn hình làm số byte cần gửi.
ASCII là một miền mã nhỏ
ASCII định nghĩa 128 mã từ 0 tới 127: chữ Latin cơ bản, chữ số, dấu và các ký tự điều khiển. Ví dụ A có mã hex 41, ký tự 0 có mã 30; code 0 là NUL, không phải ký tự số 0.
| Đối tượng | Byte hex trong ASCII/UTF-8 | Ý nghĩa |
|---|---|---|
| Ký tự A | 41 |
Chữ hoa |
| Ký tự 0 | 30 |
Chữ số viết ra |
| Ký tự xuống dòng LF | 0A |
Ký tự điều khiển |
| Số nguyên không dấu 10 trong một byte | 0A |
Dữ liệu số, chỉ thành LF khi diễn giải như văn bản |
Một byte có 256 mẫu, nhưng ASCII cơ bản chỉ dùng nửa miền ấy. Không có một “extended ASCII” duy nhất cho mọi hệ thống; các code page khác nhau có thể dùng nửa còn lại khác nhau.
Code point khác cách lưu trong bộ nhớ
Unicode gán code point cho các đối tượng ký tự. Cách viết U+0041 chỉ code point của A; nó chưa quy định mọi hệ thống phải dùng bao nhiêu byte để lưu.
UTF-8 và UTF-16 là các cách mã hóa chuỗi code point thành code unit. UTF-8 dùng các code unit một byte; một Unicode scalar hợp lệ dùng từ một tới bốn byte. Miền surrogate không phải scalar để mã hóa trực tiếp như một ký tự UTF-8 hợp lệ.
Font và cách dựng chữ quyết định hình hiển thị. Một hình có thể ghép từ nhiều code point; một code point cũng không bảo đảm luôn có glyph trên máy người dùng.
| Encoding | Độ rộng một code unit | Số code unit cho một scalar hợp lệ |
|---|---|---|
| UTF-8 | 8 bit | 1 tới 4 |
| UTF-16 | 16 bit | 1 hoặc 2; scalar ngoài BMP cần surrogate pair |
| UTF-32 | 32 bit | 1 |
Scalar là code point trong miền Unicode trừ các code point surrogate. Một surrogate đơn lẻ không phải một scalar để đưa thẳng vào UTF-8. Khi lưu UTF-16/UTF-32 thành byte, còn phải chốt byte order; số code unit không tự xác định thứ tự byte trên đường truyền.
Glyph là hình do font dựng; grapheme cluster là cụm dùng để gần với một ký tự người dùng cảm nhận. Một cụm có thể gồm chữ nền với nhiều dấu. Không dùng độ dài code unit để mặc định số glyph hoặc grapheme cluster.
Đọc vài chuỗi byte cụ thể
| Văn bản/cách tạo | Code point | Byte UTF-8 | Số byte |
|---|---|---|---|
| A | U+0041 |
41 |
1 |
| ộ dựng sẵn | U+1ED9 |
E1 BB 99 |
3 |
| o + dấu chấm dưới + dấu mũ | U+006F U+0323 U+0302 |
6F CC A3 CC 82 |
5 |
| 😀 | U+1F600 |
F0 9F 98 80 |
4 |
Hai hàng tiếng Việt có thể được dựng giống nhau nhưng byte không bằng nhau. Hàng ghép gồm ba code point; đếm byte, code point và cụm ký tự người dùng cảm nhận sẽ cho kết quả khác.
Trong UTF-8 hợp lệ, ASCII giữ nguyên byte từ 00 tới 7F. Byte bắt đầu và các byte tiếp nối của chuỗi nhiều byte có vai trò khác nhau; cắt buffer tùy ý ở một ranh giới byte có thể làm đứt một chuỗi.
Bằng nhau theo tiêu chí nào?
So byte là tiêu chí rõ cho payload nhị phân, hash hoặc một số định danh đã được chuẩn hóa theo hợp đồng. Với văn bản người dùng, hai cách ghép code point có thể cần so theo tiêu chí khác.
Normalization như NFC là một bước xử lý Unicode có quy tắc, không phải thao tác xóa dấu hay chuyển mọi văn bản về ASCII. Cần chốt yêu cầu rồi dùng thư viện phù hợp; không thay các chuỗi byte trong giao thức mà chưa biết có được phép chuẩn hóa không.
Ví dụ của bài chỉ chỉ ra hai chuỗi cụ thể khác byte. Nó không triển khai normalization, so sánh ngôn ngữ, đếm grapheme hay xác nhận toàn bộ chuỗi Unicode đều hợp lệ.
String trong ví dụ HDL là chuỗi byte
Ví dụ dùng literal UTF-8 trong source rồi kiểm độ dài và từng byte mà toolchain giữ lại. Kết quả cho “ộ” dựng sẵn là 3, không phải 1.
Khi test UART gửi văn bản, scoreboard phải biết dữ liệu là byte raw hay UTF-8. Một FIFO rộng tám bit chỉ giữ các byte; nó không tự biết byte nào là đầu hay cuối một ký tự.
Chủ đề UART chỉ là bối cảnh về đơn vị dữ liệu; bài này không triển khai hoặc xác minh protocol. Khi cần xử lý NUL, byte không hợp lệ hoặc I/O binary, phải đọc quy tắc API cụ thể; ví dụ không có các trường hợp ấy.
Các nhầm lẫn gây hỏng dữ liệu
- Đồng nhất mã ký tự số 0 với byte có giá trị 0.
- Dùng số hình nhìn thấy để tính dung lượng buffer.
- Cho rằng hai chuỗi trông giống nhau thì byte phải giống.
- Cắt một chuỗi nhiều byte ở vị trí bất kỳ rồi mong văn bản vẫn hợp lệ.
- Cho rằng đổi font là đổi encoding.
- Dùng string API để xử lý dữ liệu nhị phân mà chưa kiểm cách API xử lý NUL.
Khi debug dữ liệu đi qua giao diện, ghi đơn vị ở từng biên: số byte, số code point hoặc số mục dữ liệu. Một log hex thường giúp thấy chỗ sai rõ hơn ảnh chụp chữ.
Đếm đúng đơn vị
- Vì sao ký tự 7 có byte
37chứ không phải07trong ASCII? - Ghép A và ộ dựng sẵn cần bao nhiêu byte UTF-8? Có bao nhiêu code point?
- Hai chuỗi tiếng Việt trong bảng có cùng số code point không? Cùng số byte không?
- Một buffer chỉ còn hai byte có giữ được trọn chữ ộ dựng sẵn không?
- Khi so hai tên người dùng, cần làm rõ những tiêu chí gì trước khi chọn so byte hay normalization?
Kiểm tra
Bằng chứng kiểm chứng
Expected tính trước chạy: ENCODING CHECK strings=4 ascii=1 viet=3 decomposed=5 emoji=4 bytes=13 byte_equal=0 time_ns=4.
Bốn literal là A, ộ dựng sẵn, chuỗi o + U+0323 + U+0302 và emoji. Tham chiếu chứa 13 byte hex viết trước chạy; kiểm từng byte cùng độ dài và sự khác byte của hai chuỗi tiếng Việt. Parser/simulation đạt trên toolchain đã khóa, không claim mọi simulator giữ source encoding giống nhau.
Lượt đầu phát hiện lỗi tạo literal ghép thành DEộ: độ dài vẫn là 5 nhưng byte đầu sai, nên phép kiểm từng byte đã bắt được. Sửa literal về chuỗi code point dự định rồi chạy lại, giữ nguyên 13 byte tham chiếu.
Chưa kiểm UTF-8 không hợp lệ, NUL, normalization hay grapheme segmentation. Quy tắc string SystemVerilog và Unicode đầy đủ vẫn chờ đối chiếu chuẩn tương ứng.
Nguồn và mức độ đối chiếu
- Character Encoding Systems — bản chụp resources.zip, What is ASCII Code?; ASCII Code Examples; ASCII Character Ranges; Limitations of ASCII; What is Unicode?; Unicode Encoding Formats; UTF-8: The Universal Standard; Common Beginner Mistakes. Trích dẫn; nguồn đã đọc các mục liên quan.
- Thí nghiệm nguyên bản A1.encoding, Four literal UTF-8 byte sequences, thirteen expected octets and distinct composed/decomposed bytes. Trích dẫn; nguồn đã đọc toàn văn.
- IEEE 1800-2023 SystemVerilog LRM, String literal source encoding, len/getc byte access and string comparison — chưa đối chiếu LRM. Chưa đối chiếu với văn bản nguồn (chưa mở).
- RFC 3629: UTF-8 — chờ đối chiếu, Valid UTF-8 scalar encoding and byte sequences — chưa đối chiếu RFC. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Unicode UAX 15: Normalization Forms — chờ đối chiếu, Canonical equivalence and NFC normalization — chưa đối chiếu UAX 15. Chưa đối chiếu với văn bản nguồn (chưa mở).
- IEEE 1800-2023 SystemVerilog LRM, String len/getc byte operations and source text handling — chưa đối chiếu LRM. Chưa đối chiếu với văn bản nguồn (chưa mở).