ตัวระบุรูปแบบที่ถูกต้องเพื่อพิมพ์ตัวชี้หรือที่อยู่?


194

ฉันควรใช้ตัวระบุรูปแบบใดเพื่อพิมพ์ที่อยู่ของตัวแปร ฉันสับสนระหว่างล็อตด้านล่าง

% u - จำนวนเต็มไม่ได้ลงนาม

% x - ค่าเลขฐานสิบหก

% p - ตัวชี้โมฆะ

รูปแบบใดที่เหมาะสมที่สุดในการพิมพ์ที่อยู่

คำตอบ:


240

คำตอบที่ง่ายที่สุดสมมติว่าคุณไม่คำนึงถึงความหลากหลายและรูปแบบในรูปแบบระหว่างแพลตฟอร์มที่แตกต่างกันคือ%pสัญลักษณ์มาตรฐาน

มาตรฐาน C99 (ISO / IEC 9899: 1999) ระบุไว้ใน§7.19.6.1¶8:

pvoidอาร์กิวเมนต์ที่จะเป็นตัวชี้ไปยัง ค่าของตัวชี้ถูกแปลงเป็นลำดับของอักขระการพิมพ์ในลักษณะที่กำหนดการนำไปใช้งาน

(ใน C11 - ISO / IEC 9899: 2011 - ข้อมูลอยู่ใน§7.21.6.1¶8)

ในบางแพลตฟอร์มนั้นจะรวมถึงผู้นำ0xและอื่น ๆ ซึ่งจะไม่มีและตัวอักษรอาจเป็นตัวพิมพ์เล็กหรือตัวใหญ่และมาตรฐาน C ไม่ได้กำหนดว่ามันจะเป็นผลลัพธ์เลขฐานสิบหกแม้ว่าฉันจะรู้ ไม่มีการติดตั้งใช้งาน

มันค่อนข้างเปิดให้อภิปรายว่าคุณควรแปลงพอยน์เตอร์ด้วยการ(void *)ร่าย มันเป็นการชัดเจนซึ่งโดยปกติจะดี (ดังนั้นจึงเป็นสิ่งที่ฉันทำ) และมาตรฐานบอกว่า 'ข้อโต้แย้งจะเป็นตัวชี้ไปvoid' ในเครื่องส่วนใหญ่คุณจะไม่ต้องสนใจกับนักแสดงที่ชัดเจน อย่างไรก็ตามมันจะสำคัญกับเครื่องที่การแสดงบิตของที่char *อยู่สำหรับตำแหน่งหน่วยความจำที่กำหนดนั้นแตกต่างจากที่อยู่ ' สิ่งอื่นสำหรับตัวชี้ ' สำหรับตำแหน่งหน่วยความจำเดียวกัน นี่จะเป็นคำที่ได้รับการแก้ไขแทนที่จะเป็นแบบไบต์ เครื่องเหล่านี้ไม่ธรรมดา (อาจไม่มี) วันนี้ แต่เครื่องแรกที่ฉันทำงานหลังจากมหาวิทยาลัยเป็นหนึ่ง (ICL Perq)

ถ้าคุณไม่ได้มีความสุขกับพฤติกรรมการดำเนินงานที่กำหนดไว้%pแล้วใช้ C99 <inttypes.h>และuintptr_tแทน:

printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);

วิธีนี้ช่วยให้คุณปรับการแสดงที่เหมาะกับตัวคุณเองได้ ฉันเลือกที่จะมีเลขฐานสิบหกในตัวพิมพ์ใหญ่เพื่อให้ตัวเลขมีความสูงเท่ากันและมีลักษณะการจุ่มที่จุดเริ่มต้นของการ0xA1B2CDEFปรากฏดังนั้นไม่เหมือน0xa1b2cdefที่ dips ขึ้นและลงตามจำนวนเกินไป ทางเลือกของคุณแม้ว่าภายในขอบเขตที่กว้างมาก (uintptr_t)หล่อขอแนะนำอย่างไม่น่าสงสัยโดย GCC เมื่อมันสามารถอ่านสตริงรูปแบบที่รวบรวมเวลา ฉันคิดว่ามันถูกต้องที่จะขอนักแสดง แต่ฉันแน่ใจว่ามีบางคนที่จะเพิกเฉยต่อคำเตือนและหลบไปกับมันเกือบตลอดเวลา


