c++ c

Czy mogę zamienić niepodpisany znak na znak i odwrotnie?


Chcę użyć funkcji, która oczekuje takich danych:
void process(char *data_in, int data_len);

Więc tak naprawdę obsługuje tylko niektóre bajty.
Ale wygodniej mi jest pracować z „unsigned char”, jeśli chodzi o nieprzetworzone bajty (z jakiegoś powodu bardziej poprawne jest zajmowanie się tylko dodatnimi wartościami od 0 do 255), więc moje pytanie brzmi:
Czy zawsze mogę bezpiecznie przekazać
unsigned char *
do tej funkcji?
Innymi słowy:
  • Czy jest zagwarantowane, że mogę bezpiecznie konwertować (rzucać) między char i unsigned char, do woli, bez utraty informacji
  • Czy mogę bezpiecznie konwertować (rzucać) między wskaźnikami na znak i znak bez znaku do woli, bez utraty informacji

Bonus: czy odpowiedź jest taka sama w C i C ++?
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Krótka odpowiedź brzmi: tak, jeśli używasz wyraźnej obsady, ale aby szczegółowo to wyjaśnić, należy wziąć pod uwagę trzy aspekty:

1) legalność przekształcenia
Konwersja między
signed T *
i
unsigned T *
(dla niektórych typów
T
) w dowolnym kierunku jest zwykle możliwa, ponieważ oryginalny typ można najpierw przekonwertowane na
void *
(jest to standardowa konwersja, §4.10), a
void *
można przekonwertować na typ docelowy za pomocą jawnego
static_cast
(§5.2.9/trzynaście):
static_cast<unsigned char*>(static_cast<void *>(data_in))

Można to skrócić (§5.2.10/7) w następujący sposób:
reinterpret_cast<unsigned char *>(data_in)

ponieważ
char
jest typem standardowego układu (§3.9.1/7.8 i §3.9/9), a podpisywanie nie zmienia wyrównania (§3.9.1/1). Można go również napisać jako obsadę w stylu C:
(unsigned char *)(data_in)

Ponownie działa to w obie strony, od
unsigned *
do
signed *
iz powrotem. Istnieje również gwarancja, że ​​jeśli zastosujesz tę procedurę w jedną stronę, a potem z powrotem, wartość wskaźnika (czyli adres, na który wskazuje) nie zmieni się (§5.2.10/7).
Dotyczy to nie tylko konwersji między
signed char *
i
unsigned char *
, ale także
char *
/
unsigned char *
i
char *
/
signed char *
. (
char
,
signed char
i
unsigned char
to formalnie trzy różne typy, §3.9.1/1.)
Żeby było jasne, nie ma znaczenia, której z trzech metod rzucania używasz, ale powinieneś użyć jednej. Zwykłe przekazywanie wskaźnika nie zadziała, ponieważ konwersja, chociaż legalna, nie jest konwersją standardową, więc nie zostanie wykonana niejawnie (kompilator zgłosi błąd, jeśli spróbujesz).

2) jasno określony dostęp do wartości
Co się stanie, jeśli wyłuskujemy wskaźnik wewnątrz funkcji, to znaczy wykonujemy
* data_in
, aby uzyskać wartość glvalue dla bazowego znaku; czy jest dobrze zdefiniowany i legalny? Odpowiednią zasadą jest tutaj ścisła reguła antyaliasingu (§ 3.10/10):

Jeśli program próbuje uzyskać dostęp do przechowywanej wartości obiektu za pośrednictwem

glvalue

typu innego niż jeden z poniższych, to zachowanie jest niezdefiniowane:
  • [...]
  • typ, który jest typem ze znakiem lub bez znaku odpowiadającym typowi dynamicznemu obiektu,
  • [...]
  • wpisz
    char
    lub
    unsigned char
    .
Zatem dostęp do
signed char
(lub
char
) za pośrednictwem
unsigned char *
(lub
char
) i na odwrót nie jest zakazana przez tę zasadę - powinieneś to zrobić bez żadnych problemów.

