Co to jest znak bez znaku?


479

W C / C ++ do czego unsigned charsłuży an ? Czym różni się od zwykłego char?

Odpowiedzi:


548

W C ++ istnieją trzy różne typy znaków:

  • char
  • signed char
  • unsigned char

Jeśli używasz typów znaków dla tekstu , użyj niekwalifikowanego char:

  • jest to rodzaj literałów znakowych takich jak 'a'lub '0'.
  • jest to typ, który tworzy ciągi C. "abcde"

Działa również jako wartość liczbowa, ale nie jest określone, czy ta wartość jest traktowana jako podpisana czy niepodpisana. Uważaj na porównania postaci przez nierówności - chociaż ograniczając się do ASCII (0-127), jesteś prawie bezpieczny.

Jeśli używasz typów znaków jako liczb , użyj:

  • signed char, co daje co najmniej zakres od -127 do 127. (Od -128 do 127 jest powszechne)
  • unsigned char, co daje co najmniej zakres od 0 do 255.

„Przynajmniej”, ponieważ standard C ++ podaje tylko minimalny zakres wartości, który musi obejmować każdy typ liczbowy. sizeof (char)wymagana jest wartość 1 (tj. jeden bajt), ale bajt teoretycznie może wynosić na przykład 32 bity. sizeofnadal będzie zgłosić swoją wielkość, jak1 - co oznacza, że mogłyby mieć sizeof (char) == sizeof (long) == 1.


4
Żeby było jasne, czy możesz mieć 32-bitowe znaki i 32-bitowe liczby całkowite oraz mieć sizeof (int)! = Sizeof (char)? Wiem, że standard mówi sizeof (char) == 1, ale czy względny sizeof (int) opiera się na rzeczywistej różnicy wielkości czy różnicy zasięgu?
Joseph Garvin

14
+1. Istnieją jednak cztery różne typy znaków w C ++, wchar_t jest jednym z nich.
Eric Z

11
od c ++ 11 masz 6 różnych typów: char, podpisany char, niepodpisany char, wchar_t, char16_t, char32_t.
marcinj

12
@unheilig Często wstawia się spację, sizeofponieważ nie jest to funkcja, ale operator. Jeszcze lepszym stylem jest pominięcie nawiasu przy przyjmowaniu wielkości zmiennej. sizeof *plub sizeof (int). Dzięki temu szybko staje się jasne, czy ma zastosowanie do typu lub zmiennej. Podobnie zbędne jest umieszczanie nawiasów po return. To nie jest funkcja.
Patrick Schlüter

3
char: to rodzaj literałów znakowych takich jak 'a'lub '0'.” jest prawdziwe w C ++, ale nie w C. W C 'a'jest int.
chux - Przywróć Monikę

92

Jest to zależne od implementacji, ponieważ standard C NIE definiuje podpisu char. W zależności od platformy char może być signedlub unsigned, więc musisz jawnie o to poprosić signed charlub od unsigned chartego zależy twoja implementacja. Po prostu użyjchar jeśli zamierzasz reprezentować znaki z ciągów, ponieważ będą one pasować do tego, co twoja platforma umieszcza w ciągu.

Różnica między signed chari unsigned charjest taka, jak można się spodziewać. Na większości platform signed charbędzie 8-bitową liczbą uzupełnień do dwóch, od -128do 127, i unsigned charbędzie 8-bitową liczbą całkowitą bez znaku ( 0do 255). Uwaga standard nie wymaga, aby chartypy miały 8 bitów, tylko ten sizeof(char)zwrot 1. Możesz dostać się do liczby bitów w znaku z CHAR_BITin limits.h. Istnieje jednak niewiele, jeśli w ogóle, platform, na których będzie to coś innego niż 8.

Ładne streszczenie tego problemu znajduje się tutaj .

Jak wspomnieli inni, odkąd to opublikowałem, lepiej jest używać int8_ti uint8_tjeśli naprawdę chcesz reprezentować małe liczby całkowite.


