Penentu format yang benar untuk mencetak penunjuk atau alamat?

194

Penentu format manakah yang harus saya gunakan untuk mencetak alamat variabel? Saya bingung antara banyak di bawah ini.

% u - bilangan bulat tak bertanda

% x - nilai heksadesimal

% p - batal pointer

Format mana yang paling optimal untuk mencetak alamat?

San
sumber

Jawaban:

240

Jawaban paling sederhana, dengan asumsi Anda tidak keberatan dengan keanehan dan variasi dalam format antara platform yang berbeda, adalah %pnotasi standar .

Standar C99 (ISO / IEC 9899: 1999) mengatakan dalam §7.19.6.1 ¶8:

pArgumen harus menjadi penunjuk void. Nilai pointer dikonversi ke urutan karakter pencetakan, dengan cara yang ditentukan implementasi.

(Dalam C11 - ISO / IEC 9899: 2011 - informasinya ada di §7.21.6.1 ¶8.)

Pada beberapa platform, itu akan mencakup yang terdepan 0xdan yang lain tidak, dan huruf-hurufnya bisa dalam huruf kecil atau huruf besar, dan standar C bahkan tidak mendefinisikan bahwa itu akan menjadi output heksadesimal meskipun saya tahu tidak ada implementasi di mana tidak.

Ini agak terbuka untuk diperdebatkan apakah Anda harus secara eksplisit mengkonversi pointer dengan (void *)pemain. Sedang eksplisit, yang biasanya baik (jadi itu yang saya lakukan), dan standar mengatakan 'argumen akan menjadi penunjuk ke void'. Pada sebagian besar mesin, Anda bisa menghilangkan gips eksplisit. Namun, itu akan menjadi masalah pada mesin di mana representasi bit dari char *alamat untuk lokasi memori yang diberikan berbeda dari pointer ' apa pun alamat ' ' untuk lokasi memori yang sama. Ini akan menjadi mesin yang dialamatkan kata, bukan byte yang dialamatkan. Mesin seperti itu tidak umum (mungkin tidak tersedia) hari ini, tetapi mesin pertama yang saya kerjakan setelah universitas adalah salah satunya (ICL Perq).

Jika Anda tidak puas dengan perilaku yang ditentukan implementasi %p, gunakan C99 <inttypes.h>dan uintptr_tsebagai gantinya:

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

Ini memungkinkan Anda menyempurnakan representasi agar sesuai dengan diri Anda. Saya memilih untuk memiliki angka heksa dalam huruf besar sehingga jumlahnya seragam tinggi yang sama dan kemiringan karakteristik pada awal 0xA1B2CDEFmuncul demikian, tidak seperti 0xa1b2cdefyang naik turun di sepanjang nomor juga. Pilihan Anda, dalam batas yang sangat luas. Para (uintptr_t)pemain tidak diragukan lagi direkomendasikan oleh GCC ketika dapat membaca string format pada waktu kompilasi. Saya pikir itu benar untuk meminta para pemeran, meskipun saya yakin ada beberapa yang akan mengabaikan peringatan dan lolos begitu saja sepanjang waktu.


Kerrek bertanya dalam komentar:

Saya agak bingung tentang promosi standar dan argumen variadik. Apakah semua petunjuk dipromosikan standar untuk dibatalkan *? Kalau tidak, jika int*, katakanlah, dua byte, dan void*4 byte, maka jelas akan menjadi kesalahan untuk membaca empat byte dari argumen, bukan?

Saya berada di bawah ilusi bahwa standar C mengatakan bahwa semua pointer objek harus memiliki ukuran yang sama, jadi void *dan int *tidak dapat menjadi ukuran yang berbeda. Namun, apa yang saya pikirkan adalah bagian yang relevan dari standar C99 tidak begitu tegas (meskipun saya tidak tahu implementasi di mana apa yang saya sarankan benar sebenarnya salah):

§6.2.5 Jenis

¶26 Suatu pointer ke void harus memiliki persyaratan representasi dan perataan yang sama dengan pointer ke tipe karakter. 39) Demikian pula, pointer ke versi yang memenuhi syarat atau tidak memenuhi syarat dari jenis yang kompatibel harus memiliki persyaratan representasi dan penyelarasan yang sama. Semua pointer ke tipe struktur harus memiliki persyaratan representasi dan perataan yang sama satu sama lain. Semua pointer ke tipe serikat harus memiliki persyaratan representasi dan penyelarasan yang sama satu sama lain. Pointer ke tipe lain tidak perlu memiliki representasi atau persyaratan penyelarasan yang sama.

39) Persyaratan representasi dan penyelarasan yang sama dimaksudkan untuk mengimplikasikan interchangeability sebagai argumen untuk fungsi, mengembalikan nilai dari fungsi, dan anggota serikat pekerja.

