uint8_t vs unsigned char

231

Jakie są zalety korzystania z uint8_tciągu unsigned charw C?

Wiem, że w prawie każdym systemie uint8_tjest po prostu typedef unsigned char, więc po co go używać?

Lyndon White
źródło

Odpowiedzi:

225

Dokumentuje to twoją intencję - będziesz przechowywać małe liczby zamiast postaci.

Wygląda też ładniej, jeśli używasz innych typów czcionek, takich jak uint16_tlub int32_t.

Mark Ransom
źródło
1
W pierwotnym pytaniu nie było jasne, czy mówiliśmy o typie standardowym, czy nie. Jestem pewien, że na przestrzeni lat istniało wiele odmian tej konwencji nazewnictwa.
Mark Ransom,
8
Jawnie za pomocą unsigned charlub signed chardokumentuje również intencję, ponieważ bez ozdoby charjest to, co pokazuje, że pracujesz z postaciami.
caf
9
Myślałem, że bez ozdoby unsignedjest unsigned intz definicji?
Mark Ransom
5
@endolith, użycie uint8_t do łańcucha nie musi być złe, ale jest zdecydowanie dziwne.
Mark Ransom,
5
@endolith, myślę, że mogę zrobić argument dla uint8_t z tekstem UTF8. Rzeczywiście charwydaje się , że implikuje znak, podczas gdy w kontekście ciągu UTF8 może to być tylko jeden bajt znaku wielobajtowego. Użycie uint8_t może wyjaśnić, że nie należy oczekiwać znaku na każdej pozycji - innymi słowy, że każdy element łańcucha / tablicy jest dowolną liczbą całkowitą, o której nie należy przyjmować żadnych semantycznych założeń. Oczywiście wszyscy programiści C wiedzą o tym, ale może popchnąć początkujących do zadawania właściwych pytań.
tne
70

Żeby być pedantycznym, niektóre systemy mogą nie mieć typu 8-bitowego. Według Wikipedii :

Implementacja jest wymagana do zdefiniowania typów liczb całkowitych o dokładnej szerokości dla N = 8, 16, 32 lub 64 tylko wtedy, gdy ma dowolny typ spełniający wymagania. Nie jest wymagane ich zdefiniowanie dla żadnego innego N, nawet jeśli obsługuje odpowiednie typy.

Nie uint8_tma gwarancji, że będzie istnieć, chociaż będzie to miało miejsce na wszystkich platformach, na których 8 bitów = 1 bajt. Niektóre platformy wbudowane mogą się różnić, ale staje się to bardzo rzadkie. Niektóre systemy mogą definiować chartypy na 16 bitów, w takim przypadku prawdopodobnie nie będzie żadnego typu 8-bitowego.

Poza tym (mniejszym) problemem, odpowiedź @ Mark Ransom jest moim zdaniem najlepsza. Użyj tego, który najlepiej pokazuje, do czego używasz danych.

Zakładam również, że miałeś na myśli uint8_t(standardowy typedef z C99 podany w stdint.hnagłówku), a nie uint_8(nie jest częścią żadnego standardu).

Chris Lutz
źródło
3
@caf, z czystej ciekawości - czy możesz link do opisu niektórych? Wiem, że istnieją, ponieważ ktoś wspomniał o jednym (i powiązany z dokumentacją dla programistów) w comp.lang.c ++. Moderowana dyskusja na temat tego, czy gwarancje typu C / C ++ są zbyt słabe, ale nie mogę już znaleźć tego wątku i zawsze jest przydatny powoływanie się na to w podobnych dyskusjach :)
Pavel Minaev,
3
„Niektóre systemy mogą definiować typy znaków jako 16 bitów, w takim przypadku prawdopodobnie nie będzie żadnego typu 8-bitowego”. - i pomimo pewnych niepoprawnych zastrzeżeń ode mnie, Pavel wykazał w swojej odpowiedzi, że jeśli char ma 16 bitów, to nawet jeśli kompilator zapewnia typ 8-bitowy, nie może go wywoływać uint8_t(lub wpisywać do niego). Wynika to z faktu, że typ 8-bitowy miałby nieużywane bity w reprezentacji pamięci, których uint8_tnie może mieć.
Steve Jessop,
3
Architektura SHARC ma 32-bitowe słowa. Szczegółowe informacje można znaleźć na stronie en.wikipedia.org/wiki/ ...
BCran
2
A procesory DSP C5000 firmy TI (które były w OMAP1 i OMAP2) są 16-bitowe. Myślę, że dla OMAP3 wybrali serię C6000 z 8-bitowym char.
Steve Jessop
4
Zagłębiając się w N3242 - „Roboczy szkic, standard dla języka programowania C ++”, sekcja 18.4.1 <cstdint> streszczenie mówi - typedef unsigned integer type uint8_t; // optional Zasadniczo biblioteka zgodna ze standardem C ++ nie jest wcale potrzebna do zdefiniowania uint8_t (patrz komentarz // opcjonalny )
nightlytrails
43