2
podpisany znak ma tylko minimalny zakres od -127 do 127, a nie od -128 do 127
12431234123412341234123 28.01.17

3
@ 12431234123412341234123: Technicznie prawdziwe, ponieważ standard C definiuje -127 do 127 jako minimalny zakres. Wzywam cię jednak do znalezienia platformy, która nie wykorzystuje arytmetyki uzupełnień do dwóch. Na prawie każdej nowoczesnej platformie rzeczywisty zakres podpisanych znaków będzie wynosił od -128 do 127.
Todd Gamblin

CHAR_BITzgodnie z normą wymagane jest co najmniej 8 bitów.
martinkunev

39

Ponieważ czuję, że jest to naprawdę potrzebne, chcę tylko podać niektóre zasady C i C ++ (są one pod tym względem takie same). Po pierwsze, wszystkie bity od unsigned charudziału w ustalaniu wartości, jeśli jakiekolwiek unsigned char obiektu. Po drugie, unsigned charjest wyraźnie określony jako niepodpisany.

Teraz rozmawiałem z kimś o tym, co się dzieje, kiedy konwertujesz wartość -1typu int na unsigned char. Odrzucił ideę, że wynikowe unsigned charma wszystkie bity ustawione na 1, ponieważ martwił się reprezentacją znaków. Ale nie musi. Bezpośrednio z tej reguły wynika, że ​​konwersja robi to, co jest zamierzone:

Jeśli nowy typ nie jest podpisany, wartość jest konwertowana przez wielokrotne dodawanie lub odejmowanie wartości większej niż maksymalna wartość, którą można przedstawić w nowym typie, dopóki wartość nie znajdzie się w zakresie nowego typu. ( 6.3.1.3p2w wersji roboczej C99)

To jest opis matematyczny. C ++ opisuje to w kategoriach rachunku modułowego, który daje tę samą regułę. W każdym razie, co nie gwarantuje, że wszystkie bity w całkowitej -1są jednym przed konwersją. Co więc mamy, abyśmy mogli twierdzić, że wynikowy unsigned charma wszystkie CHAR_BITbity zmienione na 1?

  1. Wszystkie bity uczestniczą w określaniu jego wartości - to znaczy, że w obiekcie nie występują bity wypełniające.
  2. Dodanie tylko jeden raz UCHAR_MAX+1na -1przyniesie wartość w zakresie, a mianowicieUCHAR_MAX

Właściwie to wystarczy! Tak więc, kiedy tylko chcesz mieć unsigned charwszystkie swoje bity, robisz to

unsigned char c = (unsigned char)-1;

Wynika z tego również, że konwersja to nie tylko obcinanie bitów wyższego rzędu. Szczęśliwym wydarzeniem dla uzupełnienia dwóch jest to, że jest to tylko obcięcie, ale niekoniecznie tak samo jest w przypadku innych reprezentacji znaków.


2
Dlaczego nie po prostu użyć UCHAR_MAX?
Nicolás,

1
Bo (unsigned type)-1to jakiś idiom. ~0nie jest.
Patrick Schlüter

1
jeśli mam coś takiego int x = 1234i char *y = &x. Binarna reprezentacja 1234 jest 00000000 00000000 00000100 11010010. Moja maszyna jest małym endianem, więc odwraca ją i zapisuje w pamięci 11010010 00000100 00000000 00000000LSB na pierwszym miejscu. Teraz główna część. jeśli użyję printf("%d" , *p). printfodczyta pierwszy bajt 11010010tylko wyjście -46, ale 11010010jest 210tak dlatego to wydrukować -46. Naprawdę jestem zdezorientowany. Wydaje mi się, że jakiś znak do promocji liczb całkowitych robi coś, ale nie wiem.
Suraj Jain,

27

Na przykład zastosowania niepodpisanego znaku :