(C11 mengatakan persis sama di bagian §6.2.5, ¶28, dan catatan kaki 48.)

Jadi, semua pointer ke struktur harus memiliki ukuran yang sama satu sama lain, dan harus berbagi persyaratan pelurusan yang sama, meskipun struktur yang ditunjuk oleh pointer mungkin memiliki persyaratan pelurusan yang berbeda. Demikian pula untuk serikat pekerja. Pointer karakter dan pointer kosong harus memiliki ukuran dan persyaratan pelurusan yang sama. Pointer ke variasi pada int(makna unsigned intdan signed int) harus memiliki ukuran dan persyaratan perataan yang sama satu sama lain; sama untuk jenis lainnya. Tetapi standar C tidak secara resmi mengatakan itu sizeof(int *) == sizeof(void *). Oh well, SO bagus untuk membuat Anda memeriksa asumsi Anda.

Standar C secara definitif tidak mensyaratkan pointer fungsi memiliki ukuran yang sama dengan pointer objek. Itu perlu untuk tidak merusak model memori yang berbeda pada sistem seperti DOS. Di sana Anda bisa memiliki pointer data 16-bit tetapi pointer fungsi 32-bit, atau sebaliknya. Inilah sebabnya mengapa standar C tidak mengamanatkan bahwa pointer fungsi dapat dikonversi ke pointer objek dan sebaliknya.

Untungnya (untuk pemrogram yang menargetkan POSIX), langkah-langkah POSIX ke dalam pelanggaran dan mengamanatkan bahwa fungsi pointer dan pointer data memiliki ukuran yang sama:

§2.12.3 Jenis Pointer

Semua tipe penunjuk fungsi harus memiliki representasi yang sama dengan penunjuk tipe untuk dibatalkan. Konversi dari pointer fungsi ke void *tidak akan mengubah representasi. Sebuah void *nilai yang dihasilkan dari konversi tersebut dapat dikonversi kembali ke jenis pointer fungsi asli, menggunakan cast yang eksplisit, tanpa kehilangan informasi.

Catatan: Standar ISO C tidak memerlukan ini, tetapi diperlukan untuk kesesuaian POSIX.

Jadi, sepertinya cast eksplisit void *sangat disarankan untuk keandalan maksimum dalam kode ketika meneruskan pointer ke fungsi variadic seperti printf(). Pada sistem POSIX, aman untuk melemparkan penunjuk fungsi ke penunjuk kosong untuk dicetak. Pada sistem lain, itu tidak selalu aman untuk melakukan itu, juga tidak selalu aman untuk melewati pointer selain void *tanpa gips.

Jonathan Leffler
sumber
3
Saya agak bingung tentang promosi standar dan argumen variadik. Apakah semua petunjuk mendapatkan promosi standar void*? Kalau tidak, jika int*, katakanlah, dua byte, dan void*4 byte, maka jelas akan menjadi kesalahan untuk membaca empat byte dari argumen, bukan?
Kerrek SB
Perhatikan bahwa pembaruan untuk POSIX (POSIX 2013) telah menghapus bagian 2.12.3, alih-alih memindahkan sebagian besar persyaratan ke dlsym()fungsi. Suatu hari saya akan menulis perubahan ... tetapi 'satu hari' bukan 'hari ini'.
Jonathan Leffler
Apakah jawaban ini juga berlaku untuk pointer ke fungsi? Bisakah mereka dikonversi void *? Hmm saya melihat komentar Anda di sini . Karena hanya konversi satu-wat yang diperlukan (penunjuk fungsi ke void *), apakah itu berfungsi?
chux - Reinstate Monica
@ chux: Sebenarnya, jawabannya adalah 'tidak', tetapi dalam praktiknya jawabannya adalah 'ya'. Standar C tidak menjamin bahwa pointer fungsi dapat dikonversi ke void *dan kembali tanpa kehilangan informasi. Secara pragmatis, ada beberapa mesin yang ukuran pointer fungsi tidak sama dengan ukuran pointer objek. Saya tidak berpikir standar menyediakan metode pencetakan pointer fungsi pada mesin di mana konversi itu bermasalah.
Jonathan Leffler
"dan kembali tanpa kehilangan informasi" tidak relevan dengan pencetakan. Apakah itu membantu?
chux - Reinstate Monica
50

padalah penentu konversi untuk mencetak pointer. Gunakan ini.

int a = 42;

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

Ingatlah bahwa menghilangkan para pemain adalah perilaku yang tidak terdefinisi dan bahwa pencetakan dengan pspecifier konversi dilakukan dengan cara yang ditentukan implementasi.

