Kioptimalizálja a fordító, vagy nem?

Fórumok

Vegyük ezt a két formát C-ben:

if (cond) {
  func1();
} else {
  func0();
}

cond ? func1() : func0();

Azon túl, hogy próbáljam ki, mire számíthatok? A második alak esetén a visszatérési érték nem lesz változónak átadva. Ezért kioptimalizálja a fordító, vagy mindenképpen meghívja cond értékétől függően az egyik függvényt? Tehát az első formula írható-e második alakban félelemérzet nélkül?

Hozzászólások

Ha a cond erteke nem fix a ckrdito szamara, akkor meg fogja hivni az egyik fuggvenyt. A ?: operator etekadasra van, de talan igy is leforditja a c++. A tobbi fejlesztovel ne szurj ki hogy utobbi formaban irod. :) 

Az nem kérdés, hogy lefordítja. Mikrokontrollerre lefordítottam élő kódban, amin dolgozom, onnan jött a kérdés. De aztán inkább kitöröltem, mert ha kioptimalizálja a fordító, mondván, a visszaadott értéket senki semmire nem használja, akkor ott leszek megfürödve. Szóval elbizonytalanodtam, így maradt az if () else szerkezet.

Amúgy azért kacérkodtam vele, mert ez a kis egysoros faék egyszerűségű, szerintem jól olvasható, de hát én assembly-n nevelkedtem, így aztán a tömör megfogalmazásoktól nem riadok vissza. :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

> A ?: operator etekadasra van

Ezt a baromságot honnan vetted? OK, valószínűleg a legtöbb programozó valóban csak értékadásban írja le, de ettől ilyen nincs, hogy arra VAN. És még C++ sem kell hozzá, ez már K&R C-ben is működött.

Ezt a baromságot...

Nem kicsit sértő a kezdésed! Kollégáiddal, családoddal is így beszélsz, amikor eszmét cseréltek?

Az if hivatalos neve conditional statement, azaz ez egy vezérlő szerkezet, a ?: pedig conditional operator, ami egy expression-t eredményez. Az expression jellegét tekintve visszaad valamit, az if pedig adott irányban lefut. Nem szigoróan véve arra van, de ha így jobb: "Ha megtiszteled vele a kollégádat, akkor arra használod, jelezve, hogy mi a célod vele."

Ha nem érted, nem érted. Nem te vagy barom, hanem amit mondtál, az a baromság. Lényeges különbség, megsértődni felesleges.

Visszatérve a szakmára.

Álljon itt egy idézet. A forrásnál nincs hitelesebb:

Kernighan - Ritchie: A C programozási nyelv - ez a '78-as Prentice-Hall kiadás 85-ös magyar fordítása. Sajnos az eredeti nincs birtokomban, de ez a fordítás igen.

===

A Kernighan-Ritchie könyv 2. 11 pontjában (59. o.) ez áll szó szerint:

2.11 Feltételes kifejezések

Az

if ( a > b )

 z = a;

else

 z =b;

feltételes utasítás eredményeként z a és b közül a nagyobbik értékét veszi fel. A C-ben a háromoperandusú ?: operátor segítségével az ilyen szerkezeteket sokkal rövidebben leírhatjuk. Legyen e1, e2, e3 három kifejezés. Az

e1 ? e2 : e3

feltételes kifejezésben a gép először e1-et értékeli ki. Ha értéke nem nulla (igaz), akkor e2, egyébként e3 kiértékelése következik, és a kapott érték lesz a feltételes kifejezés értéke. ...

===

Itt egy másik idézet: (62.o)

===

3.1 Utasítások és blokkok:

A kifejezések, pl. x = 0, i++ vagy printf(...) utasítássá válnak, ha pontosvessző követi őket:

x = 0;

i++;

printf( ... );

A C-ben a pontosvessző utasításlezáró jel (terminátor) és nem elválasztó szimbólum, mint az ALGOL-szerű nyelvekben.

===

Peace.

Szerintem te nem érted, hogy mit olvastál.