Chodzi o to, aby napisać kod niezależny od implementacji. unsigned charnie jest gwarantowanym typem 8-bitowym. uint8_tjest (jeśli dostępne).

Mrówka
źródło
4
... jeśli istnieje w systemie, ale będzie to bardzo rzadkie. +1
Chris Lutz
2
cóż, jeśli naprawdę masz problemy z niekompilacją kodu w systemie, ponieważ uint8_t nie istnieje, możesz użyć find i sed, aby automatycznie zmienić wszystkie wystąpienia uint8_t na znak bez znaku lub coś bardziej przydatnego.
bazz
2
@bazz - nie, jeśli zakładasz, że jest to 8-bitowy typ, którego nie możesz - na przykład, aby rozpakować dane zapakowane w inny sposób przez zdalny system. Domniemane założenie jest takie, że przyczyną nieistnienia uint8_t jest procesor, w którym char jest większy niż 8 bitów.
Chris Stratton,
wrzuć asercję asercja (sizeof (niepodpisany znak) == 8);
bazz
3
@bazz niepoprawne stwierdzenie Obawiam się. sizeof(unsigned char)zwróci 11 bajt. ale jeśli char i int systemu są tego samego rozmiaru, na przykład 16-bitów, to sizeof(int)również zwróci1
Toby
7

Jak powiedziałeś, „ prawie każdy system”.

charjest prawdopodobnie jedną z mniej prawdopodobnych zmian, ale kiedy zaczniesz używać uint16_ti znajomych, uint8_tlepiej używasz mieszania, a nawet może być częścią standardu kodowania.

Po prostu zakochany
źródło
7

Z mojego doświadczenia wynika, że ​​istnieją dwa miejsca, w których chcemy używać uint8_t w celu oznaczenia 8 bitów (i uint16_t itp.) I gdzie możemy mieć pola mniejsze niż 8 bitów. Oba miejsca mają znaczenie dla przestrzeni i często musimy spojrzeć na surowy zrzut danych podczas debugowania i musimy być w stanie szybko określić, co to reprezentuje.

Pierwszy dotyczy protokołów RF, szczególnie w systemach wąskopasmowych. W tym środowisku może być konieczne spakowanie jak największej ilości informacji w jedną wiadomość. Drugi dotyczy pamięci flash, w której możemy mieć bardzo ograniczoną przestrzeń (na przykład w systemach wbudowanych). W obu przypadkach możemy użyć struktury danych spakowanych, w których kompilator zajmie się dla nas pakowaniem i rozpakowywaniem:

#pragma pack(1)
typedef struct {
  uint8_t    flag1:1;
  uint8_t    flag2:1;
  padding1   reserved:6;  /* not necessary but makes this struct more readable */
  uint32_t   sequence_no;
  uint8_t    data[8];
  uint32_t   crc32;
} s_mypacket __attribute__((packed));
#pragma pack()

Wybór metody zależy od kompilatora. Konieczne może być także wsparcie kilku różnych kompilatorów z tymi samymi plikami nagłówkowymi. Dzieje się tak w systemach wbudowanych, w których urządzenia i serwery mogą być zupełnie inne - na przykład możesz mieć urządzenie ARM, które komunikuje się z serwerem Linux x86.

Istnieje kilka zastrzeżeń dotyczących używania upakowanych struktur. Największym problemem jest to, że musisz unikać dereferencji adresu członka. W systemach ze słowami wyrównanymi mutibajtami może to spowodować niepoprawny wyjątek i zrzut rdzeniowy.

Niektórzy ludzie martwią się również wydajnością i twierdzą, że użycie tych spakowanych struktur spowolni twój system. Prawdą jest, że za kulisami kompilator dodaje kod, aby uzyskać dostęp do nieprzystosowanych elementów danych. Możesz to zobaczyć patrząc na kod asemblera w twoim IDE.

Ponieważ jednak upakowane struktury są najbardziej przydatne do komunikacji i przechowywania danych, dane można wyodrębnić do niepakowanej reprezentacji podczas pracy z nimi w pamięci. Zwykle i tak nie musimy pracować z całym pakietem danych w pamięci.

Oto kilka istotnych dyskusji:

pragma pack (1) ani __attribute__ ((wyrównany (1))) działa

Czy pakiet gcc __attribute __ ((pack)) / #pragma jest niebezpieczny?

http://solidsmoke.blogspot.ca/2010/07/woes-of-structure-packing-pragma-pack.html

Tereus Scott
źródło
6

Jest mało Z punktu widzenia przenośności charnie może być mniejszy niż 8 bitów i nic nie może być mniejsze niż char, więc jeśli dana implementacja C ma 8-bitową liczbę całkowitą bez znaku, będzie char. Alternatywnie może nie mieć go wcale, w którym to momencie jakiekolwiek typedefsztuczki są dyskusyjne.

