Định dạng đúng định dạng để in con trỏ hoặc địa chỉ?


192

Tôi nên sử dụng định dạng định dạng nào để in địa chỉ của biến? Tôi bối rối giữa rất nhiều dưới đây.

% u - số nguyên không dấu

% x - giá trị thập lục phân

% p - con trỏ trống

Đó sẽ là định dạng tối ưu để in một địa chỉ?

Câu trả lời:


239

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à %pký 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ới void. 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 0xvà 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>uintptr_tthay 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 0xA1B2CDEFxuất hiện, do đó không giống như số 0xa1b2cdefnà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 *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 intsigned 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ột void *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.


3
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 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?
Kerrek SB

Lưu ý rằng bản cập nhật cho POSIX (POSIX 2013) đã xóa phần 2.12.3, 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".
Jonathan Leffler

Có câu trả lời này cũng áp dụng cho con trỏ đến các chức năng? Họ có thể được chuyển đổi thành 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 đó?
chux - Phục hồi Monica

@chux: Nghiêm túc, câu trả lời là 'không', nhưng trong thực tế, câu trả lời là 'có'. Tiêu chuẩn C không đảm bảo rằng các con trỏ hàm có thể được chuyển đổi thành một 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 đề.
Jonathan Leffler

"Và trở lại mà không mất thông tin" không liên quan đến in ấn. cái đó có giúp ích không?
chux - Phục hồi Monica

50

plà 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 pchỉ định chuyển đổi được thực hiện theo cách xác định thực hiện.


2
Xin lỗi, tại sao bỏ qua các diễn viên là "hành vi không xác định"? Liệu vấn đề này là địa chỉ của biến nào, nếu tất cả những gì bạn cần là địa chỉ, không phải giá trị?
valdo

9
@valdo vì C nói nó (C99, 7.19.6.1p8) "p Đối số sẽ là một con trỏ để trống."
ouah

12
@valdo: Không nhất thiết là tất cả các con trỏ đều có cùng kích thước / đại diện.
phê

32

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ụ: %umong đợ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);

2
Tất cả các con trỏ đối tượng đều có thể chuyển đổi thành 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 *.
phê

@caf: Ồ, tôi không biết về các đối số dao động - đã sửa! Cảm ơn!
Kerrek SB

2
Tiêu chuẩn C không yêu cầu các con trỏ hàm có thể chuyển đổi 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.
Jonathan Leffler

2
Jonathan và R.: Điều này rất thú vị, nhưng tôi khá chắc chắn rằng chúng tôi không cố in các con trỏ hàm ở đây, vì vậy có lẽ đây không phải là nơi thích hợp để thảo luận về vấn đề này. Tôi muốn thấy một số hỗ trợ ở đây để tôi khăng khăng không sử dụng %u!
Kerrek SB

2
%u%lusai 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 printfrấ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ờ.
R .. GitHub DỪNG GIÚP ICE

9

Thay thế cho các câu trả lời (rất tốt) khác, bạn có thể chuyển sang uintptr_thoặ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.


xem xét #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 %uthay vì %p?
Kẻ hủy diệt

1
@Destructor: Không, %ulà định dạng cho unsigned intloại và không thể được sử dụng với đối số con trỏ tới printf.
R .. GitHub DỪNG GIÚP ICE

-1

Bạn có thể sử dụng %xhay %Xhay %p; tất cả đều đúng

  • Nếu bạn sử dụng %x, địa chỉ được cung cấp dưới dạng chữ thường, ví dụ:a3bfbc4
  • Nếu bạn sử dụng %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 %xhoặ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ụ:


1
Chào mừng đến với SO. Xin vui lòng dành chút thời gian để xem xét các câu trả lời khác, họ đang giải thích rõ ràng một số chi tiết bạn đang xem.
AntoineL
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.