Tiêu chuẩn C không yêu cầu con trỏ rỗng phải ở địa chỉ của máy là 0. TUY NHIÊN, việc truyền một 0
hằng số thành một giá trị con trỏ phải dẫn đến một NULL
con trỏ (§6.3.2.3 / 3) và việc đánh giá con trỏ null dưới dạng boolean phải là false. Đây có thể là một chút vụng về nếu bạn thực sự làm muốn có một địa chỉ không, và NULL
không phải là địa chỉ không.
Tuy nhiên, với các sửa đổi (nặng) đối với trình biên dịch và thư viện tiêu chuẩn, không thể không NULL
được biểu diễn bằng một mẫu bit thay thế trong khi vẫn tuân thủ nghiêm ngặt thư viện tiêu chuẩn. Tuy nhiên, chỉ đơn giản là thay đổi định nghĩa của chính nó là không đủ NULL
, vì sau đó NULL
sẽ đánh giá thành true.
Cụ thể, bạn cần phải:
- Sắp xếp các số không theo nghĩa đen trong các phép gán cho con trỏ (hoặc phôi tới con trỏ) được chuyển đổi thành một số giá trị ma thuật khác chẳng hạn như
-1
.
- Sắp xếp các bài kiểm tra bằng nhau giữa con trỏ và một số nguyên không đổi
0
để kiểm tra giá trị ma thuật thay thế (§6.5.9 / 6)
- Sắp xếp cho tất cả các ngữ cảnh trong đó kiểu con trỏ được đánh giá là boolean để kiểm tra sự bình đẳng với giá trị ma thuật thay vì kiểm tra bằng 0. Điều này tuân theo ngữ nghĩa kiểm tra bình đẳng, nhưng trình biên dịch có thể triển khai nó theo cách khác nhau trong nội bộ. Xem §6.5.13 / 3, §6.5.14 / 3, §6.5.15 / 4, §6.5.3.3 / 5, §6.8.4.1 / 2, §6.8.5 / 4
- Như caf đã chỉ ra, hãy cập nhật ngữ nghĩa để khởi tạo đối tượng tĩnh (§6.7.8 / 10) và khởi tạo phức hợp một phần (§6.7.8 / 21) để phản ánh biểu diễn con trỏ null mới.
- Tạo một cách thay thế để truy cập số không địa chỉ thực.
Có một số việc bạn không phải xử lý. Ví dụ:
int x = 0;
void *p = (void*)x;
Sau đó, p
KHÔNG được đảm bảo là một con trỏ null. Chỉ các phép gán hằng số mới cần được xử lý (đây là một cách tiếp cận tốt để truy cập địa chỉ thực số 0). Tương tự như vậy:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Cũng thế:
void *p = NULL;
int x = (int)p;
x
không được đảm bảo là 0
.
Nói tóm lại, điều kiện này rõ ràng đã được xem xét bởi ủy ban ngôn ngữ C, và những cân nhắc được thực hiện đối với những người sẽ chọn đại diện thay thế cho NULL. Tất cả những gì bạn phải làm bây giờ là thực hiện các thay đổi lớn đối với trình biên dịch của mình và chào bạn trước khi hoàn thành :)
Lưu ý thêm, có thể thực hiện những thay đổi này với giai đoạn chuyển đổi mã nguồn trước khi trình biên dịch thích hợp. Có nghĩa là, thay vì dòng thông thường của bộ tiền xử lý -> trình biên dịch -> trình hợp dịch -> trình liên kết, bạn sẽ thêm một bộ tiền xử lý -> chuyển đổi NULL -> trình biên dịch -> trình hợp dịch -> trình liên kết. Sau đó, bạn có thể thực hiện các phép biến đổi như:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Điều này sẽ yêu cầu trình phân tích cú pháp C đầy đủ, cũng như trình phân tích cú pháp kiểu và phân tích typedef và khai báo biến để xác định số nhận dạng nào tương ứng với con trỏ. Tuy nhiên, bằng cách làm này, bạn có thể tránh phải thực hiện các thay đổi đối với các phần tạo mã của trình biên dịch thích hợp. clang có thể hữu ích cho việc thực hiện điều này - tôi hiểu rằng nó được thiết kế với các phép biến đổi như thế này. Tất nhiên, bạn vẫn có thể cần thực hiện các thay đổi đối với thư viện chuẩn.
mprotect
là bảo mật. Hoặc, nếu nền tảng không có ASLR hoặc tương tự, các địa chỉ nằm ngoài bộ nhớ vật lý của nền tảng. Chúc may mắn.