Câu hỏi tưởng chừng đơn giản về việc gì xảy ra khi bạn yêu cầu máy tính cấp phát không byte bộ nhớ nào đã gây ra những cuộc tranh luận sôi nổi trong cộng đồng lập trình viên. Câu đố kỹ thuật này tiết lộ những vấn đề sâu xa hơn về tiêu chuẩn phần mềm, tính tương thích hệ thống, và những thách thức trong việc viết mã có thể chạy trên nhiều nền tảng.
Khi các lập trình viên gọi malloc(0) - một hàm yêu cầu cấp phát không byte bộ nhớ - các hệ thống máy tính khác nhau phản hồi theo những cách hoàn toàn khác biệt. Một số trả về giá trị null đặc biệt cho biết không có gì được cấp phát, trong khi những hệ thống khác cung cấp một địa chỉ bộ nhớ duy nhất trỏ tới... thực ra là không có gì cả. Sự không nhất quán này đã gây ra những lỗi phần mềm thực tế và tiếp tục làm frustrate các nhà phát triển hàng thập kỷ sau khi hành vi này được tiêu chuẩn hóa lần đầu.
Các tiêu chuẩn cho phép hai hành vi khác nhau
Gốc rễ của vấn đề này nằm ở cách các tiêu chuẩn lập trình được viết. Cả tiêu chuẩn ngôn ngữ lập trình C và các đặc tả POSIX đều cố ý cho phép malloc(0) hoạt động theo hai cách: hoặc trả về null hoặc trả về một con trỏ duy nhất không thể được sử dụng để truy cập bộ nhớ. Quyết định này được đưa ra để tránh phá vỡ các triển khai phần mềm hiện có đã hoạt động khác nhau.
Cuộc thảo luận trong cộng đồng tiết lộ rằng hành vi kép này tạo ra những vấn đề thực tế. Khi cùng một đoạn mã chạy trên các hệ thống khác nhau, nó có thể hoạt động hoàn hảo trên một máy nhưng lại thất bại thảm hại trên máy khác. Một nhà phát triển đã chia sẻ kinh nghiệm của họ trong việc duy trì phần mềm bị lỗi cụ thể trên các hệ thống AIX vì malloc(0) trả về null trong khi mã lệnh mong đợi một con trỏ hợp lệ.
Các tùy chọn hành vi malloc(0) trong chuẩn C:
- Tùy chọn 1: Trả về con trỏ NULL (cho biết không có phân bổ bộ nhớ)
- Tùy chọn 2: Trả về con trỏ không phải NULL duy nhất (không thể được tham chiếu)
- Yêu cầu: Nếu không phải NULL, mỗi lần gọi phải trả về một địa chỉ duy nhất
- Tiêu chuẩn: Được định nghĩa trong các đặc tả C89, C99 và POSIX
Yêu cầu con trỏ duy nhất tạo ra sự phức tạp
Khi các hệ thống chọn trả về một con trỏ hợp lệ cho việc cấp phát không byte, chúng phải đảm bảo mỗi lần gọi tạo ra một địa chỉ duy nhất. Yêu cầu này, được quy định bởi các tiêu chuẩn, buộc các bộ cấp phát bộ nhớ phải theo dõi những con trỏ đặc biệt này mặc dù chúng trỏ tới không có bộ nhớ nào có thể sử dụng.
Mỗi việc cấp phát như vậy sẽ tạo ra một con trỏ tới một đối tượng tách biệt khỏi bất kỳ đối tượng nào khác.
Yêu cầu tính duy nhất này đã dẫn đến những triển khai sáng tạo nhưng có vấn đề. Một số hệ thống nhúng cũ trả về giá trị hằng số -1 cho tất cả các lần gọi malloc(0), điều này vi phạm tiêu chuẩn nhưng phục vụ nhu cầu cụ thể của chúng. Tuy nhiên, những cách tiếp cận như vậy có thể phá vỡ mã sử dụng con trỏ làm khóa trong các cấu trúc dữ liệu, nơi các địa chỉ trùng lặp gây ra những xung đột bất ngờ.
Tác động thực tế đến phát triển phần mềm
Sự không nhất quán của malloc(0) ảnh hưởng nhiều hơn chỉ những cuộc thảo luận học thuật. Các nhà phát triển làm việc với container tổng quát, thư viện quản lý bộ nhớ, và phần mềm đa nền tảng phải tính đến sự biến đổi này. Một số hàm trong thư viện chuẩn C , như memcpy, từ chối làm việc với con trỏ null ngay cả khi sao chép không byte, khiến sự phân biệt giữa null và con trỏ hợp lệ trở nên quan trọng về mặt thực tế.
Cách tiếp cận an toàn nhất, theo sự đồng thuận của cộng đồng, là tránh hoàn toàn malloc(0). Các nhà phát triển có thể yêu cầu malloc(1) khi họ cần một địa chỉ duy nhất, hoặc bọc malloc trong các hàm tùy chỉnh cung cấp hành vi nhất quán trên tất cả các hệ thống. Mặc dù điều này thêm mã phụ, nó loại bỏ một nguồn gây ra vấn đề tính di động đáng kể.
Các Giải Pháp Thay Thế Phổ Biến Của Developer:
- Sử dụng
malloc(1)
thay vìmalloc(0)
khi cần địa chỉ duy nhất - Bọc malloc trong các hàm tùy chỉnh để có hành vi nhất quán
- Thêm size+1 vào các yêu cầu phân bổ để tránh trường hợp đặc biệt zero-byte
- Triển khai theo dõi bộ nhớ đặc thù cho ứng dụng đối với các phân bổ có kích thước bằng không
Kết luận
Cuộc tranh luận về malloc(0) minh họa cách những quyết định kỹ thuật tưởng chừng nhỏ có thể có hậu quả lâu dài. Điều bắt đầu như một nỗ lực để phù hợp với các triển khai hiện có khác nhau đã tạo ra một nguồn gây nhầm lẫn và lỗi vĩnh viễn. Đối với các nhà phát triển hiện đại, bài học rõ ràng là: khi các tiêu chuẩn cho phép nhiều hành vi, con đường an toàn nhất là tránh phụ thuộc vào bất kỳ hành vi cụ thể nào.
Ghi chú kỹ thuật: malloc là một hàm cấp phát bộ nhớ trong lập trình C dành riêng không gian cho các chương trình lưu trữ dữ liệu. POSIX là một tập hợp các tiêu chuẩn định nghĩa cách các hệ điều hành nên hoạt động.
Tham khảo: You're using a suspiciously old browser