unsigned charjest często stosowany w grafice komputerowej, która bardzo często (choć nie zawsze) przypisuje jeden bajt do każdego komponentu koloru. Często zdarza się, że kolor RGB (lub RGBA) reprezentowany jest przez 24 (lub 32) bity każdy unsigned char. Ponieważ unsigned charwartości mieszczą się w zakresie [0,255], są one zazwyczaj interpretowane jako:

  • 0 oznacza całkowity brak danego składnika koloru.
  • 255, co oznacza 100% danego koloru pigmentu.

Tak więc otrzymałeś czerwony RGB jako (255,0,0) -> (100% czerwony, 0% zielony, 0% niebieski).

Dlaczego nie użyć signed char? Arytmetyka i zmiana bitów staje się problematyczna. Jak już wyjaśniono, signed charzakres a jest zasadniczo przesunięty o -128. Bardzo prostą i naiwną (najczęściej nieużywaną) metodą konwersji RGB na skalę szarości jest uśrednienie wszystkich trzech składników koloru, ale napotyka to problemy, gdy wartości składników koloru są ujemne. Średnie czerwone (255, 0, 0) to (85, 85, 85) przy zastosowaniu unsigned chararytmetyki. Jednak jeśli wartości byłyby signed chars (127, -128, -128), otrzymalibyśmy (-99, -99, -99), co byłoby (29, 29, 29) w naszej unsigned charprzestrzeni, co jest niepoprawne .


13

Jeśli chcesz użyć znaku jako małej liczby całkowitej, najbezpieczniejszym sposobem na to jest użycie typów int8_ti uint8_t.


2
Nie jest to dobry pomysł: int8_ti uint8_tsą opcjonalne i nie zdefiniowano na architekturach gdzie wielkość bajt nie jest dokładnie 8 bitów. I odwrotnie, signed chari unsigned charzawsze są dostępne i gwarantują, że mieszczą co najmniej 8 bitów. Może to być powszechny sposób, ale nie najbezpieczniejszy .
chqrlie

2
To jest komentarz, nie odpowiada na pytanie.
Lundin

@chqrlie Więc masz na myśli, że najbezpieczniejszym sposobem na reprezentowanie małej liczby całkowitej, jeśli chcesz zaoszczędzić pamięć, jest zachowanie signed chari unsigned char? A może poleciłbyś lepszą „bezpieczniejszą” alternatywę w tym konkretnym przypadku? Na przykład trzymać się „prawdziwych” liczb całkowitych signed inti unsigned intzamiast tego z jakiegoś powodu?
RobertS wspiera Monikę Cellio

@ RobertS-ReinstateMonica: Używanie signed chari unsigned charjest przenośne dla wszystkich zgodnych implementacji i pozwoli zaoszczędzić miejsce, ale może spowodować pewne zwiększenie rozmiaru kodu. W niektórych przypadkach można zaoszczędzić więcej miejsca, przechowując małe wartości w polach bitowych lub pojedynczych bitach zwykłych liczb całkowitych. Nie ma absolutnej odpowiedzi na to pytanie, znaczenie tego podejścia zależy od konkretnego rozpatrywanego przypadku. Ta odpowiedź i tak nie odnosi się do pytania.
chqrlie,

10

unsigned charprzyjmuje tylko wartości dodatnie .... jak 0 do 255

natomiast

signed charprzyjmuje zarówno dodatnie, jak i ujemne wartości .... jak -128 do +127


9

chari unsigned charnie ma gwarancji, że będą to typy 8-bitowe na wszystkich platformach - są gwarantowane, że są 8-bitowe lub większe. Niektóre platformy mają 9-bitowe, 32-bitowe lub 64-bitowe bajty . Jednak najpopularniejsze obecnie platformy (Windows, Mac, Linux x86 itp.) Mają 8-bitowe bajty.


8

signed char ma zakres od -128 do 127; unsigned charma zakres od 0 do 255.

char będzie równoważny znakowi podpisanemu lub znakowi niepodpisanemu, w zależności od kompilatora, ale jest odrębnym typem.

