Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

C,C++,C#

Moderátori: psichac, Moderátori

Dumitru
Stály člen
Stály člen
Príspevky: 374
Dátum registrácie: 06 Nov 2011, 22:19
Vek: 32

Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa Dumitru » 12 Dec 2018, 23:19

Kód: Vybrať všetko

uint16_t temp;
uint8_t data[2];

// bežný zápis 
temp = data[1];
temp = temp<<8;
temp |= data[0];

// UDAJNE to iste ale inak 

*((char *)&Temp) = data[1];
 *((char *)&Temp+1) = data[0];

Čaute môžete mi niekto vysvetliť druhy zápis a či je naozaj totožný a rýchlejší ?

*(data) je to iste ako data[0]
*(data+1) je to iste ako data[1]

tomuto rozumiem ale jednasa o pole teda viac premien rovnakého typu

tam ale mam jednu premennú 16 bitovu ktorú ako keby adresovo rozbili na 2-e 8 bitove :rolleyes:

Vďaka
0

peterple
Ultimate člen
Ultimate člen
Príspevky: 2328
Dátum registrácie: 25 Jún 2013, 21:06
Bydlisko: Krajné
Vek: 57
Kontaktovať používateľa:

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa peterple » 12 Dec 2018, 23:42

Je aj nie je. V prípade že prekladač používa big endian reprezentáciu tak áno. Ak ale takýto kód niekto premigruje na little endian architektúru tak je to v háji.
A áno je to rýchlejšie.
0
Ukáž múdremu chybu a on sa ti poďakuje. Ukáž chybu hlupákovi a on sa urazí.

Používateľov profilový obrázok
budvar10
Pokročilý člen
Pokročilý člen
Príspevky: 983
Dátum registrácie: 15 Dec 2014, 10:55
Bydlisko: Košice

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa budvar10 » 13 Dec 2018, 07:50

Všetko záleží na kompilátore a na jeho nastavení. Najefektívnejšie a z pohľadu prekladača najspoľahlivejšie je použitie union.

V tom prvom prípade je vysoká pravdepodobnosť, že sa to preloží ako matematická operácia sčítania a preto tam môžu byť nadbytočné inštrukcie z pohľadu momentálneho použitia.
0

Dumitru
Stály člen
Stály člen
Príspevky: 374
Dátum registrácie: 06 Nov 2011, 22:19
Vek: 32

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa Dumitru » 13 Dec 2018, 08:09

Ja tomu stále presne nerozumiem prečo toto

Kód: Vybrať všetko


*((char *)&temp+1) //ukazuje (pointer) na mladšie 8bit premennej temp 
 *((char *)&temp)  // ukazuje (pointer) na staršie 8bit premennej temp
pre mňa zápis *(temp+1) je že sa posuniem na ďalšiu premennú ktorá je 16bitova :confused: ale tuto je to v rámci jednej premennej

ak mam 32bitovu premennú tak tam môžem takto zapisovať data ?

Kód: Vybrať všetko

*((char *)&temp) = , *((char *)&temp+1) = , *((char *)&temp+2) = ,*((char *)&temp+3) =...
alebo

Kód: Vybrať všetko

*((uint16_t *)&temp) = , *((uint16_t *)&temp+1) =
0

cirkus
Okoloidúci
Okoloidúci
Príspevky: 16
Dátum registrácie: 07 Feb 2017, 09:33

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa cirkus » 13 Dec 2018, 09:00

mozes to zapisat takto

Kód: Vybrať všetko

((uint8_t*)&temp)[0] = data[0];
((uint8_t*)&temp)[1] = data[1];
a nemusis riesit nejake pricitavanie alebo co
0

Dumitru
Stály člen
Stály člen
Príspevky: 374
Dátum registrácie: 06 Nov 2011, 22:19
Vek: 32

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa Dumitru » 13 Dec 2018, 11:39

To je len vec zápisu
0

Používateľov profilový obrázok
Tribec Zacks
Pokročilý člen
Pokročilý člen
Príspevky: 709
Dátum registrácie: 26 Jún 2010, 00:00
Bydlisko: Levice / Cork IRL
Vek: 41
Kontaktovať používateľa:

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa Tribec Zacks » 13 Dec 2018, 12:58

Dumitru napísal:

Kód: Vybrať všetko

uint16_t temp;
uint8_t data[2];

// bežný zápis 
temp = data[1];
temp = temp<<8;
temp |= data[0];

// UDAJNE to iste ale inak 

*((char *)&Temp) = data[1];
 *((char *)&Temp+1) = data[0];

