Mencetak karakter heksadesimal dalam C

103

Saya mencoba membaca dalam sebaris karakter, lalu mencetak karakter yang setara heksadesimal.

Misalnya, jika saya memiliki string yaitu "0xc0 0xc0 abc123", di mana 2 karakter pertama c0dalam hex dan karakter yang tersisa abc123dalam ASCII, maka saya harus mendapatkan

c0 c0 61 62 63 31 32 33

Namun, printfmenggunakan %xmemberi saya

ffffffc0 ffffffc0 61 62 63 31 32 33

Bagaimana cara mendapatkan keluaran yang saya inginkan tanpa "ffffff"? Dan mengapa hanya c0 (dan 80) yang memiliki karakter ffffff, tetapi tidak karakter lainnya?

Rayne
sumber
String yang cocok dengan array byte Anda akan menjadi ..."\xc0\xc0abc123"
burito

Jawaban:

132

Anda melihat ffffffkarena charditandatangani di sistem Anda. Di C, fungsi vararg seperti printfakan mempromosikan semua bilangan bulat yang lebih kecil dari intpada int. Karena charmerupakan integer (integer bertanda 8-bit dalam kasus Anda), karakter Anda sedang dipromosikan intmelalui ekstensi tanda.

Sejak c0dan 80memiliki 1-bit terdepan (dan negatif sebagai integer 8-bit), mereka diperpanjang tanda sementara yang lain dalam sampel Anda tidak.

char    int
c0 -> ffffffc0
80 -> ffffff80
61 -> 00000061

Inilah solusinya:

char ch = 0xC0;
printf("%x", ch & 0xff);

Ini akan menutupi bit atas dan menyimpan hanya 8 bit bawah yang Anda inginkan.

Mistik
sumber
15
Solusi saya menggunakan cast to unsigned charadalah satu instruksi yang lebih kecil di gcc4.6 untuk x86-64 ...
lvella
1
Mungkin saya bisa membantu. Ini (secara teknis) perilaku tidak terdefinisi karena penentu xmembutuhkan tipe unsigned, tetapi ch dipromosikan menjadi int. Kode yang benar hanya akan dilemparkan ch untuk unsigned, atau menggunakan cor untuk unsigned char dan specifier yang: hhx.
2501
1
Jika saya punya printf("%x", 0), tidak ada yang dicetak.
Gustavo Meira
Itu tidak mencetak apa pun karena minimum disetel ke 0. Untuk memperbaikinya, coba printf("%.2x", 0);yang akan meningkatkan karakter minimum yang ditarik ke 2. Untuk menyetel maks, tambahkan. dengan nomor. Misalnya, Anda dapat memaksa hanya 2 karakter yang ditarik dengan melakukanprintf("%2.2x", 0);
pengguna2262111
Ada alasan mengapa printf("%x", ch & 0xff)harus lebih baik daripada hanya menggunakan printf("%02hhX", a)seperti dalam jawaban @ brutal_lobster ?
maxschlepzig
62

Memang, ada jenis konversi ke int. Anda juga bisa memaksa tipe menjadi char dengan menggunakan% hhx specifier.

printf("%hhX", a);

Dalam kebanyakan kasus, Anda mungkin ingin mengatur panjang minimum juga untuk mengisi karakter kedua dengan nol:

printf("%02hhX", a);

ISO / IEC 9899: 201x mengatakan:

7 Pengubah panjang dan artinya adalah: hh Menentukan bahwa penentu konversi d, i, o, u, x, atau X berikut berlaku untuk argumen char atau unsigned char yang ditandatangani (argumen akan dipromosikan sesuai dengan promosi integer, tetapi nilainya akan dikonversi menjadi karakter yang ditandatangani atau karakter yang tidak ditandatangani sebelum dicetak); atau yang berikut

brutal_lobster
sumber
30

Anda dapat membuat karakter unsigned:

unsigned char c = 0xc5;

Mencetaknya akan memberi C5dan tidak ffffffc5.

Hanya karakter yang lebih besar dari 127 yang dicetak dengan ffffffkarena negatif (karakter ditandatangani).

Atau Anda dapat melakukan cast charsambil mencetak:

