[Megoldva] warning: Unsequenced modification and access to 'j'

Fórumok

Na, de miért?

while (isupper((unsigned char) p[j])) buff[j++] = p[j];

Az '=' operátor right to left asszociatív. Vagy dönthet a fordító úgy, hogy a magasabb precedenciájú kifejezéseket akármilyen sorrendben értékelteti ki futásidőben, s majd a részeredmények felhasználásával lesz az értékadás jobbról balra? Mert én úgy gondoltam, amikor ezt a sort leírtam, hogy előbb meghatározza p[j] értékét, majd utána ezt értékül adja buff[j]-nek, majd j-t növeli eggyel.

Ezt a warningot egyébként a Qt Creator zúdítja rám. Miért nem mcedit-tel írom a forráskódot? Akkor boldog tudatlanságban élhetnék... :)

Megoldás. Illetve megkerestem szemmelveréssel - elratott egy darabig, amíg végignéztem a

grep -anF '++' *.[ch] | less

eredményeit. Egyetlen helyen használtam ezt a csúnya megoldást. Kijavítottam. Már úgy értem, abban a kódban, amelyikben szólt a Qt Creator mögött lévő analizátor, a nyitóban idézett volt az egyetlen ilyen, de van egy sokkal fontosabb kód, amiből termék lesz, abban nem szeretnék bugot. Abban volt egy ilyen csúnyaság.

Hozzászólások

Szerkesztve: 2025. 05. 20., k – 16:37

>Ezt a warningot egyébként a Qt Creator zúdítja rám.

Nem a fordítóból veszi? Saját értelmezője és warningjai vannak? Nem hinném. Nézz utána, hogy milyen parancsot futtat, gondolom szigorúbb warningokat kér magától, mint ahogy te szoktad paraméterezni a fordítót.

IMHO ez a j++ indexben már önmagában is zavaró, én a falra mászok ezektől. Nálam ez így nézne ki: {buff[j] = p[j]; j++;} Minek terheljem az olvasót azzal, hogy akkor ez posztfix, tehát kiértékelés után lesz inkrementálva és satöbbi? Leírnám úgy ahogy történik sorban és kész. Ezeket már tényleg kibogozza a fordító optimálisra akárhogy is írod az ekvivalens kifejezéseket.

Igazából az a kérdésem, hogy ez a sor egyértelmű a C-ben, vagy a fordítható fordíthatja-e úgy, hogy az értékadás bal oldalával foglalkozik előbb, ott inkrementálja az indexet, majd a jobb oldal ezt a növelt értéket használja. Szerintem ezt nem teheti a fordító - ebben tévedhetek -, egyértelműnek és jónak látom ezt a sort. Azokkal itt nem kívánok foglalkozni, akik kényszeresen kiteszik oda is a zárójelet, ahova nem kell, hátha a fordító rosszul emlékszik a precedenciára, és hasonlók. A mask << x + 4 is egyértelmű, de ha valaki zárójel fetisiszta, az írhatja felőlem, hogy mask << (x + 4), csak ne szaladjunk abba bele, hogy muszáj, mert biztos bugos a fordító, vagy annak a feje, aki olvassa a kódot.

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

>Szerintem ezt nem teheti a fordító - ebben tévedhetek -, egyértelműnek és jónak látom ezt a sort.

>Azokkal itt nem kívánok foglalkozni, akik kényszeresen kiteszik oda is a zárójelet, ahova nem kell, hátha a fordító rosszul emlékszik a precedenciára, és hasonlók.

Az a baj, hogy aminek egyértelműnek kellene lenni szerinted - nevezetesen, hogy mi a kiértékelési sorrend - az a jelek szerint nem is egyértelmű a szabvány szerint. (Én se tudtam.) Ezért mondom, hogy mindenki sokkal jobban jár, ha sorba le van írva, hogy mit kell csinálni. Fel se merülne a probléma, ha az inkrementálás külön sorban lenne leírva úgy, ahogy ésszerű volna.

Persze a C nyelv egy trükkhalmaz, még akkor is megviccelheti az embert, ha mindent háromszor körüljár. De ésszerűbb legalább a triviális komplexitást elkerülni. Az olyanokat, mint például ez, hogy indexben inkrementáljuk a változót. Szép trükkös, ismerem én is az egysorosokat, de már nem írok le ilyeneket többé. Jobb a békesség.