3) wartości wynikowe
Czy po wyłuskaniu konwertowanego typu wskaźnika możesz pracować z uzyskaną wartością? Należy pamiętać, że konwersja i dereferencja wskaźnika opisanego powyżej jest równoznaczne z reinterpretacją (nie modyfikowaniem!) Wzorca bitowego przechowywanego pod adresem symbolu. Więc co się dzieje, gdy wzorzec bitowy dla znaku ze znakiem jest interpretowany jako wzorzec dla znaku bez znaku (lub odwrotnie)?
Przechodząc od niepodpisanego do podpisanego

typowy efekt

oznacza, że ​​dla wartości od 0 do 128 nic się nie dzieje, a wartości powyżej 128 stają się ujemne. Podobnie w odwrotnej kolejności: podczas przechodzenia od znaku do znaku bez znaku wartości ujemne będą wyświetlane jako wartości większe niż 128.
Ale to zachowanie jest w rzeczywistości

sprawa nie jest gwarantowana

standard. Jedyną standardową gwarancją jest to, że dla wszystkich trzech typów,
char
,
unsigned char
i
signed char
, wszystkie bity (niekoniecznie 8 przez znak sposób) są używane do reprezentowania wartości. Dlatego jeśli interpretujesz jedną jako drugą, wykonasz wiele kopii, a następnie zapiszesz je z powrotem w ich pierwotnej lokalizacji, możesz być pewien, że nie nastąpi utrata informacji (tak jak żądałeś), ale niekoniecznie będziesz wiedział, jakie te wartości Są włączone. Mają na myśli (przynajmniej nie w całkowicie przenośny sposób).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

znak bez znaku
lub
znak ze znakiem
to tylko interpretacja: nie następuje odwrócenie.
Ponieważ przetwarzasz bajty, aby pokazać zamiar, lepiej byłoby zadeklarować jako
void process(unsigned char *data_in, int data_len);

[Jak zauważył redaktor: zwykły
znak
może być podpisany lub niepodpisany. Standardy C i C ++ wyraźnie na to zezwalają (jest to zawsze typ oddzielny od
unsigned char
lub
signed char
, ale ma taki sam zakres jak jeden z nich)]
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Tak, zawsze możesz przekonwertować ze znaku na znak bez znaku & amp; i

przeciwnie

nie ma problemu. Jeśli uruchomisz poniższy kod i porównasz go z tabelą ASCII (zobacz.

http://www.asciitable.com/
http://www.asciitable.com/), możesz sam zobaczyć dowód i jak C/C ++ radzi sobie z konwersjami - działają dokładnie tak samo:
#include "stdio.h"
int main(void) {
//converting from char to unsigned char
char c = 0;
printf("%d byte(s)\n", sizeof(char));// result: 1byte, i.e. 8bits, so there are 2^8=256 values that a char can store.
for (int i=0; i<256; i++){
printf("int value: %d - from: %c\tto: %c\n", c, c, (unsigned char) c);
c++;
}//converting from unsigned char to char
unsigned char uc = 0;
printf("\n%d byte(s)\n", sizeof(unsigned char));
for (int i=0; i<256; i++){
printf("int value: %d - from: %c\tto: %c\n", uc, uc, (char) uc);
uc++;
}
}

Nie opublikuję wyjścia, ponieważ jest w nim za dużo wierszy! Na wyjściu można zauważyć, że w pierwszej połowie każdej sekcji, tj. Zaczynając od i = 0: 127, następuje konwersja znaków na znaki bez znaku i

przeciwnie

działa dobrze bez żadnych zmian i strat.
Jednak z i = 128: 255 znaków i znaków bez znaku nie można odtworzyć lub będziesz mieć inne dane wyjściowe, ponieważ unsigned char przechowuje wartości z [0: 256], a char przechowuje wartości w interwale [-128: 127 ]) ... Jednak zachowanie w drugiej połowie jest nieistotne, ponieważ w C/C ++ generalnie prowadzisz tylko ze znakami/bez znaku jako znakami ASCII, które mogą przyjmować tylko 128 różnych wartości, a pozostałe 128 wartości (dodatnie dla chars lub minus dla znaków bez znaku) nigdy nie są używane.
Jeśli nigdy nie umieścisz wartości w znaku, który nie reprezentuje znaku, i nigdy nie umieścisz wartości w znaku bez znaku, który nie reprezentuje znaku, wszystko będzie w porządku!
Dodatkowo: nawet jeśli użyjesz UTF-8 lub innego kodowania (dla znaków specjalnych) w swoich ciągach C/C ++, wszystko, co ma tego rodzaju rzutowanie, będzie OK, na przykład używając UTF-8 (ref.

http://lwp.interglacial.com/appf_01.htm
http://lwp.interglacial.com/appf_01.htm
):
char hearts[] = {0xe2, 0x99, 0xa5, 0x00};
char diamonds[] = {0xe2, 0x99, 0xa6, 0x00};
char clubs[] = {0xe2, 0x99, 0xa3, 0x00};
char spades[] = {0xe2, 0x99, 0xa0, 0x00};
printf("hearts (%s)\ndiamonds (%s)\nclubs (%s)\nspades (%s)\n\n", hearts, diamonds, clubs, spades);

