Pretypovani ukazatele struktury na bajt

David Obdrzalek David.Obdrzalek na mff.cuni.cz
Úterý Únor 25 13:28:27 CET 2020


Mi tak dneska v tramvaji volným uvažováním došlo, že ta funkce napln není správně - 
mělo by tam být explicitní přetypování e, f na uint32_t, protože jinak se to spočte 
v intech, kde e<<16 i f<<24 vyjde 0  a teprv na závěr se to uloží do většího.

Co mě ale zaskočilo: pro ověření, jak se to chová blbě při nepřetypování, jsem to 
prohnal gcc, ale pro opravu bylo potřeba přetypovat na větší typ i první dva členy, 
jinak se to pořád uřízlo. Proč? Myslel jsem, ze by mělo stačit, když na větší 
přetypuju ty, co by přetekly, tím část výrazu bude celkově větší než int a proto se 
i ostatní taky natáhnou.

S explicitním přetypováním všeho se pro AVR vygeneruje docela uvěřitelný kód 
(zkusil jsem i konstanty):
hdr.magic = 0xAB | 0xBA << 8;
 4ba:	8b ea       	ldi	r24, 0xAB	; 171
 4bc:	9a eb       	ldi	r25, 0xBA	; 186
 4be:	90 93 b0 01 	sts	0x01B0, r25	; 0x8001b0 <hdr+0x1>
 4c2:	80 93 af 01 	sts	0x01AF, r24	; 0x8001af <hdr>
hdr.ident = ((uint32_t)0xDE) | ((uint32_t)0xAD) << 8 | ((uint32_t)0xBE) << 16 | 
((uint32_t)0xEF) << 24;
 4c6:	8e ed       	ldi	r24, 0xDE	; 222
 4c8:	9d ea       	ldi	r25, 0xAD	; 173
 4ca:	ae eb       	ldi	r26, 0xBE	; 190
 4cc:	bf ee       	ldi	r27, 0xEF	; 239
 4ce:	80 93 b1 01 	sts	0x01B1, r24	; 0x8001b1 <hdr+0x2>
 4d2:	90 93 b2 01 	sts	0x01B2, r25	; 0x8001b2 <hdr+0x3>
 4d6:	a0 93 b3 01 	sts	0x01B3, r26	; 0x8001b3 <hdr+0x4>
 4da:	b0 93 b4 01 	sts	0x01B4, r27	; 0x8001b4 <hdr+0x5>

hdr.magic = a | b << 8;
hdr.ident = ((uint32_t)c) | ((uint32_t)d) << 8 | ((uint32_t)e) << 16 | 
((uint32_t)f) << 24;
 518:	6b 81       	ldd	r22, Y+3	; 0x03
 51a:	2c 81       	ldd	r18, Y+4	; 0x04
 51c:	9d 81       	ldd	r25, Y+5	; 0x05
 51e:	8e 81       	ldd	r24, Y+6	; 0x06
hdr.magic = a | b << 8;
 520:	49 81       	ldd	r20, Y+1	; 0x01
 522:	5a 81       	ldd	r21, Y+2	; 0x02
 524:	50 93 b0 01 	sts	0x01B0, r21	; 0x8001b0 <hdr+0x1>
 528:	40 93 af 01 	sts	0x01AF, r20	; 0x8001af <hdr>
hdr.ident = ((uint32_t)c) | ((uint32_t)d) << 8 | ((uint32_t)e) << 16 | 
((uint32_t)f) << 24;
 52c:	60 93 b1 01 	sts	0x01B1, r22	; 0x8001b1 <hdr+0x2>
 530:	20 93 b2 01 	sts	0x01B2, r18	; 0x8001b2 <hdr+0x3>
 534:	90 93 b3 01 	sts	0x01B3, r25	; 0x8001b3 <hdr+0x4>
 538:	80 93 b4 01 	sts	0x01B4, r24	; 0x8001b4 <hdr+0x5>


ALE když odmažu přetypování prvních dvou bytů, tak tam vyjde:
hdr.ident = 0xDE | 0xAD << 8 | ((uint32_t)0xBE) << 16 | ((uint32_t)0xEF) << 24;
 4c6:	8e ed       	ldi	r24, 0xDE	; 222
 4c8:	9d ea       	ldi	r25, 0xAD	; 173
 4ca:	af ef       	ldi	r26, 0xFF	; 255
 4cc:	bf ef       	ldi	r27, 0xFF	; 255
 4ce:	80 93 b1 01 	sts	0x01B1, r24	; 0x8001b1 <hdr+0x2>
 4d2:	90 93 b2 01 	sts	0x01B2, r25	; 0x8001b2 <hdr+0x3>
 4d6:	a0 93 b3 01 	sts	0x01B3, r26	; 0x8001b3 <hdr+0x4>
 4da:	b0 93 b4 01 	sts	0x01B4, r27	; 0x8001b4 <hdr+0x5>

