Régi program fordítása

 ( nehai | 2014. augusztus 10., vasárnap - 15:36 )

Van egy 2003-ban készült programocska amit szerettem volna újabb környezetben gcc4.x-el lefordítani. Eredetileg gcc2.9.x fordult.
Az alábbi két soron elbuktam, hozzá kell tennem C tudásom nem sokkal magasabb, mint a sarki zöldségesé.

((size_t*)p)--;

*(((size_t *)(*p))++) = s;

Megpróbáltam gcc3.x-el, azzal lefordul és működik is, de az alábbi üzenet jön mindkét sorra:
warning: use of cast expressions as lvalues is deprecated

Gcc4.x-el az alábbi hibaüzenetek jönnek:

error: lvalue required as decrement operand
error: lvalue required as increment operand

Gondolom a -- és ++ hiba. Hogyan lehet ezt kijavítani a gcc4.x-nek ehető formátumra? Esetleg nincs a gcc-nek valami őskompatibilitási kapcsolója?

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Ez egy felettébb érdekes probléma, ugyanis:

a (size_t*)p az elméletileg egy unsigned int-re mutató mutatót fog visszaadni (architektúrafüggően) amit minden probléma nélkül meg lehet operálni egy aritmetikai operátorral.
Az igazán vicces az, hogyha csinálsz egy közbenső változót, akkor működik a dolog:

size_t* pt;
pt = (size_t*)p;
pt--;

minden gond nélkül lefordul 4.7.2-es gccvel és működik is.
Kíváncsi vagyok, hogy a nálunk okosabb C-sek mit tudnak erre mondani :)

Nézzük ezt:
((size_t*)p)--;
Ha a 'p' mérete annyi, mint egy size_t -é (ami jó esetben(!) annyi, mint egy pointeré), akkor eggyel csökkenti a 'p' értékét. Ha a méret nem ugyanannyi... hát, valami akkor is történik, legfeljebb nem örülünk neki...

Ha arra tippelünk (a név alapján), hogy 'p' egy pointer, akkor ilyesmit lehetne javasolni:
p= (void *)((char *)p-1);
esetleg:
p= (void *)((intptr_t)p-1);

A másodiknál már inkább segédváltozót használnék:

size_t *tmp= *(size_t **)p;
*tmp = s;
*(size_t **)p= tmp+1;

Nagy "hacker" lehetett a kód szerzője... Jópofa dolog ilyen kifejezéseket írkálni, csak karbantartani nem vicces.

Az előttem szólóval értek egyet, az ilyesmiket én több sorban szoktam lekódolni, hogy érthetőbb legyen.

A probléma az, hogy ez a két sor nem C. Ez valami gusztustalan förmedvény, ami véletlenül régi gcc-vel lefordult.

Konkrétan az a gond, hogy a ++ ill. a -- operátort olyan operandusra próbálod ráhúzni, ami nem lvalue. Az lvalue nagyjából azt jelenti, hogy a dolognak van helye. Mivel van helye, ezért az objektumot ott, ahol van, lehet inkrementálni és dekrementálni. (Mondhatnánk azt, hogy "van címe", de az még kevésbé lenne pontos, mert pl. register tárolási osztályú változóknak nincs címe, ahogy a bit-field-eknek sem, de ettől még mindkettő állhat lvalue szerepben.)

A cast operátor eredménye nem lvalue, hanem egy kifejezés értéke. Egy értéket nem tudsz helyben megnövelni, mert nincs helye. A fenti két kifejezés olyan, mintha az írtad volna le: (a+3)++.

Hogy a kód eredetileg mit akart kifejezni, azzal kapcsolatban csak találgatni tudok. Az első esetben valószínűleg van egy mutatónk (p), ami nem size_t-re, hanem valami másra mutat. A kód úgy akarja csökkenteni p-t, mintha p size_t-re mutatna. Ezt kétféleképpen írhatjuk le:

(*(size_t **)&p)--;

Ebben az esetben a p által elfoglalt tárhelyet úgy értelmezzük újra, mint egy size_t-re mutató pointert, és helyben csökkentjük. Ez nagyjából ordenáré módon hordozhatatlan, de ha a p helye (alignment-je), mérete és tartalma (== az ott lévő bitminta) valóban érvényes az adott környezetben és platformon mint size_t-re mutató pointer, akkor menni fog. Ha meg nem, akkor undefined behavior.

Egy fokkal kevésbé ronda az alábbi:

TYPE *p;

/* ... */
p = (TYPE *)((size_t *)p - 1);

Ebben az esetben a p által elfoglalt bitmintát nem értelmezzük úja, hanem a p-t explicite konvertáljuk, csökkentjük, majd visszakonvertáljuk.

A második sorral kapcsolatban kifejezetten ajánlanám az eredeti programozónak, hogy vegyen vissza az arcából, mert a C nem a vagánykodásról, hanem az óvatosságról szól. Ebben az esetben különösen mentegethetetlen a kód, mert a p-ben eleve a növelendő mutatónak a címe van! Ezt akarhatta írni:

*(*(size_t **)p)++ = s;

Ez le fog fordulni, és valószínűleg működni is fog (x86-on), de továbbra is förtelmes.

Köszi a magyarázatot, én abban a hitben éltem, hogy a fenti esetben a p helyén helyben csinálja a dekrementálást, de így már értem, hogy az a kifejezés miért nem lesz lvalue. Az mindenesetre igaz, hogy ez így elég gusztustalan :D

Kösz a kimerítő információt, ha annakidején Te oktattad volna nekem a C-t még meg is kedveltem volna.
Egyébként egy malloc wrapper két sora, és még van vagy 30 ilyen vagy ilyenebb a framabuffer manipulációknál.
Egyenlőre inkább nem foglalkozom a dologgal, gcc3.4-el lefordult és teszi a dolgát.

Ha esetleg valakit érdekel az alábbi miatt nem működnek ezek a vagánykodások a gcc4.x-től kezdve, részlet a changelog-ból:

The cast-as-lvalue, conditional-expression-as-lvalue and compound-expression-as-lvalue extensions, which were deprecated in 3.3.4 and 3.4, have been removed.

" ha annakidején Te oktattad volna nekem a C-t még meg is kedveltem volna."
Épp ugyanezt akartam írni én is. :)