wynik tego kodu będzie następujący:

serca (♥)

moje diamenty (♦)

kluby (♣)

łopaty (♠)




nawet jeśli rzucisz każdą z jego postaci na znaki bez znaku.
Więc:
  • "can I always safely pass a unsigned char * into this function"Tak!
  • "Czy jest zagwarantowane, że mogę bezpiecznie konwertować (rzucać) między chara i unsigned char, do woli, bez utraty informacji" Tak!
  • "Czy mogę bezpiecznie rzucać między wskaźnikami char i unsigned char, do woli, bez utraty informacji" Tak!
  • "is the answer same in C and C++"Tak!
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możesz przekazać wskaźnik do innego typu
char
, ale może być konieczne jawne rzutowanie. Gwarantujemy, że wskaźniki mają ten sam rozmiar i te same wartości. Podczas konwersji nie nastąpi utrata informacji.
Jeśli chcesz przekonwertować
char
na
unsigned char
wewnątrz funkcji, po prostu przypisz wartość
char
do
unsigned char
zmienna lub rzutowanie wartości
char
na
unsigned char
.
Jeśli chcesz przekonwertować
unsigned char
na
char
bez utraty danych, jest to trochę trudne, ale nadal możliwe:
#include <limits.h>char uc2c(unsigned char c)
{
#if CHAR_MIN == 0
// char is unsigned
return c;
#else
// char is signed
if (c <= CHAR_MAX)
return c;
else
// ASSUMPTION 1: int is larger than char
// ASSUMPTION 2: integers are 2's complement
return c - CHAR_MAX - 1 - CHAR_MAX - 1;
#endif
}

Ta funkcja konwertuje
unsigned char
na
char
, dzięki czemu wartość zwracana może zostać przekonwertowana z powrotem na tę samą wartość
unsigned char
co parametr.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Pod względem semantyki

przejścia

między
unsigned char *
a
char *
są bezpieczne i nawet jeśli są rzutowane między nimi, jak w c ++.
Jednak rozważ następujący przykład kodu:
#include "stdio.h"void process_unsigned(unsigned char *data_in, int data_len) {
int i=data_len;
unsigned short product=1; for(; i--; product*=data_in[i])
; for(i=sizeof(product); i--; ) {
data_in[i]=((unsigned char *)&product)[i];
printf("%d\r\n", data_in[i]);
}
}void process(char *data_in, int data_len) {
int i=data_len;
unsigned short product=1; for(; i--; product*=data_in[i])
; for(i=sizeof(product); i--; ) {
data_in[i]=((unsigned char *)&product)[i];
printf("%d\r\n", data_in[i]);
}
}void main() {
unsigned char
a[]={1, -1},
b[]={1, -1}; process_unsigned(a, sizeof(a));
process(b, sizeof(b));
getch();
}

Wyjście:

0
255
-1
-1Cały kod wewnątrz
process_unsigned
i
process
jest po prostu

IDENTICAL
... Jedyna różnica to brak znaku i podpis. Ten przykład pokazuje, że kod w

czarna skrzynka

naprawdę pod wpływem

SIGN

i

nic

nie jest gwarantowane między wywoływanym a dzwoniącym.
Dlatego powiedziałbym, że ma to zastosowanie tylko do

przechodzący

ale żadne inne możliwości nie są gwarantowane.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Naprawdę musisz przyjrzeć się kodowi przed
process ()
, aby sprawdzić, czy bez znaku można bezpiecznie przekazać. Jeśli funkcja używa znaków jako indeksu w tablicy, to nie, nie można używać danych bez znaku.

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się