Wie ktoś jak przeprowadzić taką konwersję w C?
Jeśli masz pewność, że wartość zapisana w tym int nie przekroczy "pojemności" uint8_t i nie będzie ujemna (czyli 0-255), to:
int a = 42;
uint8_t b = (uint8_t)a;
W innych przypadkach będziesz po konwersji miał nieprawidłowe wartości.
Pamiętaj, że definicja uint8_t siedzi w stdint.h
Dla ścisłości dodam, że to się nazywa rzutowanie, nie zaś konwersja.
A co jeśli int zajmuje więcej niż 1 bajt? Bo uint8_t chyba właśnie 1 bajt?
int zajmuje zazwyczaj 4 bajty w dzisiejszych czasach. Generalnie po rzutowaniu otrzymujesz najmłodszy bajt z inta, chociaż nie wiem czy to jest wymuszone w specyfikacji C++.
Chodziło mi o to, że int się nie zmieści na jednym bajcie.
A jak odjąć najmłodszy bajt?
a & 255 = najmłodszy bajt
a & -256 = liczba z odjętym najmłodszym bajtem
Jakoś tego nie rozumiem. Mógłbyś dać przykład jak zamienić np. liczbę 300?
300 & -256 = 256 - to wynik po odjęciu wartości najmłodszego bajta
Binarnie mamy
...00000100101100 & ...1111111000000000 = ....0100000000
Gdzie te kropki oznaczają powtórzenie pierwszej cyfry. Jak widać ostatnie 8 bitów się nam wyzerowało.
300 & 255 = 44 - to wartość ostatniego bajta
Binarnie 44 to
....00000101100
Jak widać zostały wyzerowane wszystkie bity oprócz ostatnich ośmiu (nota bene tylko jeden oprócz nich był ustawiony na 1).
SoyeR napisał(a)
Jakoś tego nie rozumiem. Mógłbyś dać przykład jak zamienić np. liczbę 300?
Sorry za głupie pytanie, ale czy Ty przypadkiem nie chcesz upchnąć wartości 300 w jednym bajcie? Jeśli tak, to se ne da. Następny typ, który jest w stanie pomieścić 300 to uint16_t (2 bajty), ale to nadal jest mniej niż int (4 lub 8 bajtów).
Nie, ja chcę upchnąć int w czterech uint8_t. Tylko nie za bardzo wiem jak dzielić bajty i je przesuwać.
#include <stdio.h>
// typedef unsigned char uint8_t;
int main(){
int a = 42424242;
uint8_t *b = (uint8_t*)&a;
printf(" a = %i\n",a);
printf("b[0] = %i\n",b[0]);
printf("b[1] = %i\n",b[1]);
printf("b[2] = %i\n",b[2]);
printf("b[3] = %i\n",b[3]);
}
tylko pamiętaj, że b
w tym momencie wskazuje na a
, więc jest prawidłowe tak długo, jak istnieje a
.
Tyle, że powyższy zapis jest zależny od kolejności bajtów w incie, np x86 jest little-endian, a PPC z reguły big-endian, więc ten kod może dać różne wyniki na różnych procesorach.
Ewentualnie:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int main(void) {
int a = 42424242;
uint8_t b[4];
memcpy(b, &a, 4);
printf(" a = %i\n",a);
printf("b[0] = %i\n",b[0]);
printf("b[1] = %i\n",b[1]);
printf("b[2] = %i\n",b[2]);
printf("b[3] = %i\n",b[3]);
return 0;
}
(wykorzystałem fragment kodu kolegi wyżej, sorki)
Brawo, to może zarzućcie coś jeszcze z reinterpret_castem. Tak czy siak wszystkie wasze metody są nieprzenośne.
Zakładamy, że twórca wątku ma świadomość kolejności bajtów na różnych platformach. Zauważ, że nie zaznaczyliśmy też explicite, że na 64bit int ma 8 bajtów i odpowiednio musi sobie pod to zmodyfikować kod. Nie będziemy mu przecież tutaj wykładać teorii i konstruować funkcji byte-swap (które nawiasem mówiąc na różnych platformach różnie wyglądają). A nawet jeśli, to czy na format przenośny wybrać LE, BE czy hosta? Dużo tego i wątek by się rozrósł wojnami między sympatykami jednego i drugiego. Szkoda takiego pięknego dnia, a kolega wątkotwórca może sobie conieco poczyta w tym temacie i wyjdzie lepiej dla niego ;)
Ale po co byte swap?
u_int32_t x;
u_int8_t a = (x >> 0) & 0xFF;
u_int8_t b = (x >> 8) & 0xFF;
u_int8_t c = (x >> 16) & 0xFF;
u_int8_t d = (x >> 24) & 0xFF;
To jest najpopularniejsza metoda, nie widziałem nigdy wcześniej waszych metod, nikt ich nie stosuje bo są nieprzenośne.
O, fajna metoda. Trzeba było tak od razu :)
A teraz korzystajmy z pięknego dnia.
Tyle, że powyższy zapis jest zależny od kolejności bajtów w incie, np x86 jest little-endian, a PPC z reguły big-endian, więc ten kod może dać różne wyniki na różnych procesorach.
Żadna metoda „rozbijania inta” nie jest wolna od konieczności podjęcia jakiejś decyzji w tym względzie.
Azarien_ napisał(a)
Tyle, że powyższy zapis jest zależny od kolejności bajtów w incie, np x86 jest little-endian, a PPC z reguły big-endian, więc ten kod może dać różne wyniki na różnych procesorach.
Żadna metoda „rozbijania inta” nie jest wolna od konieczności podjęcia jakiejś decyzji w tym względzie.
Tak, tyle że mi chodzi o przenośność - pewność że na każdej platformie kod zadziała tak samo. Mogę sobie nawet permutować bajty w dowolnej ale ustalonej kolejności, ważne jest tylko to żebym wiedział, co oznacza dana zmienna.
No tak, zaproponowanie przez ciebie metody są przenośne, tylko że wolniejsze. Nie czepiam się akurat tej metody, ale ogólnie podejścia, że w C++ należy programować jak najbardziej wielosystemowo. C++ jest naprawdę bliski językowi maszynowemu (mimo paru dodatkowych, bardzo przydatnych, poziomów abstrakcji), tym samym nadaję się głównie do pisania programów, w których głównie liczy się wydajność.
Jeżeli wydajność nie jest czynnikiem decydującym, to istnieje wiele innnych języków, w których można napisać daną aplikacje łatwiej, szybciej oraz bezpieczniej (patrz np. Java, pozbawiona min. wskaźników, typów unsigned, czy goto, ale z to z garbage collectorem i większą objektownością (moja opinia może być kompletnie niezgodna z rzeczywistością, bo Javy dobrze nie znam)), ale mniej wydajnie (co w 90% przypadków nie robi różnicy).
(patrz np. Java, pozbawiona min. wskaźników, typów unsigned, czy goto, ale z to z garbage collectorem i większą objektownością
C# ma GC i „większą obiektowość” zachowujący przy tym wskaźniki, typy unsigned i goto ;-)
Zjarek napisał(a)
No tak, zaproponowanie przez ciebie metody są przenośne, tylko że wolniejsze. Nie czepiam się akurat tej metody, ale ogólnie podejścia, że w C++ należy programować jak najbardziej wielosystemowo. C++ jest naprawdę bliski językowi maszynowemu (mimo paru dodatkowych, bardzo przydatnych, poziomów abstrakcji), tym samym nadaję się głównie do pisania programów, w których głównie liczy się wydajność.
Jeżeli wydajność nie jest czynnikiem decydującym, to istnieje wiele innnych języków, w których można napisać daną aplikacje łatwiej, szybciej oraz bezpieczniej (patrz np. Java, pozbawiona min. wskaźników, typów unsigned, czy goto, ale z to z garbage collectorem i większą objektownością (moja opinia może być kompletnie niezgodna z rzeczywistością, bo Javy dobrze nie znam)), ale mniej wydajnie (co w 90% przypadków nie robi różnicy).
Jest naprawę niewiele różnic pomiędzy platformami na poziomie programowania w C++. W zasadzie to kolejność bajtów jest chyba jedynym problemem napotykanym przy pisaniu zwykłych aplikacji, a konstrukcję napisaną przeze mnie kompilator x86 powinien sobie zoptymalizować do jakiegoś mova czy bswapa (mnemoniki x86).
ARM zyskuje na sile, nie wiadomo czy w ciągu kilku lat nie będzie popularną platformą, konkurującą z x86 w segmencie laptopów na przykład. A tam kolejność bajtów jest raczej inna niż w x86.
@Wibowit - kompilator nawet bswapa nie potrzebował.
No tak, zaproponowanie przez ciebie metody są przenośne, tylko że wolniejsze.
Ech, takich pseudooptymalizacji to nie wykonywano chyba nawet w czasach kiedy jeszcze liczyli każdy cykl procesora...
Tutaj kilka na szybko wykonanych przeze mnie testów (gcc na -O3):
uwaga, sporo bezsensownych listingów : ;)
Na początek 'wolny' program Wibowita:
#include <stdio.h>
typedef unsigned long int u_int32_t;
typedef unsigned char u_int8_t;
int main()
{
u_int32_t x;
scanf("%ld", x);
u_int8_t a = (x >> 0) & 0xFF;
u_int8_t b = (x >> 8) & 0xFF;
u_int8_t c = (x >> 16) & 0xFF;
u_int8_t d = (x >> 24) & 0xFF;
printf("%d", a);
printf("%d", b);
printf("%d", c);
printf("%d", d);
}
Co daje nam w wyniku (wrzucam kody od znaczących parametrów do scanf do ostatniego printf):
.text:004012EA mov [esp+38h+var_34], ebx
.text:004012EE shr ebx, 8
.text:004012F1 mov [esp+38h+var_38], offset unk_403000
.text:004012F8 call scanf
.text:004012FD mov [esp+38h+var_38], offset dword_403004
.text:00401304 mov edx, esi
.text:00401306 mov eax, esi
.text:00401308 shr edx, 10h
.text:0040130B xor esi, esi
.text:0040130D shr eax, 18h
.text:00401310 mov [ebp+var_18], dl
.text:00401313 mov [esp+38h+var_34], esi
.text:00401317 mov [ebp+var_28], al
.text:0040131A call printf
.text:0040131F mov [esp+38h+var_38], offset dword_403004
.text:00401326 movzx ecx, bl
.text:00401329 mov [esp+38h+var_34], ecx
.text:0040132D call printf
.text:00401332 movzx edx, [ebp+var_18]
.text:00401336 mov [esp+38h+var_38], offset dword_403004
.text:0040133D mov [esp+38h+var_34], edx
.text:00401341 call printf
.text:00401346 movzx eax, [ebp+var_28]
.text:0040134A mov [esp+38h+var_38], offset dword_403004
.text:00401351 mov [esp+38h+var_34], eax
.text:00401355 call printf
Rezultat? Mniej-więcej 5 opkodów operacji wykonujących te całe "powolne" działania...
Ok, zmienne były lokalne, zobaczymy co będzie jeśli użyjemy globalnych (kompilator mie może zrobić pewnych optymalizacji)
#include <stdio.h>
typedef unsigned long int u_int32_t;
typedef unsigned char u_int8_t;
u_int8_t a;
u_int8_t b;
u_int8_t c;
u_int8_t d;
int main()
{
u_int32_t x;
scanf("%ld", x);
a = (x >> 0) & 0xFF;
b = (x >> 8) & 0xFF;
c = (x >> 16) & 0xFF;
d = (x >> 24) & 0xFF;
printf("%d", a);
printf("%d", b);
printf("%d", c);
printf("%d", d);
}
.text:004012FB mov [esp+18h+var_18], offset unk_403000
.text:00401302 shr edi, 18h
.text:00401305 call scanf
.text:0040130A mov ds:byte_40400A, bl
.text:00401310 mov edx, esi
.text:00401312 mov eax, edi
.text:00401314 mov ds:byte_404009, dl
.text:0040131A xor ebx, ebx
.text:0040131C mov [esp+18h+var_14], ebx
.text:00401320 mov [esp+18h+var_18], offset unk_403004
.text:00401327 mov ds:byte_40400B, 0
.text:0040132E mov ds:byte_404008, al
.text:00401333 call printf
.text:00401338 movzx ecx, ds:byte_40400A
.text:0040133F mov [esp+18h+var_18], offset unk_403004
.text:00401346 mov [esp+18h+var_14], ecx
.text:0040134A call printf
.text:0040134F movzx edx, ds:byte_404009
.text:00401356 mov [esp+18h+var_18], offset unk_403004
.text:0040135D mov [esp+18h+var_14], edx
.text:00401361 call printf
.text:00401366 movzx eax, ds:byte_404008
.text:0040136D mov [esp+18h+var_18], offset unk_403004
.text:00401374 mov [esp+18h+var_14], eax
.text:00401378 call printf
Wynik? Koło 6 opkodów narzutu.
No i na deser "zoptymalizowana", nieprzenośna wersja:
#include <stdio.h>
typedef unsigned long int u_int32_t;
typedef unsigned char u_int8_t;
int main()
{
u_int32_t a;
char *b = (char*)&a;
scanf("%ld", a);
printf(" a = %i\n",a);
printf("b[0] = %i\n",b[0]);
printf("b[1] = %i\n",b[1]);
printf("b[2] = %i\n",b[2]);
printf("b[3] = %i\n",b[3]);
}
.text:004012E8 mov [esp+18h+var_18], offset unk_403000
.text:004012EF mov ecx, [ebp+var_4]
.text:004012F2 mov [esp+18h+var_14], ecx
.text:004012F6 call scanf
.text:004012FB mov [esp+18h+var_18], offset aAI ; " a = %i\n"
.text:00401302 mov edx, [ebp+var_4]
.text:00401305 mov [esp+18h+var_14], edx
.text:00401309 call printf
.text:0040130E movsx eax, byte ptr [ebp+var_4]
.text:00401312 mov [esp+18h+var_18], offset aB0I ; "b[0] = %i\n"
.text:00401319 mov [esp+18h+var_14], eax
.text:0040131D call printf
.text:00401322 movsx ecx, byte ptr [ebp+var_4+1]
.text:00401326 mov [esp+18h+var_18], offset aB1I ; "b[1] = %i\n"
.text:0040132D mov [esp+18h+var_14], ecx
.text:00401331 call printf
.text:00401336 movsx edx, byte ptr [ebp+var_4+2]
.text:0040133A mov [esp+18h+var_18], offset aB2I ; "b[2] = %i\n"
.text:00401341 mov [esp+18h+var_14], edx
.text:00401345 call printf
.text:0040134A movsx eax, byte ptr [ebp+var_4+3]
.text:0040134E mov [esp+18h+var_18], offset aB3I ; "b[3] = %i\n"
.text:00401355 mov [esp+18h+var_14], eax
.text:00401359 call printf
Kwestię 'czy warto' pozostawiam do rozważenia.
bswap nie jest dostępny pod 386, pewnie dlatego kompilator nie użył tej instrukcji. Dodaj -march=pentium do flag i zobacz czy kompilator użyje bswapa.