RE: C: co lze oèekávat

Jan Waclawek konfera na efton.sk
Středa Srpen 31 22:58:09 CEST 2016


Aha, zaujimave.

Takze na uvod zhrniem to, co uz bolo povedane:

- znamienkovost char (bez explicitneho signed/unsigned) je
implementation-defined, overil som si v manuali XC8 ze to je vzdy unsigned

- pretecenie u unsigned premennych je legalne, sposobi wraparound (t.j.
char x = 0; x--; vysledok je x == 0xFF)

- index pri pouziti prvku pola je vyraz, tento sa vyhodnocuje ako vsetky
ostatne vyrazy t.j. podlieha "usual arithmetic conversions" co v tomto
pripade znamena konverziu na int, ten je 16-bitovy, t.j. [x + 1] je index
na 0x100-ty prvok pola

- spravna oprava Vasho kodu je urobit maskovanie (alebo pretypovanie na
unsigned char) toho indexu


Teraz vysvetlenie toho co vidime: zacnime tym, ze podla 6.5.2.1#2: 

The definition of the subscript operator [] is that E1[E2] is identical to
(*((E1)+(E2))) 

pricom jeden z E1/E2 musi byt smernik a druhy celociselny vyraz. Takze sa
jedna o tzv. smernikovu aritmetiku, a klucom je jej presny popis v 6.5.6#8
(ospravedlnujem sa za dlhsi citat, ale je to v tom):

When an expression that has integer type is added to or subtracted from a
pointer, the result has the type of the pointer operand. If the pointer
operand points to an element of an array object, and the array is large
enough, the result points to an element offset from the original element
such that the difference of the subscripts of the resulting and original
array elements equals the integer expression. In other words, if the
expression P points to the i-th element of an array object, the
expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value
n) point to, respectively, the i+n-th and i?n-th elements of the array
object, provided they exist. [...] If both the pointer operand and the
result point to elements of the same array object, or one past the last
element of the array object, the evaluation shall not produce an overflow;
otherwise, the behavior is undefined.

(Ta vynechana cast sa tyka toho, co je spomenute aj v poslednej vete, ze je
dovolene aj to, aby vysledny smernik ukazoval na jeden prvok presne za
koncom pola, ale nesmie sa dereferencovat; vysvetlenie pre tuto zvlastnost
najdete v mojej oblubenej knihe od Dereka Jonesa :-) ).

Takze nielenze dereferencovat pointer ukazujuci mimo pola, ale dokonca co i
len *pocitat ho* (!), ma za nasledok "undefined behaviour" (t.j. aj
&A[x+1], co nic nedereferencuje, je pre male pole nespravne).

"Undefined behaviour" znamena pochopitelne uplne cokolvek, od spadnutia
prekladu cez spadnutie programu, necakaneho vysledku, nerobenia nic (t.j.
totalneho vyoptimalizovania, to je moje oblubene nedefinovane spravanie
:-) ) cez spravny vysledok dosiahnuty nespravnym postupom a spravny
vysledok len pre niektore vstupy, az po spravny vysledok vzdy.

"Undefined behaviour" treba v skutocnosti chapat ako pokyn pre programatora
"toto nerob"; pre tvorcu prekladaca to vsak znamena "toto mozes pouzit, ak
sa Ti to hodi na optimalizaciu". XC8 je jeden z najlepsich, ak nie vobec
najlepsi prekladac pre 8-bitaky, a teda taketo veci bez vahania vyuziva v
hojnom mnozstve.

Takze v tych dvoch Vasich pripadoch prekladac vysiel z predpokladu, ze
kedze pole ma 3 prvky, nikdy index nebude vacsi ako jeden byte (a ked
bude, tak "undefined behaviour", je to problem programatora). V tom prvom
pripade pravdepodobne vysiel z este dalsieho predpokladu, pole on sam
alokoval na nizkych adresach a tym mohol predpokladat, ze vsetky prvky
pola maju jednobytovu adresu - to pravdepodobne suvisi s existenciou
jednobytovych smernikov v XC8 (pri tejto prilezitosti zopakujem SMERNIK
NIE JE ADRESA, NIE JE ADRESA, NIE JE ADRESA). V tom druhom pripade sice
vypocet dopadol z formalneho pohladu dobre, naozaj sa ukazuje na 0x100-ty
prvok pola aj ked neexistuje; ale vypocet je jednoduchsi, konstanta +1 je
pripocitana vopred k adrese pola. Je to sice spravne ale ktovie ako by to
dopadlo pre nejaky komplikovanejsi pripad, ci by sa nestal nejaky problem
ak by prekladac aj nadalej vychadzal z predpokladu ze pole je dostatocne
male aby akykolvek ofset bol v ramci jedneho byte. Ten treti priklad je
spravny urcite, vypocet sa robi poctivo najprv ako 16-bitove scitanie x +
1 a potom sa 16-bitovo pricita adresa pola.


wek





----- Original Message ---------------

>char arrayA[0x103] ;
>
>int main(void) {
>    volatile char x = 0;
>    volatile char a = 3;
>    x--;
>    if (a != arrayA[x + 1]) {
>        __builtin_software_breakpoint();
>    }
>    return 0;
>
>!int main(void) {
>!    volatile char x = 0;
>0xFFCC: MOVLW 0x0
>0xFFCE: MOVWF x, ACCESS
>!    volatile char a = 3;
>0xFFD0: MOVLW 0x3
>0xFFD2: MOVWF a, ACCESS
>!    x--;
>0xFFD4: DECF x, F, ACCESS
>!    if (a != arrayA[x + 1]) {
>0xFFD6: MOVF x, W, ACCESS
>0xFFD8: MOVWF __pcstackCOMRAM, ACCESS
>0xFFDA: CLRF 0x2, ACCESS
>0xFFDC: MOVLW 0x1
>0xFFDE: ADDWF __pcstackCOMRAM, F, ACCESS
>0xFFE0: MOVLW 0x0
>0xFFE2: ADDWFC 0x2, F, ACCESS
>0xFFE4: MOVLW 0x7D
>0xFFE6: ADDWF __pcstackCOMRAM, W, ACCESS
>0xFFE8: MOVWF FSR2, ACCESS
>0xFFEA: MOVLW 0xE
>0xFFEC: ADDWFC 0x2, W, ACCESS
>0xFFEE: MOVWF FSR2H, ACCESS				// arrayA @0xE7D,
>FSR2= 0xF7D
>0xFFF0: MOVF a, W, ACCESS
>0xFFF2: XORWF POSTINC2, W, ACCESS
>0xFFF4: BTFSC STATUS, 2, ACCESS
>0xFFF6: GOTO 0x0
>0xFFF8: NOP
>!        __builtin_software_breakpoint();
>!    }
>!    return 0;
>!}
>0xFFFC: GOTO 0x0
>


Další informace o konferenci Hw-list