Kerrek ถามในความคิดเห็น:

ฉันสับสนเล็กน้อยเกี่ยวกับการเลื่อนระดับมาตรฐานและการโต้แย้งแบบแปรปรวน ตัวชี้ทั้งหมดได้รับการเลื่อนระดับเป็นโมฆะ * หรือไม่ มิฉะนั้นถ้าint*เคยพูดว่าสองไบต์และvoid*เป็น 4 ไบต์แล้วมันจะชัดเจนว่าเป็นข้อผิดพลาดในการอ่านสี่ไบต์จากการโต้แย้งไม่ใช่?

ฉันถูกภายใต้ภาพลวงตาว่ามาตรฐาน C กล่าวว่าทุกตัวชี้วัตถุต้องเป็นขนาดเดียวกันดังนั้นvoid *และint *ไม่สามารถขนาดแตกต่างกัน อย่างไรก็ตามสิ่งที่ฉันคิดว่าเป็นส่วนที่เกี่ยวข้องของมาตรฐาน C99 นั้นไม่ได้เน้นย้ำมากนัก (แม้ว่าฉันจะไม่ทราบถึงการใช้งานในสิ่งที่ฉันแนะนำคือความจริงเป็นเท็จจริง ๆ ):

§6.2.5ประเภท

¶26ตัวชี้ไปที่โมฆะจะต้องมีการเป็นตัวแทนและความต้องการการจัดตำแหน่งเช่นเดียวกับตัวชี้ไปยังประเภทตัวละคร 39)ในทำนองเดียวกันพอยน์เตอร์สำหรับประเภทที่เข้ากันได้หรือไม่ผ่านการรับรองของประเภทที่เข้ากันได้จะต้องมีข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งเดียวกัน ตัวชี้ทั้งหมดไปยังประเภทโครงสร้างจะต้องมีการเป็นตัวแทนและความต้องการการจัดตำแหน่งเช่นเดียวกับแต่ละอื่น ๆ พอยน์เตอร์ทั้งหมดไปยังประเภทยูเนี่ยนจะต้องมีข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งที่เหมือนกัน ตัวชี้ไปยังประเภทอื่นไม่จำเป็นต้องมีข้อกำหนดการเป็นตัวแทนหรือการจัดตำแหน่งเดียวกัน

39)ข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งเดียวกันนั้นหมายถึงการบ่งบอกถึงความสามารถในการแลกเปลี่ยนซึ่งเป็นข้อโต้แย้งของฟังก์ชันค่าส่งคืนจากฟังก์ชันและสมาชิกของสหภาพ

(C11 พูดเหมือนกันในส่วน§6.2.5, ¶28และเชิงอรรถ 48)

ดังนั้นตัวชี้ไปยังโครงสร้างทั้งหมดต้องมีขนาดเท่ากันและต้องใช้ข้อกำหนดการจัดตำแหน่งเดียวกันแม้ว่าโครงสร้างที่ตัวชี้จะชี้อาจมีข้อกำหนดการจัดตำแหน่งที่แตกต่างกัน ในทำนองเดียวกันสำหรับสหภาพ พอยน์เตอร์พอยน์เตอร์และพอยน์เตอร์พอยน์เตอร์ต้องมีขนาดและข้อกำหนดการจัดตำแหน่งเดียวกัน ตัวชี้ไปยังการเปลี่ยนแปลงในint(ความหมายunsigned intและsigned int) ต้องมีขนาดและข้อกำหนดการจัดตำแหน่งเดียวกัน ในทำนองเดียวกันสำหรับประเภทอื่น ๆ แต่มาตรฐาน C sizeof(int *) == sizeof(void *)ไม่ได้อย่างเป็นทางการกล่าวว่า โอ้ดีดังนั้นจึงเป็นสิ่งที่ดีสำหรับการทำให้คุณตรวจสอบสมมติฐานของคุณ