char c = 0xc5; 
printf("%x", (unsigned char)c);
Hicham
sumber
3
+1 jawaban terbaik nyata, ketikan eksplisit sedekat mungkin dengan pernyataan data (tetapi tidak lebih dekat).
Bob Stein
13

Anda mungkin menyimpan nilai 0xc0 dalam charvariabel, yang mungkin merupakan tipe bertanda, dan nilai Anda negatif (kumpulan bit paling signifikan). Kemudian, ketika mencetak, itu diubah menjadi int, dan untuk menjaga kesetaraan semantik, kompilator mengisi byte ekstra dengan 0xff, sehingga negatif intakan memiliki nilai numerik yang sama dari negatif Anda char. Untuk memperbaikinya, cukup transmisikan ke unsigned charsaat mencetak:

printf("%x", (unsigned char)variable);
lvella
sumber
13

Anda bisa menggunakan hhuntuk memberitahu printfbahwa argumennya adalah unsigned char. Gunakan 0untuk mendapatkan bantalan nol dan 2untuk mengatur lebar ke 2. xatau Xuntuk karakter hex huruf besar / kecil.

uint8_t a = 0x0a;
printf("%02hhX", a); // Prints "0A"
printf("0x%02hhx", a); // Prints "0x0a"

Sunting : Jika pembaca prihatin tentang pernyataan 2501 bahwa ini bukan penentu format yang 'benar', saya sarankan mereka membaca printftautan itu lagi. Secara khusus:

Meskipun% c mengharapkan argumen int, aman untuk meneruskan karakter karena promosi integer yang terjadi saat fungsi variadic dipanggil.

Spesifikasi konversi yang benar untuk tipe karakter lebar tetap (int8_t, dll) ditentukan di header <cinttypes>(C ++) atau <inttypes.h>(C) (meskipun PRIdMAX, PRIuMAX, dll sama dengan% jd,% ju, dll) .

Adapun maksudnya tentang signed vs unsigned, dalam hal ini tidak masalah karena nilainya harus selalu positif dan mudah sesuai dengan int yang ditandatangani. Tidak ada penentu format heksideximal bertanda.

Edit 2 : (edisi "ketika-mengakui-Anda-salah"):

Jika Anda membaca standar C11 yang sebenarnya pada halaman 311 (329 PDF) Anda menemukan:

jj: Menentukan bahwa pengikut d, i, o, u, x, atau Xkonversi specifier berlaku untuk signed charatau unsigned charargumen (argumen akan telah dipromosikan sesuai dengan promosi integer, tapi nilainya akan dikonversi ke signed charatau unsigned charsebelum mencetak); atau bahwa npenentu konversi berikut berlaku untuk penunjuk ke signed charargumen.

Timmmm
sumber
Penentu tidak benar untuk jenis uint8_t. Jenis lebar tetap menggunakan penentu cetak khusus. Lihat:inttypes.h
2501
Ya, tapi semua integer vararg secara implisit dipromosikan menjadi int.
Timmmm
Itu mungkin, tetapi sejauh C didefinisikan, perilaku tidak ditentukan jika Anda tidak menggunakan penentu yang benar.
2501
Tapi% x adalah penentu yang benar. ( chardan unsigned chardipromosikan menjadi int) [ en.cppreference.com/w/cpp/language/variadic_arguments] . Anda hanya perlu menggunakan penentu PRI untuk hal-hal yang tidak sesuai dengan platform Anda int- mis unsigned int.
Timmmm
%xbenar untuk unsigned int bukan int. Jenis char dan unsigned char dipromosikan menjadi int. Selain itu, tidak ada jaminan bahwa uint8_t didefinisikan sebagai unsigned char.
2501
2

Anda mungkin mencetak dari array karakter yang ditandatangani. Cetak dari array karakter yang tidak bertanda tangan atau tutupi nilainya dengan 0xff: misalnya ar [i] & 0xFF. Nilai c0 diberi tanda diperpanjang karena bit (tanda) tinggi diset.

Richard Pennington
sumber
-1

Coba sesuatu seperti ini:

int main()
{
    printf("%x %x %x %x %x %x %x %x\n",
        0xC0, 0xC0, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33);
}

Yang menghasilkan ini:

$ ./foo 
c0 c0 61 62 63 31 32 33
ObscureRobot
sumber