Drukowanie znaków szesnastkowych w C

103

Próbuję przeczytać wiersz znaków, a następnie wydrukować szesnastkowy odpowiednik tych znaków.

Na przykład, jeśli mam ciąg, czyli "0xc0 0xc0 abc123"gdzie pierwsze 2 znaki są c0w szesnastkowym, a pozostałe abc123w ASCII, to powinienem otrzymać

c0 c0 61 62 63 31 32 33

Jednak printfużycie %xdaje mi

ffffffc0 ffffffc0 61 62 63 31 32 33

Jak uzyskać żądane wyniki bez "ffffff"? I dlaczego tylko c0 (i 80) ma ffffff, a pozostałe znaki nie mają?

Rayne
źródło
Łańcuch pasujący do twojej tablicy bajtów byłby ..."\xc0\xc0abc123"
burito

Odpowiedzi:

132

Widzisz komunikat, ffffffponieważ charjest podpisany w Twoim systemie. W języku C funkcje vararg, takie jak printfpromują wszystkie liczby całkowite mniejsze od intdo int. Ponieważ charjest to liczba całkowita (w twoim przypadku 8-bitowa liczba całkowita ze intznakiem ), Twoje znaki są promowane za pomocą rozszerzenia ze znakiem.

Ponieważ c0i 80mają początkowy 1-bitowy (i są ujemne jako 8-bitowa liczba całkowita), są one rozszerzane przez znak, podczas gdy inne w twojej próbce nie.

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

Oto rozwiązanie:

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

To zamaskuje górne bity i zachowa tylko dolne 8 bitów, które chcesz.

Mistyczne
źródło
15
Moje rozwiązanie przy użyciu rzutowania na unsigned charto jedna instrukcja mniejsza w gcc4.6 dla x86-64 ...
lvella
1
Może mogę pomóc. Jest to (technicznie) niezdefiniowane zachowanie, ponieważ specyfikator xwymaga typu bez znaku, ale ch jest promowane do int. Poprawny kod po prostu rzucić ch do niepodpisane lub użyć oddanych do unsigned char i specyfikacją: hhx.
2501
1
Jeśli mam printf("%x", 0), nic nie jest drukowane.
Gustavo Meira
Nie drukuje niczego, ponieważ minimum jest ustawione na 0. Aby to naprawić, spróbuj printf("%.2x", 0);zwiększyć minimalną liczbę rysowanych znaków do 2. Aby ustawić maksimum, wstaw przedrostek. z liczbą. Na przykład możesz wymusić narysowanie tylko 2 znaków, wykonując printf("%2.2x", 0);
czynność
Jakikolwiek powód, printf("%x", ch & 0xff)powinna być lepsza niż tuż przy użyciu printf("%02hhX", a)jako użytkownika @ brutal_lobster w odpowiedzi ?
maxschlepzig
62

Rzeczywiście, istnieje konwersja typów na int. Możesz także wymusić char, używając specyfikatora% hhx.

printf("%hhX", a);

W większości przypadków będziesz chciał ustawić również minimalną długość, aby wypełnić drugi znak zerami:

printf("%02hhX", a);

ISO / IEC 9899: 201x mówi:

7 Modyfikatory długości i ich znaczenie to: hh Określa, że ​​następujący po nim specyfikator konwersji d, i, o, u, x lub X ma zastosowanie do argumentu typu char lub unsigned char (argument będzie promowany zgodnie z promocjami liczb całkowitych, ale jego wartość zostanie przekonwertowana na znak ze znakiem lub znak bez znaku przed wydrukowaniem); lub że następujące

brutal_lobster
źródło
30

Możesz utworzyć niepodpisany znak:

unsigned char c = 0xc5;

Wydruk da C5i nie da ffffffc5.

Tylko znaki większe niż 127 są drukowane ze znakiem, ffffffponieważ są ujemne (znak jest podpisany).

Lub możesz przesłać charpodczas drukowania:

char c = 0xc5; 
printf("%x", (unsigned char)c);
Hicham
źródło
3
+1 prawdziwa najlepsza odpowiedź, jawne wpisanie jak najbliżej deklaracji danych (ale nie bliżej).
Bob Stein
13