Čaute môžete mi niekto vysvetliť druhy zápis a či je naozaj totožný a rýchlejší ?

*(data) je to iste ako data[0]
*(data+1) je to iste ako data[1]

tomuto rozumiem ale jednasa o pole teda viac premien rovnakého typu

tam ale mam jednu premennú 16 bitovu ktorú ako keby adresovo rozbili na 2-e 8 bitove :rolleyes:

Vďaka

@Dumitru, v podobnych situaciach je najlepsie to nakodit a pozret disassembly ako je to prelozene na instrukcie:

Kód: Vybrať všetko

volatile uint16_t temp;
volatile uint8_t data[2] = {0xad, 0xde};

void merge_8bit_1(){
    temp = data[1];
    temp = temp<<8;
    temp |= data[0];
}

void merge_8bit_2(){
    *((char *)&temp) = data[1];
    *((char *)&temp+1) = data[0];
}
je to kompilovane bez optimalizacie pre cortex-f7 (armv7e)
rozdiel je ze v prvom pripade je to robene ako matematicka operacie, nacitaj register, uprav na format, uloz, zopakuj pre druhe cislo, pak nacitaj, urob OR, uloz.
v druhom pripade je to podobne, ale odpada cele zvlast ukladanie upravenych premennych, znovu nacitanie, prevedenie OR... proste ich nacita, prevede na format a ulozi na iny offset v pamati.

Kód: Vybrať všetko