Jeśli używasz ciągów w stylu C, po prostu użyj char. Jeśli musisz używać znaków do obliczeń arytmetycznych (dość rzadko), podaj wyraźnie podpisane lub niepodpisane w celu przenoszenia.


8

An unsigned charjest bajtem bez znaku (od 0 do 255). Być może myślisz o charbyciu „postacią”, ale tak naprawdę jest to wartość liczbowa. Normalny charjest podpisany, więc masz 128 wartości, które są mapowane na znaki przy użyciu kodowania ASCII. Ale w obu przypadkach to, co przechowujesz w pamięci, to wartość bajtowa.


7

Jeśli chodzi o wartości bezpośrednie, zwykły znak jest używany, gdy wiadomo, że wartości są pomiędzy, CHAR_MINa CHAR_MAXgdy znak bez znaku zapewnia podwójny zakres na dodatnim końcu. Na przykład, jeśli CHAR_BITjest to 8, zakres wartości regularnych charjest gwarantowany tylko na [0, 127] (ponieważ może być podpisany lub niepodpisany), podczas gdy unsigned charbędzie wynosił [0, 255] isigned char będzie wynosił [-127, 127].

Pod względem tego, do czego jest używany, standardy pozwalają bezpośrednio konwertować obiekty POD (zwykłe stare dane) na tablicę znaków bez znaku. Umożliwia to sprawdzenie reprezentacji i wzorów bitowych obiektu. Ta sama gwarancja bezpiecznego znakowania czcionek nie istnieje dla znaków ani znaków podpisanych.


W rzeczywistości najczęściej będzie to [-128, 128].
RastaJedi 24.04.16

Standardy tylko formalnie określić reprezentacji obiektów jako sekwencję o unsigned char, a nie tablicy Specyficznie, każdy „konwersja” jest zdefiniowany tylko formalnie przez skopiowanie od obiektu do rzeczywistego, stwierdził tablicę z unsigned chari następnie kontroli ostatnich. Nie jest jasne, czy OR można bezpośrednio zinterpretować jako taką tablicę, z uwzględnieniem dopuszczalnej arytmetyki wskaźnika, tj. Czy ==„tablica” „tablica” w tym zastosowaniu. Mamy nadzieję, że uda się to wyjaśnić. Na szczęście, ponieważ ta dwuznaczność naprawdę mnie ostatnio denerwuje.
underscore_d

1
@RastaJedi Nie, nie będzie. Nie może Zakres -128 ... + 128 jest fizycznie niemożliwy do przedstawienia za pomocą 8 bitów. Ta szerokość obsługuje tylko 2 ^ 8 == 256 wartości dyskretnych, ale -128 ... + 128 = 2 * 128 + 1 dla 0 = 257. Reprezentacja wielkości znaku pozwala na -127 ... + 127, ale ma 2 (bipolarne) zera. Reprezentacja uzupełnienia do dwóch utrzymuje pojedyncze zero, ale uzupełnia zakres, mając jeszcze jedną wartość po stronie ujemnej; pozwala na -128 ... + 127. (I tak dalej dla obu przy większych szerokościach bitów.)
podkreślenie_d

Odnośnie mojego drugiego komentarza, rozsądnie jest założyć, że możemy wziąć wskaźnik do 1. unsigned charOR, a następnie kontynuować korzystanie ++ptrz tego miejsca, aby odczytać każdy jego bajt ... ale AFAICT, nie jest specjalnie zdefiniowany jako dozwolony, więc jesteśmy pozostawiono, aby wywnioskować, że „prawdopodobnie jest OK” z wielu innych fragmentów (i na wiele sposobów, samego istnienia memcpy) w standardzie, podobnie jak układanka. Co nie jest idealne. Cóż, być może brzmienie ostatecznie się poprawi. Oto problem CWG, o którym wspomniałem, ale brakowało miejsca na link - open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1701
underscore_d

@underscore_d przepraszam, to była literówka. [-128, 127] to, co miałem na myśli: p. Tak, wiem o podwójnych zerach (zero i zero) ze znakiem / wielkością. Musiałem być zmęczony: str.
RastaJedi