Prawdopodobnie przechowujesz wartość 0xc0 w charzmiennej, która prawdopodobnie jest typem ze znakiem, a twoja wartość jest ujemna (najbardziej znaczący zestaw bitów). Następnie, podczas drukowania, jest konwertowany na inti aby zachować semantyczną równoważność, kompilator dopełnia dodatkowe bajty wartością 0xff, więc negatyw intbędzie miał tę samą wartość liczbową co negatyw char. Aby to naprawić, po prostu prześlij do unsigned charpodczas drukowania:

printf("%x", (unsigned char)variable);
lvella
źródło
13

Możesz użyć, hhaby powiedzieć, printfże argument jest bez znaku. Użyj, 0aby uzyskać dopełnienie zerowe i 2ustawić szerokość na 2. xlub Xdla małych / wielkich liter szesnastkowych.

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

Edycja : Jeśli czytelnicy są zaniepokojeni stwierdzeniem 2501, że nie jest to w jakiś sposób „poprawne” specyfikatory formatu, sugeruję, aby ponownie przeczytali printfłącze . Konkretnie:

Mimo że% c oczekuje argumentu int, można bezpiecznie przekazać znak char z powodu promocji liczby całkowitej, która ma miejsce, gdy wywoływana jest funkcja wariadyczna.

Prawidłowe specyfikacje konwersji dla typów znaków o stałej szerokości (int8_t itp.) Są zdefiniowane w nagłówku <cinttypes>(C ++) lub <inttypes.h>(C) (chociaż PRIdMAX, PRIuMAX itp. Są synonimami% jd,% ju itd . ) .

Jeśli chodzi o jego punkt widzenia ze znakiem i bez znaku, w tym przypadku nie ma to znaczenia, ponieważ wartości zawsze muszą być dodatnie i łatwo zmieścić się w int ze znakiem. W każdym razie nie ma podpisanego specyfikatora formatu szesnastkowego.

Edytuj 2 : (wydanie „kiedy-przyznać-się mylisz”):

Jeśli przeczytasz aktualny standard C11 na stronie 311 (329 pliku PDF), znajdziesz:

gg: określa, że następuje d, i, o, u, x, lub Xspecyfikacją konwersji stosuje się do signed charlub unsigned charargument (argument nie będzie promowane według promocji całkowitych, ale jego wartość powinna być przekształcony signed charalbo unsigned charprzed wydrukowaniem); lub że następujący nspecyfikator konwersji ma zastosowanie do wskaźnika do signed charargumentu.

Timmmm
źródło
Specyfikatory nie są poprawne dla typu uint8_t. Typy o stałej szerokości używają specjalnych specyfikatorów drukowania. Zobacz:inttypes.h
2501
Tak, ale wszystkie liczby całkowite varargs są niejawnie promowane do int.
Timmmm
Może tak być, ale jeśli zdefiniowano C, zachowanie jest niezdefiniowane, jeśli nie użyjesz prawidłowego specyfikatora.
2501
Ale% x jest poprawnym specyfikatorem. ( chari unsigned charsą promowane do int) [ en.cppreference.com/w/cpp/language/variadic_arguments] . Będziesz musiał używać specyfikatorów PRI tylko do rzeczy, które nie pasują do twojej platformy int- np unsigned int.
Timmmm
%xjest poprawne dla unsigned int not int. Typy char i unsigned char są promowane do int. Ponadto nie ma gwarancji, że uint8_t jest zdefiniowany jako znak bez znaku.
2501
2

Prawdopodobnie drukujesz z tablicy znaków ze znakiem. Wydrukuj z tablicy bez znaku lub zamaskuj wartość za pomocą 0xff: np. Ar [i] & 0xFF. Wartości c0 są rozszerzane ze znakiem, ponieważ ustawiony jest bit wysokiego (znaku).

Richarda Penningtona
źródło
-1

Spróbuj czegoś takiego:

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

Który to produkuje:

$ ./foo 
c0 c0 61 62 63 31 32 33
ObscureRobot
źródło