Pont azt mindja a 2.11, hogy vannak olyan kódszerkezetek, hogy egy adott változó (a példában a z) értéke az egy feltétel kifejezésétől függ (a példában a > b).

Azaz az if() két ágában ugyanúgy z kap értéket, csak az if() kiértékelésétől függően mást. És erre a kódmintára vezették be rövidítésként az operátort. Erre való a :?: operátor - egy adott változó értékének beállítása két értékre, egy logikai kifejezéstől függően.

 

És nem arra lett kitalálva, hogy tetszőleges kódot futtass. A példában sem az van, hogy az if() két ágában két tök külön dolgot csinálj. Hanem a cél az, hogy egy értéket képezzél. Ezért is a ?: kifejezés értéke felhasználható értékadásként (ez egy rvalue).

 

A K&R két szerzője pontosan tudta, hogy milyen esetekben eredményez jól olvasható kódot, szépen kinéző kódot a ?: használata - értékadáskor. És nem azért találták ki, hogy BÁRMILYEN if-et helyettesíts vele, ők ezt sehol nem mondják.

_Szerintem_ egy kifejezés kiértékelése nem attól függ, hogy végül értéknek adod-e. Szóljon, aki jobban tudja; én azt gondolom, hogy pusztán az értékadás hiánya miatt nem optimalizálhatja ki a fordító.

Olyan valóban lehet, hogy egy logikai ÉS illetve VAGY kapcsolat valamelyik felét már nem értékeli ki, mert a már kiértékelt részből tudni lehet a teljes kifejezés értékét. Erre valóban vigyázni kell. Ilyenre gondolok: ( expr1() || expr2() )
Itt még a kiértékelés sorrendje sem garantált úgy emlékszem. De ha valamelyik már 1 logikai értékű, a másikat nem feltétlenül értékeli ki.

Nekem egyébként semmi bajom a ? : szerkezet olvashatóságával, sőt, sokkal szebbnek tartom, mint az if...else ágakat, főleg ha nested. Szépen tagolva a ? : tömörebb és így még olvashatóbb is lehet. Én szeretem a tömör kifejezéseket. Megfelelő rutinnal szerintem nem lehet gond az olvasásuk.

Szerintem az az || balról jobbra megy, s ha az első nem 0, már nem értékeli ki a másodikat. Úgy tudom, ez konzekvens, például azért, hogy lehessen ilyet csinálni:

uint32_t *p;

if (p && *p) --*p;

Itt ugye, ha p == NULL, akkor már nem dereferálja, s nem nézi meg, hogy a sehova sem mutató pointer által mutatott érték nulla-e.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

A FORTH a fordított lengyel jelöléssel dolgozik (Reverse Polish Notation), azaz  a műveletek elvégzése előtt várja a művelet paramétereit (pl. a stack-en). Ezért nem kellenek a zárójelek, és teljes a műveletek szintjén a demokrácia, mert minden művelet egyenrangú, nincs precedencia sorrend, a leírás sorrendjében hajtódnak végre a műveletek.

"Share what you know. Learn what you don't."

a + b * c + ( d + e ) * f   kifejezésre próbálj meg balról jobbra karakterenként haladva precedenciahelyes kiértékelőt megvalósítani.
El fogsz jutni a verem bázisú játszmáig és az RPN logikáig. A vége ugyanis
a b c * + d e + f * +
lesz RPN-ben felírva. RPN ha érdekel, a Forth iskolapélda, a dc parancs is RPN, több kalkulátorokban bekapcsolható az RPN mód.

RPN logika szerint: érték ha jön, azt beteszi a stack-be és ha operátor jön, akkor a stackből a legfelső 2 értékkel elvégzi a műveletet. Az eredményt szintén a stackbe teszi vissza. És így tovább.
Ezt az átalakítást nekünk még tanították a BME-n, de akkor én is átsiklottam felette. Aztán amikor kellett, akkor szívtam vele egy sort és szidtam magam, hogy akkor nem figyeltem oda. Mára megtanultam.

