Trên các triển khai với một mô hình bộ nhớ phẳng (về cơ bản là tất cả mọi thứ), việc chuyển sang uintptr_t
sẽ chỉ hoạt động.
(Nhưng thấy so sánh con trỏ nên được ký kết hoặc unsigned trong 64-bit x86? Để thảo luận về việc liệu bạn nên đối xử với con trỏ như đã ký kết hay không, bao gồm cả các vấn đề hình thành con trỏ bên ngoài của đối tượng đó là UB trong C.)
Nhưng các hệ thống với các mô hình bộ nhớ không phẳng tồn tại và suy nghĩ về chúng có thể giúp giải thích tình hình hiện tại, như C ++ có các thông số kỹ thuật khác nhau <
so với std::less
.
Một phần của điểm <
trên các con trỏ để phân tách các đối tượng là UB trong C (hoặc ít nhất là không xác định trong một số phiên bản C ++) là cho phép các máy lạ, bao gồm các mô hình bộ nhớ không phẳng.
Một ví dụ nổi tiếng là chế độ thực x86-16 trong đó con trỏ là phân đoạn: offset, tạo thành địa chỉ tuyến tính 20 bit thông qua (segment << 4) + offset
. Cùng một địa chỉ tuyến tính có thể được biểu diễn bằng nhiều kết hợp seg: off khác nhau.
C ++ std::less
trên các con trỏ trên các ISA kỳ lạ có thể cần phải đắt tiền , ví dụ: "bình thường hóa" một phân đoạn: bù vào x86-16 để có offset <= 15. Tuy nhiên, không có cách di động nào để thực hiện điều này. Thao tác cần thiết để chuẩn hóa một uintptr_t
(hoặc biểu diễn đối tượng của đối tượng con trỏ) là cụ thể thực hiện.
Nhưng ngay cả trên các hệ thống mà C ++ std::less
phải đắt tiền, <
cũng không phải như vậy. Ví dụ: giả sử mô hình bộ nhớ "lớn" trong đó một đối tượng nằm gọn trong một phân đoạn, <
chỉ có thể so sánh phần bù và thậm chí không bận tâm với phần phân đoạn. (Các con trỏ bên trong cùng một đối tượng sẽ có cùng phân khúc và nếu không thì UB trong C. C ++ 17 đã thay đổi thành "không xác định", điều này vẫn có thể cho phép bỏ qua chuẩn hóa và chỉ so sánh các độ lệch.) Điều này giả sử tất cả các con trỏ với bất kỳ phần nào của một đối tượng luôn sử dụng cùng một seg
giá trị, không bao giờ bình thường hóa. Đây là những gì bạn mong muốn ABI yêu cầu cho một mô hình bộ nhớ "lớn" trái ngược với mô hình bộ nhớ "khổng lồ". (Xem thảo luận trong ý kiến ).
(Ví dụ, một mô hình bộ nhớ có thể có kích thước đối tượng tối đa là 64kiB, nhưng tổng không gian địa chỉ tối đa lớn hơn nhiều có nhiều chỗ cho nhiều đối tượng có kích thước tối đa như vậy. ISO C cho phép triển khai có giới hạn về kích thước đối tượng thấp hơn giá trị tối đa (không dấu) size_t
có thể biểu thị , SIZE_MAX
. Ví dụ, ngay cả trên các hệ thống mô hình bộ nhớ phẳng, GNU C giới hạn kích thước đối tượng tối đa để PTRDIFF_MAX
tính toán kích thước có thể bỏ qua tràn tràn đã ký.) Xem câu trả lời này và thảo luận trong các nhận xét.
Nếu bạn muốn cho phép các đối tượng lớn hơn một phân đoạn, bạn cần một mô hình bộ nhớ "khổng lồ" phải lo lắng về việc tràn phần bù của con trỏ khi thực hiện p++
để lặp qua một mảng hoặc khi thực hiện lập chỉ mục / số học con trỏ. Điều này dẫn đến mã chậm hơn ở mọi nơi, nhưng có lẽ sẽ có nghĩa là p < q
sẽ xảy ra để hoạt động cho các con trỏ tới các đối tượng khác nhau, bởi vì việc triển khai nhắm mục tiêu mô hình bộ nhớ "khổng lồ" thường sẽ chọn để giữ tất cả các con trỏ được bình thường hóa mọi lúc. Xem những gì gần, xa và con trỏ lớn? - một số trình biên dịch C thực cho chế độ thực x86 đã có tùy chọn biên dịch cho mô hình "khổng lồ" trong đó tất cả các con trỏ được mặc định là "khổng lồ" trừ khi được khai báo khác.
Phân đoạn chế độ thực x86 không phải là mô hình bộ nhớ không phẳng duy nhất có thể , nó chỉ là một ví dụ cụ thể hữu ích để minh họa cách nó được xử lý bởi các triển khai C / C ++. Trong thực tế, việc triển khai mở rộng ISO C với khái niệm far
so với near
con trỏ, cho phép các lập trình viên lựa chọn khi họ có thể thoát khỏi chỉ với việc lưu trữ / chuyển xung quanh phần bù 16 bit, liên quan đến một số phân đoạn dữ liệu phổ biến.
Nhưng việc triển khai ISO C thuần túy sẽ phải chọn giữa một mô hình bộ nhớ nhỏ (mọi thứ trừ mã trong cùng 64kiB với con trỏ 16 bit) hoặc lớn hoặc lớn với tất cả các con trỏ là 32 bit. Một số vòng có thể tối ưu hóa bằng cách chỉ tăng phần bù, nhưng các đối tượng con trỏ không thể được tối ưu hóa để nhỏ hơn.
Nếu bạn biết thao tác ma thuật là gì đối với bất kỳ triển khai cụ thể nào, bạn có thể thực hiện nó trong C thuần túy . Vấn đề là các hệ thống khác nhau sử dụng địa chỉ khác nhau và các chi tiết không được tham số hóa bởi bất kỳ macro di động nào.
Hoặc có thể không: nó có thể liên quan đến việc tìm kiếm thứ gì đó từ bảng phân đoạn đặc biệt hoặc thứ gì đó, ví dụ như chế độ được bảo vệ x86 thay vì chế độ thực trong đó phần phân đoạn của địa chỉ là một chỉ mục, không phải là giá trị được dịch chuyển trái. Bạn có thể thiết lập các phân đoạn chồng lấp một phần trong chế độ được bảo vệ và các phần chọn địa chỉ của các địa chỉ thậm chí sẽ không được sắp xếp theo thứ tự như các địa chỉ cơ sở phân khúc tương ứng. Nhận địa chỉ tuyến tính từ một con trỏ seg: off trong chế độ được bảo vệ x86 có thể liên quan đến một cuộc gọi hệ thống, nếu GDT và / hoặc LDT không được ánh xạ vào các trang có thể đọc được trong quy trình của bạn.
(Tất nhiên các hệ điều hành chính cho x86 sử dụng mô hình bộ nhớ phẳng để cơ sở phân đoạn luôn là 0 (ngoại trừ lưu trữ cục bộ sử dụng fs
hoặc gs
phân đoạn) và chỉ sử dụng phần "bù" 32 bit hoặc 64 bit làm con trỏ .)
Bạn có thể thêm mã theo cách thủ công cho các nền tảng cụ thể khác nhau, ví dụ như mặc định giả định hoặc #ifdef
một cái gì đó để phát hiện chế độ thực x86 và chia uintptr_t
thành hai nửa 16 bit để seg -= off>>4; off &= 0xf;
sau đó kết hợp các phần đó lại thành một số 32 bit.