การพิมพ์อักขระเลขฐานสิบหกใน C


104

ฉันกำลังพยายามอ่านเป็นบรรทัดของอักขระจากนั้นพิมพ์เลขฐานสิบหกที่เทียบเท่าของอักขระ

ตัวอย่างเช่นถ้าฉันมีสตริงที่"0xc0 0xc0 abc123"2 ตัวแรกเป็นc0เลขฐานสิบหกและอักขระที่เหลืออยู่abc123ใน ASCII ฉันควรจะได้รับ

c0 c0 61 62 63 31 32 33

อย่างไรก็ตามการprintfใช้%xให้ฉัน

ffffffc0 ffffffc0 61 62 63 31 32 33

ฉันจะได้ผลลัพธ์ที่ต้องการโดยไม่ต้องทำ"ffffff"อย่างไร และเหตุใดจึงมีเพียง c0 (และ 80) เท่านั้นที่มีffffffแต่อักขระอื่น ๆ ไม่ได้?


สตริงที่ตรงกับอาร์เรย์ไบต์ของคุณจะเป็น ..."\xc0\xc0abc123"
burito

คำตอบ:


134

คุณเห็นffffffเนื่องจากcharมีการลงนามในระบบของคุณ ใน C, vararg ฟังก์ชั่นเช่นprintfจะส่งเสริมจำนวนเต็มทั้งหมดมีขนาดเล็กกว่าที่จะint intเนื่องจากcharเป็นจำนวนเต็ม (จำนวนเต็มลงนาม 8 บิตในกรณีของคุณ) ตัวอักษรของคุณจะถูกเลื่อนระดับintผ่านส่วนขยายเครื่องหมาย

เนื่องจากc0และ80มี 1 บิตนำหน้า (และเป็นลบเป็นจำนวนเต็ม 8 บิต) จึงมีการขยายการลงชื่อเข้าใช้ในขณะที่ตัวอื่น ๆ ในตัวอย่างของคุณไม่มี

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

นี่คือวิธีแก้ปัญหา:

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

สิ่งนี้จะปิดบังบิตบนและเก็บเฉพาะ 8 บิตล่างที่คุณต้องการ


15
วิธีแก้ปัญหาของฉันโดยใช้ cast to unsigned charคือหนึ่งคำสั่งที่เล็กกว่าใน gcc4.6 สำหรับ x86-64 ...
lvella

1
บางทีฉันอาจช่วยได้ นี่คือพฤติกรรมที่ไม่ได้กำหนด (ในทางเทคนิค) เนื่องจากตัวระบุxต้องการชนิดที่ไม่ได้ลงนาม แต่ ch ถูกเลื่อนระดับเป็น int รหัสที่ถูกต้องจะโยนเพียง CH hhxที่จะได้รับการรับรองหรือใช้โยนถ่านที่ไม่ได้ลงชื่อและระบุไปนี้:
2501

1
ถ้าฉันมีprintf("%x", 0)ก็ไม่มีอะไรจะพิมพ์
Gustavo Meira

มันไม่ได้พิมพ์อะไรเลยเพราะค่าต่ำสุดถูกตั้งไว้ที่ 0 ในการแก้ไขปัญหานี้ให้ลองprintf("%.2x", 0);ใช้ตัวอักษรที่เพิ่มขึ้นเป็น 2 ตัวในการตั้งค่าสูงสุดให้นำหน้า. ด้วยตัวเลข ตัวอย่างเช่นคุณสามารถบังคับให้ลากอักขระได้เพียง 2 ตัวโดยทำprintf("%2.2x", 0);
user2262111

เหตุผลว่าทำไมprintf("%x", ch & 0xff)ควรจะดีกว่าเพียงแค่ใช้printf("%02hhX", a)ในขณะที่ @ brutal_lobster ของคำตอบ ?
maxschlepzig

62

มีการแปลงประเภทเป็น int นอกจากนี้คุณสามารถบังคับให้พิมพ์เป็น char โดยใช้ตัวระบุ% hhx

printf("%hhX", a);

ในกรณีส่วนใหญ่คุณจะต้องกำหนดความยาวขั้นต่ำด้วยเพื่อเติมอักขระที่สองด้วยศูนย์:

printf("%02hhX", a);

ISO / IEC 9899: 201x พูดว่า:

7 ตัวปรับความยาวและความหมายคือ hh ระบุว่าตัวระบุการแปลง d, i, o, u, x หรือ X ต่อไปนี้ใช้กับอาร์กิวเมนต์ถ่านที่ลงนามหรืออาร์กิวเมนต์ถ่านที่ไม่ได้ลงนาม (อาร์กิวเมนต์จะได้รับการเลื่อนระดับตามการส่งเสริมจำนวนเต็ม, แต่ค่าของมันจะถูกแปลงเป็นถ่านที่ลงชื่อหรือถ่านที่ไม่ได้ลงชื่อก่อนพิมพ์); หรือต่อไปนี้


30

คุณสามารถสร้างถ่านที่ไม่ได้ลงชื่อ:

unsigned char c = 0xc5;

การพิมพ์ก็จะให้และไม่ได้C5ffffffc5

เฉพาะตัวอักษรที่ใหญ่กว่า 127 เท่านั้นที่พิมพ์ด้วยเครื่องหมายffffffเนื่องจากเป็นค่าลบ (มีการลงนามตัวอักษร)