มาตรฐาน C อย่างแน่นอนไม่ต้องการพอยน์เตอร์ของฟังก์ชั่นให้มีขนาดเท่ากันกับพอยน์เตอร์พอยน์เตอร์ นั่นเป็นสิ่งที่จำเป็นที่จะไม่ทำลายหน่วยความจำรุ่นต่างๆ คุณสามารถมีพอยน์เตอร์ข้อมูลแบบ 16 บิตได้ แต่พอยน์เตอร์ฟังก์ชั่น 32 บิตหรือในทางกลับกัน นี่คือเหตุผลที่มาตรฐาน C ไม่ได้บังคับให้พอยน์เตอร์ฟังก์ชันสามารถแปลงเป็นพอยน์เตอร์พอยน์เตอร์และในทางกลับกัน

โชคดีที่ (สำหรับโปรแกรมเมอร์ที่กำหนดเป้าหมาย POSIX) POSIX จะก้าวเข้าสู่การฝ่าฝืนและกำหนดหน้าที่ของพอยน์เตอร์และพอยน์เตอร์ของข้อมูลว่ามีขนาดเท่ากัน:

§2.12.3 ประเภทตัวชี้

ฟังก์ชั่นประเภทตัวชี้ทั้งหมดจะต้องมีการแสดงเช่นเดียวกับตัวชี้ประเภทที่จะเป็นโมฆะ การแปลงตัวชี้ฟังก์ชั่นที่void *จะไม่เปลี่ยนการเป็นตัวแทน void *คุ้มค่าที่เกิดจากการแปลงดังกล่าวสามารถแปลงกลับไปที่เดิมประเภทตัวชี้ฟังก์ชั่นที่ใช้หล่ออย่างชัดเจนโดยไม่ต้องสูญเสียข้อมูล

หมายเหตุ: มาตรฐาน ISO C ไม่ต้องการสิ่งนี้ แต่เป็นสิ่งจำเป็นสำหรับความสอดคล้อง POSIX

ดังนั้นจึงดูเหมือนว่าบรรยากาศที่ชัดเจนในการvoid *เป็นอย่างมากแนะนำให้เลือกสำหรับความน่าเชื่อถือสูงสุดในรหัสเมื่อผ่านตัวชี้ไปยังฟังก์ชั่น variadic printf()เช่น บนระบบ POSIX จะปลอดภัยที่จะใช้ตัวชี้ฟังก์ชันเป็นตัวชี้โมฆะสำหรับการพิมพ์ ในระบบอื่น ๆ มันไม่จำเป็นต้องปลอดภัยที่จะทำเช่นนั้นและไม่จำเป็นต้องปลอดภัยในการส่งพอยน์เตอร์ที่นอกเหนือจากที่void *ไม่ได้รับการร่าย


3
ฉันสับสนเล็กน้อยเกี่ยวกับการเลื่อนระดับมาตรฐานและการโต้แย้งแบบแปรปรวน พอยน์เตอร์ทั้งหมดได้รับการเลื่อนระดับเป็นมาตรฐานvoid*หรือไม่? มิฉะนั้นถ้าint*เคยพูดว่าสองไบต์และvoid*เป็น 4 ไบต์แล้วมันจะชัดเจนว่าเป็นข้อผิดพลาดในการอ่านสี่ไบต์จากการโต้แย้งไม่ใช่?
Kerrek SB

โปรดทราบว่าการอัพเดตเป็น POSIX (POSIX 2013) ได้ลบหัวข้อ 2.12.3 ซึ่งย้ายข้อกำหนดส่วนใหญ่ไปยังdlsym()ฟังก์ชันแทน วันหนึ่งฉันจะเขียนการเปลี่ยนแปลง ... แต่ 'วันหนึ่ง' ไม่ใช่ 'วันนี้'
Jonathan Leffler

คำตอบนี้ใช้ได้กับพอยน์เตอร์ของฟังก์ชั่นหรือไม่? พวกเขาสามารถเปลี่ยนเป็นvoid *? อืมผมเห็นความคิดเห็นของคุณที่นี่ เนื่องจากต้องการการแปลงเพียงหนึ่งวัตต์ (ตัวชี้ฟังก์ชันเป็นvoid *) จึงใช้งานได้หรือไม่
chux - Reinstate Monica