Az indexben inkrementálással nincs baj, itt a gond abból lehet, hogy ezt az indexet máshol is felhasználom, s kérdés, az még az inkrementálás előtt vagy után történik-e. Az asszociativitásból az jönne, hogy előbb, de valóban, ahogy NevemTeve írta is, lehet ezt úgy fordítani, hogy rossz legyen. Ugye, lehet ilyet is csinálni:

a = b = c = 5;

Ha nem lehetne használni az indexben inkrementálást, akkor felesleges lenne a pre- és postincrement. Azért van, hogy használjuk. Engem az lepett meg, hogy van arra esély, hogy ez úgy forduljon le, hogy rossz legyen.

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

buff[j++] = p[j]

A derék fordító maga dönthet arról, hogy ebből mit generál:
#1:

char *tmp= &buff[j];
++j;
*tmp = p[j]

#2:

buff[j] = p[j];
++j;

A jövőálló megoldás nyilván két index használata:


k= j;
while (isupper((unsigned char) p[j])) {
   buff[k++] = p[j];
}

 

Ha mondjuk kiderül, hogy az elején lévő számjegyeket át kell lépni:


k= j;
while (isdigit((unsigned char) p[j])) ++j;
while (isupper((unsigned char) p[j])) {
   buff[k++] = p[j];
}

 

#1 egy reális veszély? Azért nyomasztó, mert néhány helyen használtam ilyesmit kódban, s elég kellemetlen nagyon sok tízezer sorban az összes ++-ra keresni, s egyesével átnézni. Abból indultam ki, hogy az értékadás miatt előbb lesz kiértékelve a jobb oldal, s csak utána a bal, mert ez a #2, s akkor jó a kód.

Szerk.: Abban lehet bízni, hogy egy adott fordító legalább következetesen #2-t fordítja minden hasonló esetben? Vagy kétségbeesetten kezdjem felkutatni azt a kevés helyet a jó nagy kódban, ahol ilyen van?

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

Vannak különféle checker/linter programok, például maga a gcc (latest: 15.1), különösen a -fanalyzer opcióval.

Szerk: most látom, hogy a -fanalyzer -t a -O2 -vel kombinálva még éberebb. (Felteszem, hogy magasabb optimalizációnál pláne.)

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

"Order of evaluation of any part of any expression, including order of evaluation of function arguments is unspecified (with some exceptions listed below). The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again.

There is no concept of left-to-right or right-to-left evaluation in C++. This is not to be confused with left-to-right and right-to-left associativity of operators: the expression a() + b() + c() is parsed as (a() + b()) + c() due to left-to-right associativity of operator+, but c() may be evaluated first, last, or between a() or b() at runtime"

Eleg egyertelmu, nem kell ezt tulragozni.

Ez így elég átláthatatlan, ez az a fajta hekkes kódírás, amit nem kéne C-ben tolni, sokan az ilyen agymenések miatt utálják a C-t. Nekem az jön le, hogy egy átlag C fordító előbb megnöveli a j értékét, így a p[j]-ben a j már egyel meg lesz növelve a kifejezés végén. Semmit nem nyersz, ha ilyen tömör vagy, használj egy átmeneti változót, és írd úgy a kódot, hogy egyértelmű lépései legyenek a kiértékelésnek, így a kódod az összes fordítóval azonosan fog működni. Ezen így semmit nem nyersz, hogy megúszol ugyan egy átmeneti változót, de közben a kód kétértelműségbe, meg előre láthatatlan hibákba fut, ez inkább nekem szándékosan önszopatásnak tűnik.

The world runs on Excel spreadsheets. (Dylan Beattie)

Á, nem tudod te, mi a jó!

int8_t asciihex2nibble(char c, uint8_t *x) {

    int8_t err;

    err = 0;
    if (c >= '0' && c <= '9') {
        *x = c - '0';
    } else if (c >= 'A' && c <= 'F') {
        *x = c - 'A' + 10;
    } else if (c >= 'a' && c <= 'f') {
        *x = c - 'a' + 10;
    } else {
        err = 1;
    }
    return err;
}

int8_t asciihex2byte(char **p, uint8_t *x) {

    int8_t err;
    uint8_t data;

    err = asciihex2nibble(**p, &data);
    if (!err) {
        err = asciihex2nibble(*++*p, x);
        if (err) {
            --*p;
        } else {
            *x |= data << 4;
            ++*p;
        }
    }
    return err;
}

Figyelmedbe ajánlom a *++*p kifejezést! :)
 

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

A fordító ilyen helyzetben minden egyes esetben megcsinálja a kétszeres dereferálást? Nem jön rá, hogy ideiglenes változóban tarthatja átmenetileg a karakter címét?