หรือคุณสามารถส่งในcharขณะที่พิมพ์:

char c = 0xc5; 
printf("%x", (unsigned char)c);

3
+1 คำตอบที่ดีที่สุดที่แท้จริงพิมพ์อย่างชัดเจนให้ใกล้เคียงกับการประกาศข้อมูลมากที่สุด (แต่ไม่ใกล้กว่านี้)
Bob Stein

13

คุณอาจจัดเก็บค่า 0xc0 ในcharตัวแปรสิ่งที่น่าจะเป็นประเภทที่มีการเซ็นชื่อและค่าของคุณเป็นค่าลบ (ชุดบิตที่สำคัญที่สุด) จากนั้นเมื่อพิมพ์จะถูกแปลงเป็นintและเพื่อให้สมดุล semantical, แผ่นคอมไพเลอร์ไบต์พิเศษกับ 0xff ดังนั้นในเชิงลบจะมีค่าตัวเลขเดียวกันของเชิงลบของคุณint charในการแก้ไขปัญหานี้ให้ส่งไปที่unsigned charเมื่อพิมพ์:

printf("%x", (unsigned char)variable);

13

คุณสามารถใช้hhเพื่อบอกprintfว่าอาร์กิวเมนต์เป็นถ่านที่ไม่ได้ลงชื่อ ใช้0เพื่อเพิ่มช่องว่างเป็นศูนย์และ2กำหนดความกว้างเป็น 2 xหรือXสำหรับอักขระเลขฐานสิบหก / ตัวพิมพ์ใหญ่

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

แก้ไข : หากผู้อ่านกังวลเกี่ยวกับการยืนยันของ 2501 ว่านี่ไม่ใช่ตัวระบุรูปแบบที่ 'ถูกต้อง' ฉันขอแนะนำให้อ่านprintfลิงก์อีกครั้ง โดยเฉพาะ:

แม้ว่า% c จะคาดหวังอาร์กิวเมนต์ int แต่ก็สามารถส่งผ่านถ่านได้อย่างปลอดภัยเนื่องจากการส่งเสริมจำนวนเต็มที่เกิดขึ้นเมื่อมีการเรียกใช้ฟังก์ชันตัวแปร

ข้อกำหนดการแปลงที่ถูกต้องสำหรับความกว้างคงที่ชนิดตัวอักษร (int8_t ฯลฯ ) ที่กำหนดไว้ในส่วนหัว<cinttypes>(C ++) หรือ<inttypes.h>(C) (แม้ว่า PRIdMAX, PRIuMAX ฯลฯ เป็นตรงกันกับ% JD% จู ฯลฯ )

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

แก้ไข 2 : (ฉบับ "เมื่อจะยอมรับคุณผิด"):

หากคุณอ่านมาตรฐาน C11 จริงในหน้าที่ 311 (329 ของ PDF) คุณจะพบ:

HH: ระบุว่าต่อไปนี้d, i, o, u, xหรือXแปลงระบุนำไปใช้signed charหรือunsigned charอาร์กิวเมนต์ (อาร์กิวเมนต์จะได้รับการเลื่อนตำแหน่งตามโปรโมชั่นจำนวนเต็ม แต่ค่าของมันจะถูกแปลงไปsigned charหรือunsigned charก่อนที่จะพิมพ์); หรือตัวnระบุการแปลงต่อไปนี้ใช้กับตัวชี้ไปยังsigned charอาร์กิวเมนต์


ตัวระบุไม่ถูกต้องสำหรับประเภท uint8_t ประเภทความกว้างคงที่ใช้ตัวระบุการพิมพ์พิเศษ ดู:inttypes.h
2501

ใช่ แต่จำนวนเต็ม varargs ทั้งหมดได้รับการเลื่อนขั้นเป็น int โดยปริยาย
Timmmm

นั่นอาจเป็นได้ แต่เท่าที่กำหนด C พฤติกรรมจะไม่ถูกกำหนดหากคุณไม่ใช้ตัวระบุที่ถูกต้อง
2501

แต่% x เป็นตัวระบุที่ถูกต้อง ( charและunsigned charได้รับการเลื่อนตำแหน่งให้เป็นint) [ en.cppreference.com/w/cpp/language/variadic_arguments] คุณจะต้องใช้ตัวระบุ PRI สำหรับสิ่งที่ไม่เหมาะกับแพลตฟอร์มของคุณเท่านั้นintเช่น unsigned int
Timmmm

%xถูกต้องสำหรับ int ที่ไม่ได้ลงนามไม่ใช่ int ประเภทถ่านและถ่านที่ไม่ได้ลงนามจะเลื่อนระดับเป็น int นอกจากนี้ไม่มีการรับประกันว่า uint8_t ถูกกำหนดให้เป็นถ่านที่ไม่ได้ลงชื่อ
2501

2

คุณอาจกำลังพิมพ์จากอาร์เรย์ถ่านที่เซ็นชื่อ พิมพ์จากอาร์เรย์ถ่านที่ไม่ได้ลงชื่อหรือกำหนดค่าด้วย 0xff: เช่น ar [i] & 0xFF ค่า c0 จะถูกขยายเนื่องจากมีการตั้งค่าบิต (เครื่องหมาย) สูง


0

ลองทำสิ่งนี้:

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

ซึ่งก่อให้เกิดสิ่งนี้:

$ ./foo 
c0 c0 61 62 63 31 32 33
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.