Én is tanultam, szerintem középiskolában még, de lehet, hogy a BME-n is. Megvolt az az algoritmus is, hogy hogyan lehet átírni a normál formulát RPN-re, de rég volt, nem foglalkoztam vele, azt meg nem tudtam, hogy az RPN minek a rövidítése, de sejtettem, hogy erről van szó. Egyébként fiatalon csináltam Z80 alapú frekvenciamérőt, abban implementáltam lebegőpontos aritmetikát, és RPN lett a megvalósítás, mert gépi feldolgozásra nagyon kényelmes, sokkal jobb, mint parsert írni lineáris feldolgozáshoz, de ahogy írod, abból is RPN lesz praktikusan.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Nem erről van szó. Hanem arról, hogy ha K&R-re hivatkozunk, akkor olvassuk már el rendesen a könyvet, hogy ők minek szánták a ?: operátort, milyen kód helyettesítésére.

Az más kérdés, hogy mire lehet használni.

for ciklus is létezik a nyelvben, pedig teljesen ekvivalens minden for ciklus egy while ciklussal.

gcc (Debian 6.3.0-18+deb9u1) byte-ra pontosan ugyanazt forditja belole, de en is az elsot ajanlom, olvashatobb a kod tole.

Nem gondoltam, hogy ennyire jó kódot fordít. Tegyük hozzá, valójában épp attól tartok, hogy nincs használva a ternary condition eredménye, ezért eltűnhet az egész. De jobban belegondolva mégsem, mert akkor egy rakás függvény hívás az enyészeté lenne az optimalizálás következtében. Szóval függvényt muszáj hívni, meg gondolom, volatile változónak történő értékadás is kötelező akkor is, ha az nincs felhasználva sehol, például azért, mert az egy hardware regiszter, ami működtet valamit.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Csak rájöttél. Nem szabad kioptimalizálnia, hiszen egy fv-nek nem csak a visszatérési értéke számít (*), hanem azért csinálhat is valamit :-) Nyilván nem véletlenül van olyan programnyelv, ahol meg is különböztetik a(z eredményt visszaadó) függvényt a(z eredményt nem visszaadó) eljárástól.

(*) Amúgy ha nem adsz vissza értéket a függvényeddel, akkor miért nem deklarálod void-ként? Ha pedig void-ként deklarálod, akkor eleve jelzed, hogy NEM a visszatérési értéke a lényeges, hanem a mindenféle egyéb, amit csinál. Pl. extern változónak ad értéket.

Ha ez a feltételezés

A második alak esetén a visszatérési érték nem lesz változónak átadva. Ezért kioptimalizálja a fordító[...]?

esetleg megállja a helyét, akkor azon töprengek, hogy vajon ezen mit kéne hogy változtasson a ?: operátor hiánya... vagyis ha sizmplán annyi a kód, hogy meghívsz egy függvényt és fittyet hánysz annak visszatérési értékére, avagy eleve void típusú a függvény. Mert akkor úgy lenne logikus, hogy azt is kioptimalizálhatná, nem? Hiszen ott sem lesz „megörökítve” a visszatérési érték. Aminek nyilván semmi értelme nem volna.

Folytatva:

Egyrészt, a definíció az az, hogy ki kell értékelnie, lásd például itt, persze ez nem a specifikáció maga.

Másrészt, az optimalizálásnak olyannak kell lennie, hogy garantálja a funkcionálisan azonos viselkedést. Olyat soha nem csinálhat egyetlen fordító sem „optimizálás” címszó alatt, hogy kihagyja egy érdemleges dolgot végző függvény meghívását. Elvileg szó nem lehet olyanról, hogy eltérő legyen a program viselkedése másmilyen optimizálási szint esetén(*). De olyat például csinálhat, hoogy ha látja, hogy func0() és func1() is üres, vagy mondjuk mindkettő csak visszaad egy – egyébként eltérő – értéket de amúgy semmi mást nem tesz, akkor ezt az egész ?:-os sort khagyja, mert be tudja bizonyítani a fordító, hogy funkcionálisan megegyező (és speciel kissé gyorsabb) kódot generált.