@chux: เคร่งครัดคำตอบคือ 'ไม่' แต่ในทางปฏิบัติคำตอบคือ 'ใช่' มาตรฐาน C ไม่รับประกันว่าพอยน์เตอร์ฟังก์ชั่นสามารถแปลงเป็นvoid *และกลับโดยไม่สูญเสียข้อมูล ในทางปฏิบัติมีเครื่องน้อยมากที่ขนาดของตัวชี้ฟังก์ชั่นไม่เหมือนกับขนาดของตัวชี้วัตถุ ฉันไม่คิดว่ามาตรฐานจะให้วิธีการพิมพ์ตัวชี้ฟังก์ชั่นบนเครื่องที่การแปลงท่ามีปัญหา
Jonathan Leffler

"และย้อนกลับโดยไม่สูญเสียข้อมูล" ไม่เกี่ยวข้องกับการพิมพ์ มันช่วยได้ไหม
chux - Reinstate Monica

50

pเป็นตัวระบุการแปลงสำหรับพิมพ์พอยน์เตอร์ ใช้สิ่งนี้

int a = 42;

printf("%p\n", (void *) &a);

โปรดจำไว้ว่าการไม่ใช้งานการโยนนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดและการพิมพ์ด้วยตัวpระบุการแปลงจะกระทำในลักษณะที่กำหนดโดยการนำไปปฏิบัติ


2
ให้อภัยทำไมการละเว้นนักแสดงจึงเป็น "พฤติกรรมที่ไม่ได้กำหนด" สิ่งนี้มีความสำคัญกับที่อยู่ของตัวแปรใดหรือไม่หากคุณต้องการเพียงที่อยู่ไม่ใช่ค่าหรือไม่
valdo

9
@valdo เพราะ C บอกว่ามัน (C99, 7.19.6.1p8) "p อาร์กิวเมนต์จะเป็นตัวชี้ให้เป็นโมฆะ"
ouah

12
@valdo: มันไม่จำเป็นว่าตัวชี้ทั้งหมดจะมีขนาด / การแสดงเหมือนกัน
caf

32

ใช้%pสำหรับ "ตัวชี้" และอย่าใช้สิ่งอื่น * คุณไม่ได้รับการรับรองตามมาตรฐานที่คุณได้รับอนุญาตให้ใช้ตัวชี้เหมือนกับจำนวนเต็มใด ๆ ดังนั้นคุณจะได้รับพฤติกรรมที่ไม่ได้กำหนดด้วยรูปแบบที่สมบูรณ์ (ตัวอย่างเช่น%uคาดว่าจะมีunsigned intแต่จะเกิดอะไรขึ้นหากvoid*มีข้อกำหนดขนาดหรือการจัดตำแหน่งที่แตกต่างกว่าunsigned int)

*) [ดูคำตอบที่ดีของโจนาธาน!] หรือ%pคุณสามารถใช้มาโครเฉพาะสำหรับตัวชี้จาก<inttypes.h>เพิ่มใน C99

ทั้งหมดชี้วัตถุโดยปริยายแปลงvoid*ใน C แต่เพื่อที่จะผ่านตัวชี้เป็นอาร์กิวเมนต์ variadic คุณต้องโยนมันทิ้งอย่างชัดเจน (ตั้งแต่ตัวชี้วัตถุพลเป็นเพียงแปลงสภาพแต่ไม่เหมือนกันที่จะชี้เป็นโมฆะ):

printf("x lives at %p.\n", (void*)&x);

2
พอยน์เตอร์ออบเจ็กต์ทั้งหมดสามารถแปลงเป็นvoid *(แต่สำหรับprintf()คุณแล้วในทางเทคนิคคุณจำเป็นต้องใช้แคสต์ที่ชัดเจนเนื่องจากเป็นฟังก์ชัน Variadic) void *คำแนะนำการทำงานไม่จำเป็นต้องแปลงสภาพให้แก่
คาเฟ่

@caf: โอ้ฉันไม่ทราบเกี่ยวกับข้อโต้แย้งแปรปรวน - แก้ไข! ขอบคุณ!
Kerrek SB

