Nó là uintptr_t
gì và nó có thể được sử dụng để làm gì?
Nó là uintptr_t
gì và nó có thể được sử dụng để làm gì?
Câu trả lời:
uintptr_t
là một kiểu số nguyên không dấu có khả năng lưu trữ một con trỏ dữ liệu. Điều đó thường có nghĩa là nó có cùng kích thước với một con trỏ.
Nó được định nghĩa tùy ý trong C ++ 11 và các tiêu chuẩn mới hơn.
Một lý do phổ biến để muốn một loại số nguyên có thể chứa loại con trỏ của kiến trúc là để thực hiện các hoạt động cụ thể về số nguyên trên một con trỏ hoặc để che khuất loại của một con trỏ bằng cách cung cấp cho nó như là một "số nguyên".
Chỉnh sửa: Lưu ý rằng Steve Jessop có một số chi tiết bổ sung rất thú vị (mà tôi sẽ không ăn cắp) trong một câu trả lời khác ở đây cho bạn các kiểu mô phạm :)
size_t
chỉ cần đủ để giữ kích thước thứ của đối tượng lớn nhất và có thể nhỏ hơn con trỏ. Điều này sẽ được mong đợi trên các kiến trúc được phân đoạn như 8086 (16 bit size_t
, nhưng 32 bit void*
)
ptrdiff_t
. uintptr_t
không có nghĩa cho điều đó.
unsigned int
thường không đủ lớn. Nhưng nó có thể đủ lớn. Loại này tồn tại đặc biệt để loại bỏ tất cả "giả định" .
Điều đầu tiên, tại thời điểm câu hỏi được hỏi, uintptr_t
không có trong C ++. Nó ở C99, trong<stdint.h>
, như một loại tùy chọn. Nhiều trình biên dịch C ++ 03 cung cấp tệp đó. Nó cũng có trong C ++ 11, trong <cstdint>
đó một lần nữa là tùy chọn và đề cập đến C99 cho định nghĩa.
Trong C99, nó được định nghĩa là "một kiểu số nguyên không dấu với thuộc tính mà mọi con trỏ hợp lệ thành void có thể được chuyển đổi thành loại này, sau đó được chuyển đổi trở lại thành con trỏ thành void và kết quả sẽ so sánh bằng với con trỏ ban đầu".
Hãy hiểu điều này có nghĩa là những gì nó nói. Nó không nói bất cứ điều gì về kích thước.
uintptr_t
có thể có cùng kích thước với a void*
. Nó có thể lớn hơn. Nó có thể hình dung nhỏ hơn, mặc dù cách triển khai C ++ như vậy là sai lầm. Ví dụ: trên một số nền tảng giả thuyết void*
có 32 bit, nhưng chỉ có 24 bit không gian địa chỉ ảo được sử dụng, bạn có thể có 24 bit uintptr_t
thỏa mãn yêu cầu. Tôi không biết tại sao một triển khai sẽ làm điều đó, nhưng tiêu chuẩn cho phép nó.
void*
. Tuy nhiên, nó ảnh hưởng đến các hướng có thể có trong tương lai, đặc biệt là nếu bạn có thể muốn thay đổi để sử dụng thứ gì đó thực sự chỉ là một số nguyên, không phải là một con trỏ được chuyển đổi.
typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;
. Thay vào đó : callback.dataPtr = (void*)val
. Mặt khác, bạn tất nhiên có được void*
và phải đưa nó trở lại int
.
Đó là một kiểu số nguyên không dấu chính xác kích thước của một con trỏ. Bất cứ khi nào bạn cần làm điều gì đó bất thường bằng một con trỏ - ví dụ như đảo ngược tất cả các bit (đừng hỏi tại sao) bạn chuyển nó sang uintptr_t
và thao tác nó như một số nguyên thông thường, sau đó bỏ lại.
void*
giá trị con trỏ thành uintptr_t
và trở lại một lần nữa mang lại một void*
giá trị so sánh với con trỏ ban đầu. uintptr_t
thường có cùng kích thước với void*
, nhưng điều đó không được đảm bảo, cũng không có gì đảm bảo rằng các bit của giá trị được chuyển đổi có bất kỳ ý nghĩa cụ thể nào. Và không có gì đảm bảo rằng nó có thể giữ giá trị con trỏ chuyển đổi thành hàm mà không mất thông tin. Cuối cùng, nó không được đảm bảo để tồn tại.
Đã có nhiều câu trả lời hay cho phần "kiểu dữ liệu uintptr_t" là gì. Tôi sẽ cố gắng giải quyết "nó có thể được sử dụng để làm gì?" một phần trong bài này.
Chủ yếu cho các hoạt động bitwise trên con trỏ. Hãy nhớ rằng trong C ++, người ta không thể thực hiện các thao tác bitwise trên các con trỏ. Vì lý do xem Tại sao bạn không thể thực hiện các thao tác bitwise trên con trỏ trong C và có cách nào khác không?
Do đó, để thực hiện các thao tác bitwise trên các con trỏ, người ta sẽ cần truyền con trỏ để gõ unitpr_t và sau đó thực hiện các thao tác bitwise.
Dưới đây là ví dụ về một hàm mà tôi vừa viết để loại trừ bitwise hoặc 2 con trỏ để lưu trữ trong danh sách được liên kết XOR để chúng ta có thể di chuyển theo cả hai hướng như danh sách được liên kết đôi nhưng không bị phạt khi lưu 2 con trỏ trong mỗi nút .
template <typename T>
T* xor_ptrs(T* t1, T* t2)
{
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
}
Có nguy cơ nhận được một huy hiệu Necromancer khác, tôi muốn thêm một cách sử dụng rất tốt cho uintptr_t (hoặc thậm chí intptr_t) và đó là viết mã nhúng có thể kiểm tra được. Tôi viết phần lớn mã nhúng được nhắm mục tiêu vào các bộ xử lý khác nhau và hiện tại. Những cái này có chiều rộng xe buýt riêng khác nhau và tenilica thực sự là một kiến trúc của Harvard với các bus dữ liệu và mã riêng biệt có thể có chiều rộng khác nhau. Tôi sử dụng một kiểu phát triển hướng thử nghiệm cho phần lớn mã của mình, điều đó có nghĩa là tôi thực hiện các thử nghiệm đơn vị cho tất cả các đơn vị mã tôi viết. Kiểm tra đơn vị trên phần cứng mục tiêu thực tế là một rắc rối vì vậy tôi thường viết mọi thứ trên PC dựa trên Intel trong Windows hoặc Linux bằng Ceedling và GCC. Điều đó đang được nói, rất nhiều mã nhúng liên quan đến thao tác xoay vòng và xử lý địa chỉ bit. Hầu hết các máy Intel của tôi là 64 bit. Vì vậy, nếu bạn định kiểm tra mã thao tác địa chỉ, bạn cần một đối tượng tổng quát để làm toán. Do đó, uintptr_t cung cấp cho bạn một cách gỡ lỗi mã độc lập của máy trước khi bạn thử triển khai vào phần cứng đích. Một vấn đề khác là đối với một số máy hoặc thậm chí các mô hình bộ nhớ trên một số trình biên dịch, con trỏ hàm và con trỏ dữ liệu có độ rộng khác nhau. Trên các máy đó, trình biên dịch thậm chí có thể không cho phép truyền giữa hai lớp, nhưng uintptr_t sẽ có thể giữ một trong hai.