(*) Ha olyat írsz le, ami undefined behavior a nyelv specifikációja szerint, akkor csinálhat ilyet legalásain a fordító. Meg olyat is látott már a világ, hogy az „elvileg” és a „gyakorlatilag” nem egyezett meg.

Elvileg szó nem lehet olyanról, hogy eltérő legyen a program viselkedése másmilyen optimizálási szint esetén(*)

Kb. 20 éve Delphiben futottam bele a következőbe:

for i := 1 to 3 do func(i);

Ez a roppant bonyolult kód amennyiben be volt kapcsolva az optimalizáció 3 - 2 - 1 sorrendben futott le, ha ki volt kapcsolva akkor meg 1 - 2 - 3 ahogy azt vártuk tőle...

Én nagyon sokat használom a ? operátort, különösen áttekinthető a kód, ha egymásba ágyazott is-else-elif - fel kéne helyettesíteni.

pl:

      case 'B': bp_ndx=(data<ZMIFC->meadat.CHN[ndx].bpFlo[1])    ?    1 :
               (data<ZMIFC->meadat.CHN[ndx].bpFlo[2])    ?    2 :
               (data<ZMIFC->meadat.CHN[ndx].bpFlo[3])    ?    3 :
               (data<ZMIFC->meadat.CHN[ndx].bpFlo[4])    ?    4 :
               (data<ZMIFC->meadat.CHN[ndx].bpFlo[5])    ?    5 :
               (data<ZMIFC->meadat.CHN[ndx].bpFlo[6])    ?    6 :
               (data<ZMIFC->meadat.CHN[ndx].bpFlo[7])    ?    7 : 8;

(jelkarakterisztikát vizsgál)

Na most ez szerintem if-else párral rémálom lenne olvasás szempontjából Képzeljétek hozzá, hogy az én szövegszerkesztőmben a '?' jelek egymás alatt vannak (-:: 

Előfordulhat, hogy a ? operátor hatására jobb kód fordul. Soha nem vizsgáltam, de ezt írja a szakkönyv és én elhiszem neki.

> Sol omnibus lucet.

Két apróság:

  • a cond visszatérési értéke nem csak a ?: esetén nem kerül változóba, hanem az if szerkezet esetén sem,
  • a cond visszatérési értékére mindkét esetben szükség van, hogy a logikai döntés meghozható legyen és ennek eredményeképp vagy az egyik, vagy a másik ág fusson, függetlenül attól, hogy az if/else vagy ?:. Tehát nem optimalizálható ki a cond futtatása egyik esetben sem.

Legjobb tudásom szerint, nyugodtan használhatod a ? operátort elvileg. Gyakorlatilag elképzelhető olyan fordító ami érdekes viselkedést produkál. De az ember nem nagyon cserélgeti a fordítót, pláne nem projecten belül. Ez meg simán mehet a teszt kódba, ha új fordítót választanátok a következő projecthez.

Azoknál a kódoknál amit én írok ott az if-es szerkezet játszik, azokban a problémában az az olvashatóbb. Dehát kinek a pap, kinek a papné kezdetű mondás igaz ide is.

funct(...)
{
 return({ int z;if ( cond ) z=func1(); else z=func0(); z;});
}

Nem ez volt a felvetés. Az a két függvény legyen mondjuk printf(). Igazából csak sajnáltam a sok programsort meg a sok gépelést, s gondoltam, lehet ezt tömören is fogalmazni. Aztán látom, az a válasz, hogy jó lesz, de kicsit zavaró, nehezen olvasható.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Szegről-végről idetartozik, nem nyitok újabb topic-ot, de nem tudom a választ. Mondjuk van egy 32 bites architektúránk és egy volatile int64_t x; változónk.  Valahol a programban legyen egy x++;, miközben becsap a megszakítás, abban meg legyen egy x = 0;. Az a kérdésem, hogy ez az x++ atomikus művelet? Vagy kell attól tartani, hogy az alsó 32 bit növelése után elfogadásra kerül a megszakítás, x == 0 lesz, majd visszatérést követően, ha épp volt átvitel a felső 32 bitre, akkor emiatt ez az átvitel x = 0x100000000 eredményt okoz?

Továbbá, ha atomikus, akkor minden egyes x-szel való művelet előtt globális IT tiltás, a végén pedig IT engedélyezés van?

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Nem definiált a működés, csak akkor, ha lock-free atomic változók, vagy volatile sig_atomic_t típusú változók.

"When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects that are neither lock-free atomic objects nor of type volatile sig_atomic_t are unspecified, as is the state of the floating-point environment. The value of any object modified by the handler that is neither a lock-free atomic object nor of type volatile sig_atomic_t becomes indeterminate when the handler exits, as does the state of the floating-point environment if it is modified by the handler and not restored to its original state."

Ez a C11 szabvány legutolsó ingyenesen elérhető draft verziójából van.

Forrás: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf 5.1.2.3 Program execution fejezet.

 

Ami fontos még itt, az a volatile szemantikája. Erről ezt írja a szabvány a 6.7.3 Type qualifiers fejezetben:
An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.134) What constitutes an access to an object that has volatile-qualified type is implementation-defined.

 