2
มาตรฐาน C ไม่ต้องการให้พอยน์เตอร์ฟังก์ชั่นสามารถแปลงเป็นvoid *และกลับเป็นพอยน์เตอร์ของฟังก์ชันได้โดยไม่สูญเสีย โชคดีที่ POSIX ต้องการสิ่งนั้นอย่างชัดเจน (สังเกตว่ามันไม่ได้เป็นส่วนหนึ่งของมาตรฐาน C) ดังนั้นในทางปฏิบัติคุณสามารถหลีกเลี่ยงได้ (แปลงvoid (*function)(void)เป็นvoid *กลับไปvoid (*function)(void)) แต่อย่างเคร่งครัดไม่ได้รับคำสั่งจากมาตรฐาน C
Jonathan Leffler

2
Jonathan และ R .: ทั้งหมดนี้น่าสนใจมาก แต่ฉันค่อนข้างแน่ใจว่าเราไม่ได้พยายามพิมพ์พอยน์เตอร์ของฟังก์ชั่นที่นี่ดังนั้นบางทีนี่อาจไม่ใช่สถานที่ที่เหมาะสมที่จะพูดคุยเรื่องนี้ ฉันค่อนข้างจะเห็นการสนับสนุนบางอย่างที่นี่เพื่อยืนยันของฉันที่จะไม่ใช้%u!
Kerrek SB

2
%uและ%luเป็นสิ่งที่ผิดในทุกเครื่องไม่ใช่เครื่องบางอย่าง ข้อมูลจำเพาะของprintfมีความชัดเจนมากว่าเมื่อประเภทที่ส่งผ่านไม่ตรงกับประเภทที่ระบุโดยตัวระบุรูปแบบพฤติกรรมจะไม่ได้กำหนด ไม่ว่าขนาดของประเภทที่ตรงกัน (ซึ่งอาจเป็นจริงหรือเท็จขึ้นอยู่กับเครื่อง) นั้นไม่เกี่ยวข้อง มันเป็นประเภทที่ต้องจับคู่และพวกเขาจะไม่ทำ
. GitHub หยุดช่วยน้ำแข็ง

9

อีกทางเลือกหนึ่งสำหรับคำตอบอื่น ๆ (ดีมาก) คุณสามารถส่งไปยังuintptr_tหรือintptr_t(จากstdint.h/ inttypes.h) และใช้ตัวระบุการแปลงจำนวนเต็มที่สอดคล้องกัน สิ่งนี้จะช่วยให้มีความยืดหยุ่นมากขึ้นในการจัดรูปแบบตัวชี้ แต่การพูดอย่างเข้มงวดไม่จำเป็นต้องมีการใช้งานเพื่อให้เป็นตัวพิมพ์เหล่านี้


ควรพิจารณา#include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } พฤติกรรมที่ไม่ได้กำหนดเพื่อพิมพ์ที่อยู่ของตัวแปรโดยใช้ตัว%uระบุรูปแบบหรือไม่ ที่อยู่ของตัวแปรในกรณีส่วนใหญ่เป็นค่าบวกดังนั้นฉันจึงสามารถใช้%uแทนได้%pหรือไม่
ทำลายล้าง

1
@Destructor: ไม่%uเป็นรูปแบบสำหรับชนิดและไม่สามารถใช้ร่วมกับข้อโต้แย้งที่ชี้ไปยังunsigned int printf
. GitHub หยุดช่วยน้ำแข็ง

-1

คุณสามารถใช้%xหรือ%Xหรือ%p; ทั้งหมดถูกต้อง

  • หากคุณใช้%xที่อยู่จะได้รับเป็นตัวพิมพ์เล็กตัวอย่างเช่นa3bfbc4
  • หากคุณใช้%Xที่อยู่จะได้รับเป็นตัวพิมพ์ใหญ่ตัวอย่างเช่น:A3BFBC4

ทั้งสองอย่างนี้ถูกต้อง

หากคุณใช้%xหรือ%Xกำลังพิจารณาที่อยู่หกตำแหน่งสำหรับที่อยู่และหากคุณใช้งาน%pจะพิจารณาตำแหน่งที่อยู่แปดตำแหน่งสำหรับที่อยู่ ตัวอย่างเช่น:


1
ยินดีต้อนรับสู่ SO โปรดสละเวลาสักครู่เพื่อทบทวนคำตอบอื่น ๆ พวกเขากำลังอธิบายรายละเอียดที่คุณมองเห็นอย่างชัดเจน
AntoineL
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.