5

unsigned charjest sercem wszystkich sztuczek. W prawie WSZYSTKIM kompilatorze dla WSZYSTKICH platform jest unsigned charto po prostu bajt i liczba całkowita bez znaku (zwykle) 8 bitów, które mogą być traktowane jako mała liczba całkowita lub paczka bitów.

Nałogowo, jak powiedział ktoś inny, standard nie definiuje znaku znaku. tak masz 3 różne chartypy: char, signed char, unsigned char.


1
Nieco oszustwo, inaczej kręcenie się lub hakowanie jest rzeczywiście przyczyną uzależnienia ;-)
chqrlie

3
Zera powodują problemy. Aby uniknąć uzależnienia od kręcenia się, trzymaj się z dala od okropnych kawałków.
DragonLord

5

Jeśli podoba Ci się stosując różne typy i długości określonej signedness, jesteś prawdopodobnie lepiej z uint8_t, int8_t, uint16_titd po prostu dlatego, że robią dokładnie to, co mówią.


4

Niektórzy google znaleźli to , gdzie ludzie rozmawiali o tym.

Znak bez znaku jest w zasadzie pojedynczym bajtem. Tak więc użyłbyś tego, jeśli potrzebujesz jednego bajtu danych (na przykład, może chcesz go użyć do włączania i wyłączania flag, aby były przekazywane do funkcji, jak to często robi się w interfejsie API Windows).


4

Znak bez znaku używa bitu zarezerwowanego dla znaku zwykłego znaku jako innej liczby. Zmienia to zakres na [0–255] w przeciwieństwie do [-128–127].

Zasadniczo znaki bez znaku są używane, gdy nie chcesz znaku. Będzie to miało znaczenie podczas robienia rzeczy, takich jak przesuwanie bitów (shift wydłuża znak) i innych rzeczy, gdy ma się do czynienia z char jako bajtem, a nie z użyciem go jako liczby.


4

unsigned charprzyjmuje tylko wartości dodatnie: od 0 do 255, a signed charprzyjmuje wartości dodatnie i ujemne: od -128 do +127.


3

cytowany z książki „la cage programowania”:

Kwalifikator signedlub unsignedmoże być zastosowany do znaku lub dowolnej liczby całkowitej. liczby bez znaku są zawsze dodatnie lub zerowe i są zgodne z prawami arytmetycznego modułu 2 ^ n, gdzie n jest liczbą bitów w typie. Na przykład, jeśli znaki to 8 bitów, zmienne znakowane bez znaku mają wartości od 0 do 255, podczas gdy znaki podpisane mają wartości od -128 do 127 (w maszynie dopełniającej dwa.) To, czy zwykłe znaki są podpisane czy niepodpisane, jest maszyną -zależne, ale znaki do wydruku są zawsze dodatnie.


2

signed chari unsigned charoba reprezentują 1 bajt, ale mają różne zakresy.

   Type        |      range
-------------------------------
signed char    |  -128 to +127
unsigned char  |     0 to 255

W signed charjeśli weźmiemy pod uwagę char letter = 'A', „A” ma reprezentować binarnie z 65 w ASCII/Unicode, przypadku 65 mogą być przechowywane, -65 może być również przechowywane. Nie ma tam ujemnych wartości binarnych, więc ASCII/Unicodenie musisz się martwić o wartości ujemne.

Przykład

#include <stdio.h>

int main()
{
    signed char char1 = 255;
    signed char char2 = -128;
    unsigned char char3 = 255;
    unsigned char char4 = -128;

    printf("Signed char(255) : %d\n",char1);
    printf("Unsigned char(255) : %d\n",char3);

    printf("\nSigned char(-128) : %d\n",char2);
    printf("Unsigned char(-128) : %d\n",char4);

    return 0;
}

Wynik -:

Signed char(255) : -1
Unsigned char(255) : 255

Signed char(-128) : -128
Unsigned char(-128) : 128
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.