přetečení int v C arduino

Jan Waclawek konfera na efton.sk
Pátek Červenec 18 16:22:19 CEST 2014


>Dne 18.7.2014 10:55, Jan Waclawek napsal(a):
>> V skutocnosti nepotrebujete drzat tie premenne den, hodina, minuta atd. ako
>> long, pokojne mohli ostat ako int, len v tych operaciach nasobenia, kde to
>> pretecenie nastava, ich treba explicitne pred pouzitim pretypovat na long.
>>
>
>Jak to pak probìhne technicky, ten pùvodní int se  tou operací 
>pøetypování  "odkopíruje" do longu a následnì zase zkrátí na int?


Ano, da sa to tak chapat.

Vas povodny zapis je takehoto typu:

int16_t a;
uint32_t b;

b = b - a * 12345;

Toto sa vykonava nasledovne:

1. a * 12345

Pred vykonanim nasobenia sa urobia "usual arithmetic conversions", t.j. sa
oba operandy prevedu na "najmensi spolocny typ" (este predtym, ak je
niektory operand "kratsi" ako int, tak sa prevedie na int alebo unsigned
int, to sa vola integer promotions, ale to tu nenastava tak sa tym
momentalne netreba zapodievat). Ta konstanta 12345 ma v avr-gcc implicitny
typ int16_t (ak by bola vacsia ako 32767, tak bude mat implicitny typ
int32_t). Premenna a ma explicitny typ int16_t, takze "najmensi spolocny
typ" je prave int16_t. Vykona sa nasobenie, ktoreho vysledok ma implicitne
tiez then "najmensi spolocny typ" ako jednotlive operandy (C99 6.3.1.8),
takze tam je prave to riziko pretecenia. Norma riesi taketo pretecenie
(C99 6.5#5) ako nedefinovane spravanie, takze nasledkom moze byt uplne
cokolvek, od spravneho vysledku (dosiahnuteho nespravnym postupom), cez
nespravny vysledok az po predcasne ukoncenie prekladu alebo vykonavania
prelozeneho programu. Je na zodpovednosti autora programu, aby taketo
pretecenie nenastalo. Castokrat (aj u AVR) sa typicky taketo nasobenie
jednoducho vykonava bez kontrol a pri preteceni sa zahodia vyssie bity.

2. Vysledok z predchadzajuceho kroku (s implicitnym typom int16_t) je
odcitany od premennej b s typom uint32_t. Znova sa najprv vykonaju "usual
arithmetic conversions", najmensi spolocny typ je uint32_t a na ten sa
medzivysledok predchadzajuceho kroku prevedie jednoducho pridanim nulovych
vyssich bitov (nijako sa pri tomto neberie do uvahy napriklad to, ze by
ten medzivysledok mohol byt zaporny, takze napr. kebyze je -12345, co je
0xCFC7, tak po konverzii na uint32_t je to stale 0x0000CFC7, ale toto uz
znamena 53191 - uz chapete preco varujem pred miesanim neznamienkovych a
znamienkovych typov?). Potom sa tie dve uint32_t hodnoty odcitaju.
Podobne, ako pri nasobeni, aj tu moze nastat pretecenie, znova je
nasledkom nedefinovane spravanie, znova je zodpovednost autora, aby to
nenastalo.

3. Vysledok predchadzajuceho kroku (s implicitnym typom uint32_t) ma byt
priradeny do premennej typu uint32_t. V tom problem nemoze byt. 

---

Vas problem je v kroku 1, lebo vzhladom na predchadzajuci postup v tej
funkcii, nikde inde to pretecenie nastat nemoze. Tento problem vyriesite
tak, ze prinutite kompilator, aby ten "najmensi spolocny typ" bol
dostatocne velky na to, aby pretecenie nenastalo (a uint32_t bude pre Vas
ucel urcite stacit). Preto je riesenim bud

b = b - (uint32_t)a * 12345;

alebo

b = b - a * (uint32_t)12345;

alebo

b = b - a * 12345UL;


Premenna a bude v pamati aj nadalej len 16-bitova (aj ked zas a znova,
doporucujem uint16_t) - ak vobec bude v pamati, je to len lokalna
premenna, na druhej strane ich tam mate dost vela a ak su 32-bitove, tak
sa do registrov asi nezmestia a naozaj aspon niektore budu v pamati.
Naviac ostatne operacie na tej premennej sa tiez mozu robit len 16-bitovo,
takze ta optimalizacia sa s najvacsou pravdepodobnostou oplati.

----

Za tu spravnost vypoctu zaplatite aj dan: u AVR sa pouzije nasobenie 32-bit
* 32_bit = 64-bit, co je pomerne zdlhava zalezitost a riesi sa volanim
kniznicnej funkcie (ktora samozrejme musi byt pritomna => spotrebovana
FLASH). 

Ale ponukam Vam fintu (ktora vsak funguje az od avr-gcc v4.5
(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=23726 ) - neviem, co
aktualne mate v tom Arduine, naposledy co som sa pozeral tam bola nejaka
dost predpotopna verzia): namiesto
  a = b / 12345;
  b = b - a * 12345UL;

to zapiste ako 

  a = b / 12345;
  b = b % 12345;


div aj mod sa vypocita v jednom volani funkcie pre delenie, a netreba uz
nasobit.

wek

 



Další informace o konferenci Hw-list