ouah
sumber
2
Maaf, mengapa menghilangkan pemain adalah "perilaku yang tidak terdefinisi"? Apakah ini alamat variabel yang mana, jika yang Anda butuhkan adalah alamat, bukan nilainya?
valdo
9
@valdo karena C mengatakannya (C99, 7.19.6.1p8) "p Argumen harus menjadi pointer untuk membatalkan."
ouah
12
@valdo: Ini tidak selalu berarti bahwa semua pointer memiliki ukuran / representasi yang sama.
caf
32

Gunakan %p, untuk "pointer", dan jangan gunakan yang lain *. Anda tidak dijamin oleh standar bahwa Anda diizinkan memperlakukan pointer seperti tipe integer tertentu, sehingga Anda akan benar-benar mendapatkan perilaku tidak terdefinisi dengan format integral. (Misalnya, %umengharapkan unsigned int, tetapi bagaimana jika void*memiliki ukuran atau persyaratan penyelarasan yang berbeda dariunsigned int ?)

*) [Lihat jawaban baik-baik saja Jonathan!] Atau %p, Anda bisa menggunakan makro spesifik-pointer dari <inttypes.h>, ditambahkan dalam C99.

Semua pointer objek secara implisit dapat dikonversi ke void*dalam C, tetapi untuk meneruskan pointer sebagai argumen variadic, Anda harus melemparkannya secara eksplisit (karena pointer objek yang berubah-ubah hanya dapat dikonversi , tetapi tidak identik dengan void pointer):

printf("x lives at %p.\n", (void*)&x);
Kerrek SB
sumber
2
Semua pointer objek dapat dikonversi ke void *(meskipun untuk printf()Anda secara teknis membutuhkan pemeran eksplisit, karena itu adalah fungsi variadik). Pointer fungsi tidak harus dapat dikonversi void *.
caf
@caf: Oh, saya tidak tahu tentang argumen variadic - diperbaiki! Terima kasih!
Kerrek SB
2
Standar C tidak mengharuskan pointer fungsi dapat dikonversi ke void *dan kembali ke pointer fungsi tanpa kehilangan; Untungnya, POSIX memang secara eksplisit mensyaratkan itu (mencatat bahwa itu bukan bagian dari standar C). Jadi, dalam praktiknya, Anda bisa lolos begitu saja (mengonversi void (*function)(void)ke void *dan kembali ke void (*function)(void)), tetapi sebenarnya itu tidak diamanatkan oleh standar C.
Jonathan Leffler
2
Jonathan dan R .: Ini semua sangat menarik, tapi saya cukup yakin kami tidak mencoba untuk mencetak pointer fungsi di sini, jadi mungkin ini bukan tempat yang tepat untuk membahas ini. Saya lebih suka melihat beberapa dukungan di sini agar desakan saya tidak digunakan %u!
Kerrek SB
2
%udan %lusalah pada semua mesin , bukan beberapa mesin. Spesifikasi printfsangat jelas bahwa ketika tipe yang dikirimkan tidak cocok dengan tipe yang diperlukan oleh penspesifikasi format, perilaku tidak terdefinisi. Apakah ukuran jenis cocok (yang bisa benar atau salah, tergantung pada mesin) tidak relevan; itu adalah tipe yang harus cocok, dan mereka tidak akan pernah.
R .. GitHub BERHENTI MEMBANTU ICE
9

Sebagai alternatif dari jawaban lain (sangat baik), Anda dapat menggunakan uintptr_tatau intptr_t(dari stdint.h/ inttypes.h) dan menggunakan specifier konversi integer yang sesuai. Ini akan memungkinkan lebih banyak fleksibilitas dalam bagaimana pointer diformat, tetapi secara tegas implementasi tidak diperlukan untuk memberikan typedef ini.

R .. GitHub BERHENTI MEMBANTU ICE
sumber
pertimbangkan #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } apakah itu perilaku tidak terdefinisi untuk mencetak alamat variabel menggunakan %upenentu format? Alamat variabel dalam banyak kasus adalah positif, jadi bisakah saya menggunakan %ubukan %p?
Destructor
1
@Destructor: Tidak, %uadalah format untuk unsigned inttipe dan tidak dapat digunakan dengan argumen pointer printf.
R .. GitHub BERHENTI MEMBANTU ICE
-1

Anda dapat menggunakan %xatau %Xatau %p; semuanya benar.

  • Jika Anda menggunakan %x , alamat diberikan sebagai huruf kecil, misalnya:a3bfbc4
  • Jika Anda menggunakan %X, alamat diberikan sebagai huruf besar, misalnya:A3BFBC4

Keduanya benar.

Jika Anda menggunakan %xatau %Xmempertimbangkan enam posisi untuk alamat tersebut, dan jika Anda menggunakannya %pmempertimbangkan delapan posisi untuk alamat tersebut. Sebagai contoh:

Pasha
sumber
1
Selamat datang di SO. Silakan luangkan waktu untuk meninjau jawaban lain, mereka menjelaskan dengan jelas sejumlah detail yang Anda abaikan.
AntoineL