Câu trả lời:
Câu trả lời đơn giản nhất, giả sử bạn không bận tâm đến sự mơ hồ và các biến thể trong định dạng giữa các nền tảng khác nhau, là %p
ký hiệu chuẩn .
Tiêu chuẩn C99 (ISO / IEC 9899: 1999) cho biết trong §7.19.6.1 8:
p
Đối số sẽ là một con trỏ tớivoid
. Giá trị của con trỏ được chuyển đổi thành một chuỗi các ký tự in, theo cách được xác định theo cách thực hiện.
(Trong C11 - ISO / IEC 9899: 2011 - thông tin nằm trong §7,21.6.1 8.)
Trên một số nền tảng, nó sẽ bao gồm hàng đầu 0x
và trên các nền tảng khác, và các chữ cái có thể là chữ thường hoặc chữ in hoa và tiêu chuẩn C thậm chí không xác định rằng nó sẽ là đầu ra thập lục phân mặc dù tôi biết không thực hiện ở nơi không.
Nó là hơi mở để tranh luận về việc bạn nên chuyển đổi rõ ràng các con trỏ với một (void *)
diễn viên. Nó rõ ràng, thường là tốt (vì vậy đó là những gì tôi làm) và tiêu chuẩn nói 'đối số sẽ là một con trỏ tới void
'. Trên hầu hết các máy, bạn sẽ thoát khỏi việc bỏ qua một diễn viên rõ ràng. Tuy nhiên, sẽ là vấn đề trên một máy trong đó biểu diễn bit của một char *
địa chỉ cho một vị trí bộ nhớ nhất định khác với địa chỉ ' bất cứ thứ gì khác con trỏ ' cho cùng một vị trí bộ nhớ. Đây sẽ là một địa chỉ từ, thay vì địa chỉ byte, máy. Những máy như vậy không phổ biến (có thể không có sẵn) những ngày này, nhưng máy đầu tiên tôi làm việc sau khi học đại học là một máy như vậy (ICL Perq).
Nếu bạn không hài lòng với hành vi được xác định theo triển khai %p
, thì hãy sử dụng C99 <inttypes.h>
và uintptr_t
thay vào đó:
printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);
Điều này cho phép bạn tinh chỉnh đại diện cho phù hợp với chính mình. Tôi đã chọn để có các chữ số hex trong chữ in hoa sao cho số đó có cùng chiều cao và độ nhúng đặc trưng khi bắt đầu 0xA1B2CDEF
xuất hiện, do đó không giống như số 0xa1b2cdef
nào tăng và giảm dọc theo số. Sự lựa chọn của bạn mặc dù, trong giới hạn rất rộng. Các (uintptr_t)
diễn viên được một cách rõ ràng khuyến cáo của GCC khi nó có thể đọc các chuỗi định dạng tại thời gian biên dịch. Tôi nghĩ rằng việc yêu cầu diễn viên là chính xác, mặc dù tôi chắc chắn rằng có một số người sẽ bỏ qua cảnh báo và bỏ đi phần lớn thời gian.
Kerrek hỏi trong các ý kiến:
Tôi hơi bối rối về các chương trình khuyến mãi tiêu chuẩn và các đối số biến động. Có phải tất cả các con trỏ được thăng cấp tiêu chuẩn vào void *? Mặt khác, nếu
int*
, giả sử, hai byte vàvoid*
là 4 byte, thì rõ ràng đó là một lỗi để đọc bốn byte từ đối số, phải không?
Tôi đã ảo tưởng rằng tiêu chuẩn C nói rằng tất cả các con trỏ đối tượng phải có cùng kích thước, void *
và int *
không thể có kích thước khác nhau. Tuy nhiên, những gì tôi nghĩ là phần có liên quan của tiêu chuẩn C99 không quá rõ ràng (mặc dù tôi không biết về việc triển khai trong đó những gì tôi đề xuất là đúng là thực sự sai):
§6.2.5 Các loại
¶26 Một con trỏ tới void sẽ có cùng các yêu cầu biểu diễn và căn chỉnh như một con trỏ tới một kiểu ký tự. 39) Tương tự, các con trỏ tới các phiên bản đủ điều kiện hoặc không đủ tiêu chuẩn của các loại tương thích sẽ có cùng các yêu cầu về đại diện và căn chỉnh. Tất cả các con trỏ đến các loại cấu trúc sẽ có cùng các yêu cầu đại diện và căn chỉnh như nhau. Tất cả các con trỏ tới các loại kết hợp sẽ có cùng các yêu cầu đại diện và căn chỉnh như nhau. Con trỏ đến các loại khác không cần phải có cùng yêu cầu đại diện hoặc căn chỉnh.
39) Các yêu cầu đại diện và căn chỉnh giống nhau có nghĩa là ngụ ý khả năng thay thế lẫn nhau như các đối số cho các hàm, trả về các giá trị từ các hàm và các thành viên của các hiệp hội.
(C11 nói chính xác như vậy trong phần §6.2.5, ¶28 và chú thích 48.)
Vì vậy, tất cả các con trỏ tới các cấu trúc phải có cùng kích thước với nhau và phải chia sẻ cùng một yêu cầu căn chỉnh, mặc dù các cấu trúc mà con trỏ trỏ tới có thể có các yêu cầu căn chỉnh khác nhau. Tương tự cho các công đoàn. Con trỏ ký tự và con trỏ void phải có cùng kích thước và yêu cầu căn chỉnh. Con trỏ tới các biến thể trên int
(ý nghĩa unsigned int
và signed int
) phải có cùng kích thước và yêu cầu căn chỉnh với nhau; tương tự cho các loại khác. Nhưng tiêu chuẩn C không chính thức nói điều đó sizeof(int *) == sizeof(void *)
. Oh tốt, SO là tốt để làm cho bạn kiểm tra các giả định của bạn.
Tiêu chuẩn C dứt khoát không yêu cầu các con trỏ hàm có cùng kích thước với các con trỏ đối tượng. Điều đó là cần thiết để không phá vỡ các mô hình bộ nhớ khác nhau trên các hệ thống giống như DOS. Ở đó bạn có thể có con trỏ dữ liệu 16 bit nhưng con trỏ hàm 32 bit hoặc ngược lại. Đây là lý do tại sao tiêu chuẩn C không bắt buộc các con trỏ hàm có thể được chuyển đổi thành các con trỏ đối tượng và ngược lại.
May mắn thay (đối với các lập trình viên nhắm mục tiêu POSIX), POSIX bước vào vi phạm và bắt buộc các con trỏ hàm và con trỏ dữ liệu có cùng kích thước:
§2.12.3 Các loại con trỏ
Tất cả các loại con trỏ hàm sẽ có cùng biểu diễn với con trỏ kiểu để void. Chuyển đổi một con trỏ hàm thành
void *
sẽ không làm thay đổi biểu diễn. Mộtvoid *
giá trị kết quả từ một chuyển đổi như vậy có thể được chuyển đổi trở lại loại con trỏ hàm ban đầu, sử dụng một biểu thức rõ ràng, mà không mất thông tin.Lưu ý: Tiêu chuẩn ISO C không yêu cầu điều này, nhưng nó được yêu cầu cho sự phù hợp POSIX.
Vì vậy, có vẻ như các phôi rõ ràng void *
được khuyến khích mạnh mẽ để có độ tin cậy tối đa trong mã khi chuyển một con trỏ đến một hàm matrixdic như printf()
. Trên các hệ thống POSIX, việc truyền con trỏ hàm thành con trỏ trống để in là an toàn. Trên các hệ thống khác, không nhất thiết phải an toàn để làm điều đó, cũng không nhất thiết phải an toàn để vượt qua các con trỏ khác hơn là void *
không có diễn viên.
dlsym()
thay vào đó chuyển hầu hết các yêu cầu sang chức năng. Một ngày nào đó tôi sẽ viết lên sự thay đổi ... nhưng "một ngày" không phải là "hôm nay".
void *
? Hmm tôi thấy bình luận của bạn ở đây . Vì chỉ cần chuyển đổi một watt (con trỏ hàm thành void *
), nên nó hoạt động sau đó?
void *
và trở lại mà không mất thông tin. Về mặt thực tế, có rất ít máy trong đó kích thước của một con trỏ hàm không giống với kích thước của một con trỏ đối tượng. Tôi không nghĩ rằng tiêu chuẩn cung cấp một phương pháp in một con trỏ hàm trên các máy mà việc chuyển đổi tha có vấn đề.
p
là chỉ định chuyển đổi để in con trỏ. Dùng cái này.
int a = 42;
printf("%p\n", (void *) &a);
Hãy nhớ rằng bỏ qua diễn viên là hành vi không xác định và việc in bằng p
chỉ định chuyển đổi được thực hiện theo cách xác định thực hiện.
Sử dụng %p
, cho "con trỏ" và không sử dụng bất cứ thứ gì khác *. Bạn không được đảm bảo theo tiêu chuẩn rằng bạn được phép đối xử với một con trỏ như bất kỳ loại số nguyên cụ thể nào, vì vậy bạn thực sự có hành vi không xác định với các định dạng tích phân. (Ví dụ: %u
mong đợi một unsigned int
, nhưng nếu void*
có yêu cầu căn chỉnh hoặc kích thước khác vớiunsigned int
thì sao?)
*) [Xem câu trả lời hay của Jonathan!] Ngoài ra %p
, bạn có thể sử dụng các macro dành riêng cho con trỏ từ<inttypes.h>
, được thêm vào trong C99.
Tất cả các con trỏ đối tượng đều được chuyển đổi hoàn toàn thành void*
C, nhưng để vượt qua con trỏ dưới dạng đối số biến đổi, bạn phải sử dụng nó một cách rõ ràng (vì các con trỏ đối tượng tùy ý chỉ có thể chuyển đổi , nhưng không giống với con trỏ void):
printf("x lives at %p.\n", (void*)&x);
void *
(mặc dù về printf()
mặt kỹ thuật bạn cần diễn viên rõ ràng, vì đó là một hàm biến đổi). Con trỏ hàm không nhất thiết phải chuyển đổi thành void *
.
void *
và quay lại con trỏ hàm mà không mất; tuy nhiên, may mắn thay, POSIX không yêu cầu rõ ràng điều đó (lưu ý rằng nó không phải là một phần của tiêu chuẩn C). Vì vậy, trong thực tế, bạn có thể nhận được ngay với nó (chuyển đổi void (*function)(void)
để void *
và trở lại void (*function)(void)
), nhưng đúng nó không phải là bắt buộc theo tiêu chuẩn C.
%u
!
%u
và %lu
sai trên tất cả các máy , không phải một số máy. Đặc điểm kỹ thuật của printf
rất rõ ràng là khi loại được truyền không khớp với loại được yêu cầu bởi bộ xác định định dạng, hành vi không được xác định. Cho dù kích thước của các loại khớp (có thể đúng hay sai, tùy thuộc vào máy) là không liên quan; đó là loại phải phù hợp, và chúng sẽ không bao giờ.
Thay thế cho các câu trả lời (rất tốt) khác, bạn có thể chuyển sang uintptr_t
hoặc intptr_t
(từ stdint.h
/ inttypes.h
) và sử dụng các chỉ định chuyển đổi số nguyên tương ứng. Điều này sẽ cho phép linh hoạt hơn trong cách con trỏ được định dạng, nhưng nói đúng ra việc triển khai là không bắt buộc để cung cấp các typedefs này.
#include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); }
có phải là hành vi không xác định để in địa chỉ của biến bằng cách sử dụng %u
định dạng định dạng? Địa chỉ của biến trong hầu hết các trường hợp là tích cực vì vậy tôi có thể sử dụng %u
thay vì %p
?
%u
là định dạng cho unsigned int
loại và không thể được sử dụng với đối số con trỏ tới printf
.
Bạn có thể sử dụng %x
hay %X
hay %p
; tất cả đều đúng
%x
, địa chỉ được cung cấp dưới dạng chữ thường, ví dụ:a3bfbc4
%X
, địa chỉ được cung cấp dưới dạng chữ hoa, ví dụ:A3BFBC4
Cả hai điều này đều đúng.
Nếu bạn sử dụng %x
hoặc %X
đang xem xét sáu vị trí cho địa chỉ và nếu bạn sử dụng %p
, hãy xem xét tám vị trí cho địa chỉ. Ví dụ:
void*
? Mặt khác, nếuint*
, giả sử, hai byte vàvoid*
là 4 byte, thì rõ ràng đó là một lỗi để đọc bốn byte từ đối số, phải không?