Szóval amit elmondtál, az undefined, az adott platform adott fordítója dönti el, hogy mit csinál. A C nyelv nagyon aluldefiniált ilyen értelemben szemantikailag, rengeteg a nemdefiniált, illetve szabvány szerint implementációfüggő viselkedés.

A szabvány amúgy megengedi, és szerintem ezt meg is szokták csinálni, hogy nem is létezik ilyen platformon int64_t. Az egész stdint.h-ban definiált típusok opcionálisak:
"These types are optional. However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation, it shall define the corresponding typedef names."

Szóval az, hogy az stdint.h-ban mi van, az minden egyes platformra implementációfüggő. Szóval simán lehet, hogy le se fordul az int64_t-re hivatkozó kód az adott fordítóval és libraryvel, mert nem támogatott.

A konkrét esetben lefordul. Relatív időnek az egészrészét tárolom egy struktúra egyik tagjában, a másikban a törtrészt. Aztán, amikor egy függvényben kiszámoltam az egyiket, majd a másikat, kétségbeesetten kezdtem keresni, hogy ugye nem módosítom egy IT callback függvényében, mert ez így nem atomikus. Onnan jött a gondolat, hogy lehet, célravezetőbb volna egyetlen int64_t-ben tárolni, de aztán rájöttem, talán ez sem segít. Ekkor bizonytalanodtam el. :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Az a kérdésem, hogy ez az x++ atomikus művelet? 

Ha megnezed a *.disasm-ot, hamar kiderul hogy nem lesz atomi. Persze lehetnek beteg architekturanak beteg trukkjei (pl a 8-bites AVR-en tudsz atomi 16 bites ++ ill -- muveletet csinalni, szoval ilyen erovel siman elkepzelheto hogy egy 32-bites arch-nak vannak rejtett 64-bites muveletei is), ugyhogy valojaban meg az is lehet hogy atomi lesz ;] 

Továbbá, ha atomikus, akkor minden egyes x-szel való művelet előtt globális IT tiltás, a végén pedig IT engedélyezés van?

Igen. Ha igy csinalod, biztos jo lesz. De nem kell globalisan tiltanod a megszakitasokat, hanem eleg csak azt tiltanod ami az x-et valtoztatja. Amelyik egyszerubb/olcsobb/celravezetobb, ez mar elegge mikrokontroller-fuggo. Igazabol a problema az ami erdekes, hogy mi az az alkalmazas ami megszakitasbol is es main/thread-bol is modosit egy valtozot. 