Vůbec nachápu, proč ty dvě FF ??

hdr je deklarované takhle:
typedef struct {
  uint16_t magic;
  uint32_t ident;
} _hdr;
_hdr hdr;

Překladač avr-gcc (GCC) 5.4.0 na Win.


Jo tak teď jsem ještě zmatenější, protože mě napadlo zkusit:
hdr.ident = 0xADDE | ((uint32_t)0xBE) << 16 | ((uint32_t)0xEF) << 24;
 4c6:	8e ed       	ldi	r24, 0xDE	; 222
 4c8:	9d ea       	ldi	r25, 0xAD	; 173
 4ca:	ae eb       	ldi	r26, 0xBE	; 190
 4cc:	bf ee       	ldi	r27, 0xEF	; 239
 4ce:	80 93 b1 01 	sts	0x01B1, r24	; 0x8001b1 <hdr+0x2>
 4d2:	90 93 b2 01 	sts	0x01B2, r25	; 0x8001b2 <hdr+0x3>
 4d6:	a0 93 b3 01 	sts	0x01B3, r26	; 0x8001b3 <hdr+0x4>
 4da:	b0 93 b4 01 	sts	0x01B4, r27	; 0x8001b4 <hdr+0x5>

a tohle prošlo jak bych původně čekal!

D.O.

On 24 Feb 2020 at 17:57, David Obdrzalek wrote:
> Souhlasím. A podle mě šikovnej kompilátor i to plnění nakonec udělá kulturně 
> optimalizovaně - na platformě, co nepotřebuje zarovnaný přístup, to nasype po 
> bajtech jako kdyby se to tam sypalo jednotlivě takhle přes ten inkrementovaný 
> pointr, na platformě, co jí líp vyhovujou větší jednotky, si to připraví a pak 
> flekne kam patří a jak patří. A co je důležité, v obou případech se správnýma 
> endianama, aniž bych se o ně musel já starat.
> 
> void napln(uint8_t a, uint8_t b,uint8_t c,uint8_t d,uint8_t e,uint8_t f)
> {
>   hdr.magic = a | b << 8;
>   hdr.ident = c | d << 8 | e << 16 | f << 24;
> }
> (samozřejmě musím vědět, co v těch datech je MSB a co LSB)
> 
> Tipnul bych si, že to, že se uložilo jen kousek, bude nějakou optimalizací, kdy se
> nevygeneruje něco, co se později nijak nepoužije (např. v modelovém případě, kdy se
> ta struktura jen naplní ale pak už nečte). Starý kompilátor třeba jen 
> neoptimalizoval tak agresivně.
> 
> A ještě, zaklínadlo volatile jste zkusil?
> 
> D.O.
> 
> On 24 Feb 2020 at 9:06, Josef Štengl wrote:
> > Jinak správný způsob je naplnit tu strukturu, ne ji přetypovat a spoléhat se,
> > že to vyjde. Pak nevíte co udělá jiný 
> > kompilátor, nebo optimalizace.
> > 
> > Dne 23. 02. 20 v 17:13 Libor Konečný napsal(a):
> > > Zdravim osazenstvo, predevsim zdejsi programatory.
> > > 
> > > S novym kompilatorem (ten stary to nejakou zahadou umel)  resim pretypovani
> > ukazatelu.
> > > 
> > > Mam funkci (zjednodusene)
> > > void  funkce (UINT8 * from)
> > > {
> > > *from++=0x01 ;
> > > *from++=0x02 ;
> > > *from++=0x03 ;
> > > *from++=0x04 ;
> > > *from++=0x05 ;
> > > *from=0x06 ;
> > > }
> > > 
> > > 
> > > A pak strukduru
> > > struct {
> > >    UINT16 magic;	
> > >    DWORD ident;
> > > }  hdr;
> > > 
> > > A potrebuji ji naplnit,
> > > tedy volam
> > > funkce ((UINT8*)&hdr);
> > > 
> > > ale kompilator to naplni vzdy jen jednim znakem
> > > 
> > > tedy vysledek je ze hdr.magic ma jen prvni bajt spravny.
> > > 
> > > Pokud pouziji strukturu
> > > struct {
> > >    UINT8 pole[6];
> > > }  hdr;
> > > 
> > > Pak se naplni vsech 6 bajtu spravne.
> > > 
> > > Jak ji volat spravne aby se struktura prepisovala bajt po bajtu ? (little a
> big
> > endian zanedbejme)
> > > Je zvlastni, ze stary kompilator fungoval spravne.
> > > 
> > > Dekuji za nakopnuti.
> > > LK
> 





Další informace o konferenci Hw-list