void merge_8bit_1(){
merge_8bit_1:
  push    {r7}
  add     r7, sp, #0
    temp = data[1];
  ldr     r3, [pc, #56]   ; (0x20003ee0 <merge_8bit_1+64>)
  ldrb    r3, [r3, #1]
  uxtb    r3, r3
  uxth    r2, r3
  ldr     r3, [pc, #52]   ; (0x20003ee4 <merge_8bit_1+68>)
  strh    r2, [r3, #0]
    temp = temp<<8;
  ldr     r3, [pc, #48]   ; (0x20003ee4 <merge_8bit_1+68>)
  ldrh    r3, [r3, #0]
  uxth    r3, r3
  lsls    r3, r3, #8
  uxth    r2, r3
  ldr     r3, [pc, #40]   ; (0x20003ee4 <merge_8bit_1+68>)
  strh    r2, [r3, #0]
    temp |= data[0];
  ldr     r3, [pc, #32]   ; (0x20003ee0 <merge_8bit_1+64>)
  ldrb    r3, [r3, #0]
  uxtb    r3, r3
  uxth    r2, r3
  ldr     r3, [pc, #28]   ; (0x20003ee4 <merge_8bit_1+68>)
  ldrh    r3, [r3, #0]
  uxth    r3, r3
  orrs    r3, r2
  uxth    r2, r3
  ldr     r3, [pc, #16]   ; (0x20003ee4 <merge_8bit_1+68>)
  strh    r2, [r3, #0]
}

Kód: Vybrať všetko

void merge_8bit_2(){
merge_8bit_2:
  push    {r7}
  add     r7, sp, #0
    *((char *)&temp) = data[1];
  ldr     r3, [pc, #32]   ; (0x20003f10 <merge_8bit_2+40>)
  ldrb    r3, [r3, #1]
  uxtb    r2, r3
  ldr     r3, [pc, #32]   ; (0x20003f14 <merge_8bit_2+44>)
  sxtb    r2, r2
  strb    r2, [r3, #0]
    *((char *)&temp+1) = data[0];
  ldr     r3, [pc, #20]   ; (0x20003f10 <merge_8bit_2+40>)
  ldrb    r3, [r3, #0]
  uxtb    r2, r3
  ldr     r3, [pc, #24]   ; (0x20003f18 <merge_8bit_2+48>)
  sxtb    r2, r2
  strb    r2, [r3, #0]
}
ak zapnes optimalizaciu (gcc -O1), situacia je podstatne lepsia, ale stale v prospech druheho zapisu koli ORovaniu v prvom:

Kód: Vybrať všetko

 *((char *)&temp) = data[1];
merge_8bit_1:
  ldr     r1, [pc, #24]   ; (0x200027c8 <merge_8bit_1+28>)
  ldrb    r2, [r1, #1]
  ldr     r3, [pc, #24]   ; (0x200027cc <merge_8bit_1+32>)
  strh    r2, [r3, #0]
    temp = temp<<8;
  ldrh    r2, [r3, #0]
  lsls    r2, r2, #8
  uxth    r2, r2
  strh    r2, [r3, #0]
    temp |= data[0];
  ldrb    r2, [r1, #0]
  ldrh    r1, [r3, #0]
  orrs    r2, r1
  strh    r2, [r3, #0]
}

Kód: Vybrať všetko

 *((char *)&temp) = data[0];
merge_8bit_2:
  ldr     r2, [pc, #12]   ; (0x200027e0 <merge_8bit_2+16>)
  ldrb    r1, [r2, #1]
  ldr     r3, [pc, #12]   ; (0x200027e4 <merge_8bit_2+20>)
  strb    r1, [r3, #0]
    *((char *)&temp+1) = data[0];
  ldrb    r2, [r2, #0]
  strb    r2, [r3, #1]
}
situacia je velmi podobna aj na x86 (gcc -o0):

Kód: Vybrať všetko

merge_8bit_1:
  movzbl  0x295b(%rip),%eax        # 0x404089 <data+1>
  movzbl  %al,%edx
  lea     0x7238(%rip),%rax        # 0x408970 <temp>
  mov     %dx,(%rax)
    temp = temp<<8;
  lea     0x722e(%rip),%rax        # 0x408970 <temp>
  movzwl  (%rax),%eax
  shl     $0x8,%eax
  mov     %eax,%edx
  lea     0x721f(%rip),%rax        # 0x408970 <temp>
  mov     %dx,(%rax)
    temp |= data[0];
  movzbl  0x292d(%rip),%eax        # 0x404088 <data>
  movzbl  %al,%edx
  lea     0x720b(%rip),%rax        # 0x408970 <temp>
  movzwl  (%rax),%eax
  or      %eax,%edx
  lea     0x71ff(%rip),%rax        # 0x408970 <temp>
  mov     %dx,(%rax)
}

Kód: Vybrať všetko

*((char *)&temp) = data[1];
merge_8bit_2:
  movzbl  0x290c(%rip),%edx        # 0x404089 <data+1>
  lea     0x71ec(%rip),%rax        # 0x408970 <temp>
  mov     %rax,%rcx
  mov     %edx,%eax
  mov     %al,(%rcx)
    *((char *)&temp+1) = data[0];
  movzbl  0x28f6(%rip),%edx        # 0x404088 <data>
  lea     0x71d7(%rip),%rax        # 0x408970 <temp>
  lea     0x1(%rax),%rax
  mov     %dl,(%rax)
}
so zapnutou optimalizaciou -O1

Kód: Vybrať všetko

temp = data[1];
merge_8bit_1:
  movzbl  0x199f(%rip),%eax        # 0x403061 <data+1>
  movzbl  %al,%eax
  mov     %ax,0x62a4(%rip)        # 0x407970 <temp>
    temp = temp<<8;
  movzwl  0x629d(%rip),%eax        # 0x407970 <temp>
  shl     $0x8,%eax
  mov     %ax,0x6293(%rip)        # 0x407970 <temp>
    temp |= data[0];
  movzbl  0x197c(%rip),%eax        # 0x403060 <data>
  movzwl  0x6285(%rip),%edx        # 0x407970 <temp>
  movzbl  %al,%eax
  or      %edx,%eax
  mov     %ax,0x6279(%rip)        # 0x407970 <temp>

Kód: Vybrať všetko

*((char *)&temp) = data[1];
merge_8bit_2:
  movzbl  0x1962(%rip),%eax        # 0x403061 <data+1>
  mov     %al,0x626b(%rip)        # 0x407970 <temp>
    *((char *)&temp+1) = data[0];
  movzbl  0x1954(%rip),%eax        # 0x403060 <data>
  mov     %al,0x625f(%rip)        # 0x407971 <temp+1>
ak aj zvolis vysie optimalizacie, situacia je stale rovnaka, v druhom pripade usetris OR medzi dvoma registrami
0
Kreativita a motivacia je to, co prinasa originalne napady a myslienky, disciplina je to, co ich dokaze zrealizovat.

peterple
Ultimate člen
Ultimate člen
Príspevky: 2328
Dátum registrácie: 25 Jún 2013, 21:06
Bydlisko: Krajné
Vek: 57
Kontaktovať používateľa:

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa peterple » 13 Dec 2018, 19:46

Tak toto bude možno ťažký boj, preto to rozdelím na viacero príspevkov.Na začiatok ľahká delostrelecká príprava:
Dumitru napísal:Ja tomu stále presne nerozumiem prečo toto

Kód: Vybrať všetko


*((char *)&temp+1) //ukazuje (pointer) na mladšie 8bit premennej temp 
 *((char *)&temp)  // ukazuje (pointer) na staršie 8bit premennej temp
pre mňa zápis *(temp+1) je že sa posuniem na ďalšiu premennú ktorá je 16bitova :confused: ale tuto je to v rámci jednej premennej
Nuž to by bola pravda keby si tam nemal to pretypovanie. Ale kedže pretypovanie je bližšia košeľa ako +1 kábat tak si z toho najprv urobil ukazateľ na byte. A potom si potom posunul o jeden byte ďalej. To je v skutočnosti druhý byte toho int16
Mladšie a staršie byty to sa kde používa takáto hantírka? Jedno je dolný byte toho intu a druhé je horný byte toho int (kedže je 16 bitový)
Dúfam že sa to dá pochopiť z priloženého obrázka. Je to kompilované v AVR studiu pre nejaký ATMega328P čo je arduine (nech zabijeme dve muchy jednou ranou)
Tam si treba uvedomiť jednu vec. Je to little endian architektura (podobne ako je aj x86)

A na tejto architektúre to bude fungovať chybne
teda prvý kód vyráta nejaké číslo a druhý kód vyráta úplne iné číslo. Kto chce môže skúsiť na to prísť sám aké to budú čísla.
Budem pokračovať ďalším príspevkom
Prílohy
byte2word01.png
0
Ukáž múdremu chybu a on sa ti poďakuje. Ukáž chybu hlupákovi a on sa urazí.

Dumitru
Stály člen
Stály člen
Príspevky: 374
Dátum registrácie: 06 Nov 2011, 22:19
Vek: 32

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa Dumitru » 13 Dec 2018, 21:03

Ďakujem pekne za obšírne vysvetlenie už je mi to jasne :) :agree:

Čo sa tyká big endian a little endian sú mi jasne rizika :) jedna architektura ukladá od An ......... A0 a ďalšia presne naopak :)
0

peterple
Ultimate člen
Ultimate člen
Príspevky: 2328
Dátum registrácie: 25 Jún 2013, 21:06
Bydlisko: Krajné
Vek: 57
Kontaktovať používateľa:

Re: Zlučenie 2-och 8-bit premennych do 1-ej 16-bitovej

Príspevok od používateľa peterple » 13 Dec 2018, 21:17

Tak teraz ukazuje obrázok situáciu po vykonaní prvej časti kódu. Tento kód je platformovo neutrálny a bude dobre fungovať vždy aj na little endian aj na big endian architektúre. Preto tento kód považujem za prioritný.
Teraz čo je to endianita. https://sk.wikipedia.org/wiki/Endianita.
Kedže pamäť bežných počítačov sa skladá z poľa bytov tak väčšie čísla treba ukladať do pamete nejako za sebou. Takže ak nižší byte čísla je na nižšej adrese je to little endian (Intel 8080, x86, 8051, AVR) Ale sú aj exotické procesory kde sa to ukladá presne naopak. Teda od najvyššieho byte ktorý je na najnižšej adrese.
Konkrétne na obrázku premenná temp začína na adrese 0x100 a obsahuje dva byte čo je 16 bitov

Teraz si pozri na obrázku ten prvý výpočet. Ten predpokladá že data[1] je horný byte toho intu. Preto lebo ho naprv načítajú do premenej a ako byte (teda do dolného byte) a potom ho rotáciou 8x doprava vyšupnú do horného byte. Následne k nemu logicky pripočítajú ten druhý byte ktorý sa nerotuje takže musí byť dolný. Kedže sme na little endian architektúre tak vidno že horný byte (data[1]) sa uložil na vyššiu adresu premennej temp (0x101) a dolný byte (data[0]) sa umiestnil na adresu 0x100
Keď sa to urobí a berie sa to ako jedno 16bitové číslo little endian tak výsledok je 513 (2*256+1).
No a ako to bude s posledným výpočtom zase v ďalšom príspevku

-- Spojený príspevok 13 Dec 2018, 21:24 --

Ok keď je to jasné tak fajn. Len to pre úplnosť teda dokončím. Ten posledný kód vyráta dobré číslo len na big endian architektúre. Tu na little endian to vyjde 258 (1*256+2).

Už len doplním že podobný hokej môže nastať aj vtedy keď sa spracovávajú data z nejakého komunikačného streamu. Napr IP adredsa v hlavičke IP datagramu je vo formáte big endian.
Prílohy
byte2word02.png
byte2word03.png
0
Ukáž múdremu chybu a on sa ti poďakuje. Ukáž chybu hlupákovi a on sa urazí.

Napísať odpoveď