Például IT-ből jár egy óra, de alap szintről jön egy kérés, hogy töröljük, vagy állítsuk be valamire. Persze lehet ezt szépen csinálni. Egy flag-gel jelezni kell az IT számára, hogy van egy új valid érték egy változóban, azt kegyeskedj a változóból az általad járatott órába írni, majd töröld vissza a flag-et.

Ami az architektúrákat illeti, Z80-on is van INC BC, INC DE, INC HL például, pedig őkelme is 8 bites alapvetően. :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Ha egyértelműen meg tud róla győződni a fordító, hogy nincsen mellékhatása (pl. nincs io, globális változó változtatása) a funkcióhívásodnak, akkor ki fogja "optimalizálni" - vagy inkább már fordítási időben figyelmeztet rá, hogy felesleges hívás.

Én inkább arra gondolok, hogy most(!) ok. Viszont a kompiler verzió váltásakor, jobb esetben warning -ot dob rosszabb esetben ki optimalizálja és nem szól egy szót sem. Gondot okozhat, debug/trace esetén is.
30(?) éves munkálkodásom alatt sok különféle C trükköt láttam, de sok vezetett később nagyon nehezen azonosítható hibákhoz.
Egyébként ez még a szelídebb verziók egyike.

Egyébként, ha nincs több művelet a feltételek alatt, akkor a zárójeleket is elhagyhatod, azzal is rövidül kód. Megteheted, hogy csinálsz egy (akár globális) dummy -t amire minden ilyen kifejezés elküldheti az eredményt. Esetleg alkalmazhatsz ugrótáblát.

* Én egy indián vagyok. Minden indián hazudik.

Esetleg alkalmazhatsz ugrótáblát.

Függvénypointereket előszeretettel használok, de nem jellemző, hogy kételemű táblázatot csinálok, az még ágyúval verébre, s inkább hosszabb, lassabb, bonyolultabb, mint konkrétan bedrótozni.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Gondolom embedded kódról beszélünk. Egy ponton, két eljárás miatt biztos nem éri meg, csak "elegáns". (Érdekes lehet vajon ezt kioptimalizálja?)

Esetleg érdemes megnézi az assembly -t a két különböző megoldással. Valaki a múltkor mutatott egy ilyen compiler tesztelő honlapot.

* Én egy indián vagyok. Minden indián hazudik.

Szerkesztve: 2021. 04. 09., p – 20:06

Hat, a torvény betűje szerint nincs különbség a ket forma között. A stilisztikai kerdes az inkább szubjektív, minden csapat el tudja dönteni, hogy mennyire szereti a kompakt kódot.

Az optimalizacio egy fokkal fogosabb. A C++ szabvány azt mondja, hogy a fordító bármilyen optimalizaciot megtehet, ami nem változtatja meg a program "megfigyelhető viselkedését" ("observable behaviour"). Szóval, ha a fuggvenynek nincs mellékhatása (funkcionális terminológiával elve "pure"), akkor a fordító kioptimalizálhatja a fuggvenyhivast. Az, hogy a fgv pure-nak találja a fordító vagy nem, az az optimalizacio minőségétől függ, a lényeg az, hogyha téved, akkor az impurity irányba tévedhet csak. Tehát lehet, hogy van egy pure fgv, amit az optimalizacio impure-nak tekint es nem eliminál, pedig a szabvány betűje szerint megteheti.

Mint minden jo szabály alól, ezalol is van kivétel, es az a kivétel éppen határos a felvetett problémával. Az úgynevezett RVO (Return Value Optimization), ami azt jelenti, hogy fuggvenybol visszaadott értek eseten a másolás (sot esetleges többszöri másolás) kioptimalizálható, meg akkor is, ha ez megváltoztatja az observable behaviour-t. Nem vagyok benne biztos, hogy ez egészen pontosan hogyan működik együtt azzal a helyzettel, amikor a visszatérési értek el lesz dobva. A konzervativ tippem az, hogyha a visszeteresi értek előállítása es destruktora pure, de a másolása impure, akkor meg mindig teljesen elhagyhato a fuggvenyhivas.

 Olvasnivalo a temaban:

https://en.wikipedia.org/wiki/Copy_elision

https://en.cppreference.com/w/cpp/language/as_if

A második link leírja, hogy egész pontosan milyen esetekben számít a program megfigyelhető viselkedése ugyanolyannak es mikor nem, szóval ezen nem kell spekulálni, a szabvány elég világosan beszel (mégha bonyolult is a terminológia).

Szerkesztve: 2021. 04. 09., p – 20:44

Újabb kérdés jutott eszembe. Van egy függvénynek két bemeneti paramétere. Ezeket úgy adom meg, hogy hívok egy másik függvényt, amelyik parse-ol egy buffer tartalmat, a visszatérési értéke pedig annak eredménye, de közben a bufferen lépteti a pointert. Kicsit az atoi()-ra tessék gondolni, de nem teljesen. Bízhatok abban, hogy az eredeti függvényem paramétereibe a helyettesítés balról jobbra történik időben? Ugye nem mindegy, a parse-olás sorrendje. Valami ilyesmi:

fnc(gethex(&p), gethex(&p));

Itt az első gethex()-et mindenképpen hamarabb kellene hívni, mint a másodikat. Egyelőre biztonságosan úgy csináltam, hogy előbb felolvastam az elsőt egy változóba, az első paraméterként a változót adtam át, s csak a másodikat helyettesítettem valahogy így:

a = gethex(&p);
fnc(a, gethex(&p));

Kell ez, vagy mehet az első forma? A gethex() prototype-ja valami ilyesmi:

uint8_t gethex(char **p);

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Nem mindegy, mert ha a másodikat hívja elsőként időben, akkor az a függvény hozza fel a bufferből az első hexadecimális számot, majd a másodikként futó, de az fnc() első paraméterét adó pedig a másodikat. Ennélfogva a hívás sorrendjétől függően felcserélődhetnek az fnc() által kapott paraméterek. Ugye itt p egy futó pointer a bufferen, de ennek a pointernek a címét kapta meg a gethex(), tehát neki ezáltal módjában áll a p pointer módosítása. Két karakter felolvasását követően 2-vel megnöveli p értékét.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Persze, ez idaig nyilvanvalo, azert irtam, hogy en is az "undefined behaviour"-re emlekszem.

Ugy ertettem, hogy ha a 

fnc(gethex(&p), gethex(&p));

format hasznalod a 

a = gethex(&p);
fnc(a, gethex(&p));

helyett, es eppen szerencsed van, mert jo sorrendben hivja a fordito, akkor sem lennel vele elorebb. Ugyanis az elso gethex visszatereset ugyis elteszi valahova, szoval ugyanarra fordul a ket kod (az elsoben is lesz egy a = gethex(&p); resz, csak nem fersz hozza a-hoz). Olvashatosagban is az utobbi a jobb, ha az "a" helyett valami ertelmes modon elnevezed (igazabol lehet, hogy meg a masik gethex-et is kulon hivnam).

fnc(gethex(&p),gethex(&p));
vs.
username=gethex(&p);
password=gethex(&p);
login(username,password);

Szerintem az utobbi karbantarthatobb, mert tudod mi van benne.

A strange game. The only winning move is not to play. How about a nice game of chess?

Értem, amit írsz, ám ha kiírom az első formulát, akkor mindenképpen az 'a' változó kap hamarabb értéket, tehát jó lesz a sorrend. Ha nem, akkor is lehet, hogy ugyanazzá fordul, de ebben nem lehetek biztos, te is azt írod, hogy nem definiált, mi fog történni.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

A "nem definiált" és "nem specifikált" két különböző fogalom a C/C++-ban. Az argumentumok kiértékelési sorrendje "nem specifikált" (unspecified). A "nem definiált" (undefined) mást jelent. Ez nem csak szőrszálhasogatás, hanem eléggé más a két fogalom jelentése, szóval érdemes megismerkedni velük.