Mikrokontrolleren - de az is gcc - valami ilyesmit írtam, hogy valami[i].akármi[bármi.idx].bigyó, ezt sokszor. Aztán amikor átírtam, hogy p = &valami[i].akármi[bármi.idx]; p->bigyó, majd mindig a p-vel hivatkoztam, a forráskódom ugyan szebb lett, de byte-ra pontosan ugyanakkorára fordult a kód. Nyilván az utolsó member már változott, szóval p->másvalami, és így tovább.

Csak azt akarom mondani, a fordító simán megcsinálta, pedig csak -O1 volt.

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

Hát, az optimalizáló compiler élete nem csak játék és mese. Például arra sincs garancia, hogy a `p` és az `x` nem ugyanoda mutat, ez korlátozza az optimalizációt.

Ezen segít a 'restrict' módosító: https://en.m.wikipedia.org/wiki/Restrict

Figyelmedbe ajánlom a *++*p kifejezést! :)

Mondjuk ez így ránézésre is sima netto önszopatás. Bőven elég fos undefined behavior van a kedvenc nyelvetekben magától is, hogy elég problémát generáljon, minek ilyeneket csinálni? Mi a jó ebben azon kívül, hogy rá lehet rántani, hogy mennyire okos vagy?

Meta: annál azért jobb érv kellene, mint hogy 'laikusoknak nem intuitív'.

Itt van például egyes nyelvekben a 'lambda' (beágyazott névtelen függvény): én ugyan utálom, mert nekem kicsit sem intuitív, de attól még szívesen elismerem, hogy egy fasság, amit kár volt kitalálni.

Laikusként is értem (érteni vélem), hogy mit csinál pár sec gondolkodás után (előszedi a következő elemet valami listából), ettől még nehezen tudom elképzelni, hogy ezt ne lehetne olvashatóbban leírni alig kevesebb több* karakterből, amitől nem rándul egy pillanatra görcsbe az agya az olvasónak.

*szerk

A beágyazott névtelen függvény nagyon cool cucc. Például arra is jó, hogy ne kelljen nevet adni valaminek aminek felesleges, és hogy egy helyen legyen ami összetartozik. Eseményvezérelt programozáshoz pótolhatatlan.

Van ahol szerintem is túlzás. Például a Java stream API nem a kedvencem, legalábbis amikor eleve kész gyűjteményekre használjuk, mert nekem érthetetlenebb mint az imperatív megközelítés és általában a teljesítménye is rosszabb.

Hibajelentéshez csak be kell pasztázni a stacktrace-t, abban benne van, hogy melyik sorból jött. Nem értem ezzel mi a probléma. Azzal lehet gond, hogy össze tudd kötni, hogy annak az eseménykezelőnek mi volt az eredendő oka. De ez ugyanúgy fennáll akkor, ha van neve, általánosságban igaz a callbackes rendszerekre, hogy néha nehéz a callbackből visszakövetni, hogy mégis ez mire válaszként fut éppen.

Nem menőzésnek szántam, hanem példának, mert szerintem ez kifejezetten szép a C-ben. Azért indítottam a topikot, mert nem tudtam, hogy a precedencia és a műveletek végrehajtási sorrendje függetlenek egymástól. Abban, amit példaként hoztam, szerintem nincs ilyen potenciális bug.

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

Hogy írtad volna egyszerűbben? Szerintem épp a *++*p az, ami áttekinthető. Van a karakterre mutató pointerem, aminek megkaptam a címét, ezt dereferálom, majd növelem a karakterre mutató pointert, ami után az általa mutatott karaktert átadom a függvénynek. Épp ez az, ami tiszta, világos, nem pedig az, amikor egy rakás soron keresztül kell ezt bogozni.

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

TL;DR: a compiler warningok általában jogosak, de legalábbis érdemes kivizsgálni miért jönnek. (Na mondjuk a 'dereferencing type punned pointer' az fasság: azért warning-ol, mert különböző tipusú pointerek között type-cast-olsz. Igen, mert ha azonos típusúak lennének, akkor nem kellene a type-cast.)

Ilyen tényleg van? :) Ez valóban idiotizmus. Más a tévedés, és más az, amikor a programozó tudja, hogy mit csinál. Például legyen egy struktúra, amelyre CRC-t kell számolni. Ha nem packed, akkor gondolni kell arra, hogy a hézagokban lehet RAM szemét, szóval előbb memset(), utána a struktúra arra a területre, majd a CRC számítása, természetesen type-cast-olt pointerrel.

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