Można go użyć do lepszego udokumentowania kodu w tym sensie, że jest jasne, że potrzebujesz tam 8-bitowych bajtów i nic więcej. Ale w praktyce jest to rozsądne oczekiwanie praktycznie już wszędzie (istnieją platformy DSP, na których to nieprawda, ale szanse na uruchomienie tam kodu są niewielkie, a równie dobrze możesz popełnić błąd, używając statycznego potwierdzenia na górze programu na taka platforma).

Pavel Minaev
źródło
7
@Skizz - Nie, standard wymaga unsigned charzdolności do przechowywania wartości od 0 do 255. Jeśli możesz to zrobić w 4 bitach, moja czapka jest dla ciebie.
Chris Lutz
1
„byłoby trochę bardziej nieporęczne” - nieporęczne w tym sensie, że musiałbyś iść (pływać, złapać samolot itp.) aż do miejsca, gdzie był pisarz kompilatora, uderzyć ich w tył głowy i dodaj je uint8_tdo implementacji. Zastanawiam się, czy kompilatory dla procesorów DSP z 16-bitowymi znakami zwykle implementują uint8_t, czy nie?
Steve Jessop,
6
Nawiasem mówiąc, według drugiej myśli jest to prawdopodobnie najprostszy sposób powiedzenia „Naprawdę potrzebuję 8 bitów” - #include <stdint.h>i użycia uint8_t. Jeśli platforma ma to, da ci ją. Jeśli platforma go nie ma, twój program się nie skompiluje, a przyczyna będzie jasna i prosta.
Pavel Minaev,
2
Nadal nie ma cygara, przepraszam: „W przypadku liczb całkowitych bez znaku innych niż znak bez znaku bity reprezentacji obiektu powinny być podzielone na dwie grupy: bity wartości i bity dopełniające ... Jeśli jest N bitów wartości, każdy bit powinien reprezentować inny potęga 2 między 1 a 2 ^ (N-1), dzięki czemu obiekty tego typu będą w stanie reprezentować wartości od 0 do 2 ^ (N-1) przy użyciu czystej reprezentacji binarnej ... Nazwa typedef intN_t oznacza podpisany typ liczb całkowitych o szerokości N, bez bitów wypełniających i reprezentacji uzupełnienia do dwóch ”.
Pavel Minaev
1
Jeśli potrzebujesz tylko arytmetycznego modulo, niepodpisane pole bitowe poradzi sobie dobrze (jeśli jest niewygodne). To wtedy, gdy potrzebujesz, powiedzmy, tablicy oktetów bez wypełnienia, wtedy jesteś SOL. Morał tej historii nie polega na kodowaniu DSP i trzymaniu się właściwych, uczciwych wobec Boga architektur 8-bitowych znaków :)
Pavel Minaev,
4

Jest to naprawdę ważne na przykład podczas pisania analizatora sieci. nagłówki pakietów są definiowane przez specyfikację protokołu, a nie sposób działania kompilatora C dla konkretnej platformy.

VP.
źródło
w przeszłości, kiedy o to pytałem, definiowałem prosty protokół komunikacji szeregowej.
Lyndon White
2

Niemal na każdym systemie poznałem uint8_t == unsigned char, ale nie gwarantuje tego standard C. Jeśli próbujesz napisać przenośny kod i ma on znaczenie, dokładnie jaki jest rozmiar pamięci, użyj uint8_t. W przeciwnym razie użyj znaku bez znaku.

atlpeg
źródło
3
uint8_t zawsze dopasowuje zakres i rozmiar unsigned charoraz dopełnianie (brak), gdy unsigned char jest 8-bitowe. Kiedy unsigned charnie jest 8-bitowy, uint8_tnie istnieje.
chux - Przywróć Monikę
@chux, Czy masz odniesienie do dokładnego miejsca w standardzie, w którym to mówi? Jeśli unsigned charjest 8-bitowy, czy uint8_tna pewno jest typedefnim, a nie typedeftypu rozszerzonej liczby całkowitej bez znaku ?
hsivonen
@hsivonen „dokładne miejsce w standardzie, gdzie to mówi?” -> Nie - jeszcze spójrz na 7.20.1.1. Można go łatwo wydedukować, podobnie jak unsigned char/signed char/charnajmniejszy typ - nie mniejszy niż 8 bitów. unsigned charnie ma wyściółki. Aby uint8_tbyć, musi być 8-bitowy, bez dopełnienia, istnieje z powodu implementacji podanej typu liczby całkowitej: spełniającej minimalne wymagania unsigned char. Jeśli chodzi o „... gwarantujemy, że będzie to typedef ...” wygląda na dobre pytanie do opublikowania.
chux - Przywróć Monikę