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.
- 939 megtekintés
Hozzászólások
>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.
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
>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.
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
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 hozzászóláshoz be kell jelentkezni
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];
}
- A hozzászóláshoz be kell jelentkezni
Mármint nem j, hanem j++.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Hát ebben van igazság, bocsi.
Így egymás közt azt is bevallhatom, hogy gyakran így csinálom a hasonlókat:
for (--j; isspace(from[++j]););
for (--j; ++j<limit && isdigit(from[j]); )
into[k++]= from[j]
- A hozzászóláshoz be kell jelentkezni
Itt lehet bővebben tájékozódni:
https://en.wikipedia.org/wiki/Sequence_point
- A hozzászóláshoz be kell jelentkezni
#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
- A hozzászóláshoz be kell jelentkezni
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.)
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
Nem egyszerű azért ez a dolog, pl. az első oké, a második tilos a fordítónak:
val = a[i++] - b[j++] -> val=a[i]-b[j]; ++i; ++j;
val = a[i++] || b[j++] -> tmp=a[i]; val= tmp? tmp: b[j++]; ++i;
- A hozzászóláshoz be kell jelentkezni
Miből gondolod, hogy a második tilos a fordítónak? Mind a || operátor, mind a ?: operátor rövidzárral működik. A || nem értékeli ki a második operandusát, ha az első nemnulla; a ?: pedig akkor és csak akkor értékeli ki a második ill. harmadik operandusát, ha az első operandusa nemnulla ill. nulla.
C99 6.5.14 Logical OR operator, p4:
Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; there is a sequence point after the evaluation of the first operand. If the first operand compares unequal to 0, the second operand is not evaluated.
C99 6.5.15 Conditional operator, p4:
The first operand is evaluated; there is a sequence point after its evaluation. The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below. [...]
- A hozzászóláshoz be kell jelentkezni
Nem írtam, hogy ez C, viszont attól még ugyanígy igaz. Nagyszerű. :( Kereshetem a forrásban a nem sok, de létező ilyen megoldásaimat. :((
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Még mindig a linter/checker programokat ajánlom.
gcc pl. tudja, hogy ez nem jó:
char oda[5];
sprintf(oda,"%u", n);
ez meg jó:
char oda[5];
sprintf(oda,"%u", n%10000);
- A hozzászóláshoz be kell jelentkezni
Ja, hogy kiszúrja, ha nem fér bele...
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Itt ez ugyan valami gcc, de PIC32, MIPS core-ra. Erre nem tudom, van-e szerszám. A másik topikban felvetett nem blokkolós I/O az viszont linuxos PC-n futó program.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Úgy szokták ezt orvosolni, hogy legalább a program legnagyobb részét meg lehet úgy írni, hogy mindkét platformon forduljon, és akkor a toolok is mind végigmennek rá.
- A hozzászóláshoz be kell jelentkezni
Van egy néhány megabyte-os include file, amely leírja a mikrokontroller regisztereit, hardware-ét. Már itt baj lesz szerintem.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Néha az a legjobb kezdet, ha csak úgy elkezdjük, és meglátjuk, mi lesz.
- A hozzászóláshoz be kell jelentkezni
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)
- A hozzászóláshoz be kell jelentkezni
Á, 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 hozzászóláshoz be kell jelentkezni
Na az pont egy performanciacsökkentő intézkedés... inkább így:
int8_t asciihex2byte(char **pptr, uint8_t *x) {
register char *p= *pptr;
...
*pptr= p;
return err;
}
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
Ha van fügvényből kihívás valahol, akkor addigra az összes pointer szerinti írást meg kell tenni. Tehát a p-t az eredeti helyén is kell frissíteni, nem elég regiszterben. Ha viszont beteszed egy átmeneti változóba, akkor megteheti a fordító, hogy csak regiszterben van, akkor is ha van függvényhívás a függvényen belül.
Ha nincsen kihívás a függvényből, akkor szerintem szemantikailag mindegy, hogy csinálunk-e átmeneti változót. Kivéve, ha a char **p-nek van valami módosítója, hogy minden esetben vissza kell írni tényleg, akkor nem tudja kioptimalizálni a fordító.
Amúgy, ha pointer pointer helyett átadod a sima pointert és a végén visszaadod a módosítottat, az lehet gyorsabb, mert azt megúszhatja regiszterben oda vissza passzolgatással együtt, nem kell RAM-ba írni. Sajnos a C nyelvben nincsen tuple visszatérési érték, se exception, ezért szórakozni kell a hibaértékkel, bár talán NULL értékkel meg lehet úszni a hiba jelölését.
- A hozzászóláshoz be kell jelentkezni
Igen sok függvényemnek a visszatérési értéke a hibakód, s ha van outputja, azt a neki átadott pointer által mutatott változóban, tömbben meséli el. Ha csak egy skalár az eredmény, akkor szoktam azt is, hogy negatív érték a hibakód, pozitív érték vagy a nulla pedig a helyes visszatérési érték, tehát az output.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
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?
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
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 hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
Nagyon, például egy debuggolásnál, hibajelentésnél, újrafelhasználásnál: hát ott a 'foo' függvény 65-70 százalékánál van a beágyazott függvény, amit hagyományos észjárással egy külön 'neves' függvényben csináltunk volna.
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
Külön viccess ilyennel "menőzni" egy olyan topic-ban ami alapján egyértelmű, hogy tudásbeli hiányosságok vannak a nyelv alapvető működésével kapcsolatban, ráadásul érintőlegesen pont a pre/post increment kapcsán...
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
Sehogy, mert hál istennek ilyeneket csak olvasnom kell :D De eddig még mindig megúsztam, hogy így legyen végigiterálva egy akármi, szóval szerintem azért lehet másképp is :)
Ha nekem kellene, akkor őszintén szólva csak az alapján, amit itt ti, C-hez értő kollégák elő tudtok adni arról, hogy az ilyen egymásra matekozott pointer műveletek és inkrementálós shortcutok mekkora fosott side effecteket tudnak előadni különböző csillagállásoknál, tuti, hogy minden ilyet kerülnék mint a szar.
Egyébként meg írnék bele valamit arról, hogy itt valami elem indexet léptetek. Imho már eleve ott el van rontva -- bár készségesen elismerem, hogy c-ben ezt talán túlzás elvárni, -- hogy a egy valamilyen konténer adatszerkezetet az elemei direkt basztatásával járunk be, nem a konténeren keresztül.
- A hozzászóláshoz be kell jelentkezni
egy valamilyen konténer adatszerkezetet az elemei direkt basztatásával járunk be, nem a konténeren keresztül
Ugye, nem sokat programoztál assembly-ben? :)
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Bárcsak direkt odaírtam volna, hogy értem, hogy itt nem mindig van erre sok felesleges kör nélkül lehetőség...
- A hozzászóláshoz be kell jelentkezni
Lehetőség? Szükség sincs ilyenekre. A programozás végsősoron nagyon leegyszerűsítve a hardware, egy szinkron szekvenciális hálózat vezérlése. Az absztrakciókkal nagyon elrugaszkodtunk ettől, de ez elég rendesen tesz overhead-et a végrehajtásba.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Persze persze, írjunk mindent assemblyben, vagy legalábbis cben kizárólag pointerekre bytokat írva...
- A hozzászóláshoz be kell jelentkezni
Meg szerencse, hogy a junior kollegad kodjat prodba kerules elott elkaptad code review-n, igy nem lett belole kulonosebb baj!
A strange game. The only winning move is not to play. How about a nice game of chess?
- A hozzászóláshoz be kell jelentkezni
A példát látod bugosnak? Vagy az a problémád, hogy bízunk egymásban, nem vagyunk software-fejlesztő cég, s így nem nézegetjük egymás kódjait?
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
"nem vagyunk software-fejlesztő cég, s így nem nézegetjük egymás kódjait" - ez egyfajta cégkultúra, a ti dolgotok. Nekem "több mint furcsa" de semmi közöm hozzá, ti tudjátok...
--
zászló, zászló, szív
- A hozzászóláshoz be kell jelentkezni
Nekem az a furcsa, ha valakinek a gondolatmenetébe másnak is bele kell élnie magát. Ez azt jelenti, hogy ugyanazt a munkát többen végzik el. Akkor akár az is írhatta volna a kódot, aki átbogarássza azt. Sok pénzetek és időtök lehet, meg persze kevés bizalmatok egymásban. Arról nem is beszélve, hogy semmi kedvem nem lenne arról vitázni, hogy miért így oldottam meg valamit, mert biztos, hogy mindenkinek van egy jó ötlete. Hát akkor csinálja ő! Ha kitalálok valamit, ahhoz érzelmileg is kötődöm. Nem favágó vagyok, nem égtem ki, és nem is vágyom erre, de szerintem a review-k alkalmával biztosan kiégnék, mert nem érezném magaménak a munkát, ha mások beledumálnának. Lehet benne bug, akkor szóljon a kolléga, mondja el, mit tapasztalt, ki fogom javítani, de a hogyanba ne szóljon bele! Egyébként logokat írok időbélyeggel, nagyon beszédes, látszik, ha valami szörnyűség van.
Persze, értem, amennyi vád, kínos helyzet érte már a software fejlesztő cégeket, azok alapján a management nyilván a legtöbb ilyen helyen úgy döntött, hogy nem bízik meg a saját munkavállalóiban.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Én csinálok ilyen review-s dolgot. Van ahol kötelező is. Például egy repülőgépet vezérlő kód nem is készülhet másképpen. Valami értelme van, persze még ezzel együtt is csúsznak be hibák.
>Ez azt jelenti, hogy ugyanazt a munkát többen végzik el. Akkor akár az is írhatta volna a kódot, aki átbogarássza azt.
Ez tényleg így van, hogy egy minőségi review igen költséges. De mégis megérheti, mert többlet bizonyosságot ad, hogy nincs hiba benne.
>kevés bizalmatok egymásban
Bárki hibázhat, ezt nem kell így venni, hogy ez valami negatív dolog lenne. Ahogy a két pilótára se mondjuk, hogy azért kell mert nem bíznak egymásban. Azért kell mert előfordulhat hibázás, és annak a hatását minimalizálni akarjuk. Persze a szándékos károkozás esélyét is csökkentjük, az is igaz. De ha te nem akarsz szándékosan kárt okozni, akkor mitől lenne hátrány, hogy review-znak?
>Arról nem is beszélve, hogy semmi kedvem nem lenne arról vitázni, hogy miért így oldottam meg valamit, mert biztos, hogy mindenkinek van egy jó ötlete. Hát akkor csinálja ő!
Ez nekem is valós probléma. Van eset, amikor fontosnak tűnik, hogy úgy maradjon ahogy terveztem. Akkor az utolsó szögig elmagyarázok minden gondolatot, hogy miért pontosan úgy van. Egyébként ez hasznos, mert segít terjedni a tudást a tervezési döntésekről. Gondolj bele, hogy egy cégnek nagyon nem jó, ha csak egy ember ért valamit. Ha átment a review-n, akkor már ketten értik. És itt se valami ellenségeskedésre gondolj, de egy ember bármikor megbetegedhet, vagy akár meg is halhat mindenkinek a legjobb szándéka ellenére is.
Volt olyan is, hogy ugyan nem értettem egyet a reviewer meglátásával, de a kedvéért átírtam valamit csak hogy legyünk túl rajta. Mert amúgy felesleges részletkérdés volt, működött úgy is. Ízléseken felesleges vitatkozni. Erről sokan beszélnek a tech influencerek, hogy mindenki úgy érzi a reviewn, hogy valamit bele kell írni, ha már dolgozik vele, és emiatt jönnek ezek, hogy ezt én máshogy hívnám, meg hasonlók.
Én egyébként a review-nak a technikájával nem értek egyet: a legtöbb helyen kommitonként review-znak, aminek szerintem túl kicsi a granularitása. Fel kell venni a fonalat, meg kell érteni a kontextust akár pár sor kedvéért. A kereszthatásokat a néhány megváltozó sor alapján sokszor fel se lehet fogni, pedig ugye éppen az volna a lényeg, a hibák többnyire nem nyilvánvaló helyen vannak. Plusz a review kényszere hátráltatja annak a munkáját, akinek pusholni kellene valamit, az ő munkájára mások munkája épülhet, stb -> többszörös lassulást okozhat. Ráadásul még 3x átírhatják mire release lesz belőle, az 2x feleslegesen elvégzett munka. Emiatt én azon a párton vagyok, hogy nagyobb lépéseket egyben kellene review-zni utólag. Hatékonyabban elvégezhető és minőségibb az eredmény is.
- A hozzászóláshoz be kell jelentkezni
Nem bizalom kerdese, akarki benezhet barmit a kodban. Mikrokontrolleres projectnel raadasul nem annyi, hogy kiadas utan 3 hettel kitolsz egy javitast. Amugy a tesztelok is penzbe kerulnek, megis van, amikor van ertelme, mert a hiba dragabb lenne.
Olvashato kodot sokszor minimalis lassulas karara is jobb irni, mert jobban atlatod, es kevesbe lesz benne hiba. Ha ujra bele kell nezned, akkor idoben is jobban jossz ki belole. Persze gyakran irok en is egyszerhasznalatos - jellemzoen bash vagy Python - kodot, ott ennek nincs jelentosege.
A strange game. The only winning move is not to play. How about a nice game of chess?
- A hozzászóláshoz be kell jelentkezni
uh, régi "szép" emlékek jöttek elő arról, milyen volt soronként végigkövetni mások által írt olyan kódot, amit másfél évre parkolópályára tettek és már senki sem volt ott a készítők közül, s a specifikáció/üzleti logia leírása sem egyezett meg vele, némely képességek nem voltak valójában elkészítve, míg mások már benne voltak, csak nem a specifikációt követve.
Hú de utáltam akkor a lambdázást is.
- A hozzászóláshoz be kell jelentkezni
Ez addig rossz, amíg nem értetted meg, mi volt a költő szándéka, majd újra nem írod az egészet, vagy legalább átszervezed úgy, hogy jobban illeszkedjen abba a környezetbe, amelyet köré írtál. Ha ezen a holtponton átesel, kezesbárány a kód, mert már érted, mi miért van benne.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Olvashato kodot sokszor minimalis lassulas karara is jobb irni
Ezzel egyetértek, ha valóban minimális a lassulás. Teszem azt, önálló függvénybe kitenni valamit, ami logikailag jól elkülönül, s ezt akkor is, ha csak egyetlen helyről lesz hívva. Még az is lehet, hogy erre a fordító is rájön, s befordítja inline-nak. Vagy nem, de akkor is megéri szebben írni.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
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.)
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
Ez a téma háborús övezet a fordítóprogramok fejlesztői és a lefordítandó programok fejlesztői között :)
A kulcskifejezés a TBAA -- Type-based Alias Analysis. Ha rákeresel, sokat tudsz róla olvasni.
Röviden arról szól, hogy különböző típusú objektumokra mutató pointerek nem mutathatnak ugyanoda (mert ahhoz az kellene, hogy egy adott objektumnak egyszerre két típusa legyen). Az pedig, hogy két pointer nem mutathat ugyanoda, lehetővé teszi a fordítónak, hogy levezesse, hogy egy adott objektumot pontosan egy pointeren keresztül lehet elérni (az adott környezetben). Ez pedig azért nagyon hasznos optimalizálás céljára, mert a fordító elkezdhet "csalni": amíg arra ügyel, hogy azon a bizonyos egyetlen pointeren keresztül mindig érvényesnek "hasson" az objektum tartalma, addig egyébként olyan kódot generál annak az objektumnak a manipulálására, amilyet akar. És az derült ki, hogy ezen "nagyot lehet nyerni", a bináris futási sebességében.
Emiatt jelent meg a C99-ben a restrict kulcsszó, és emiatt vezették be az "effective type" szabályokat (lásd 6.5 Expressions, p6-7). Lényegében arról szólnak e szabályok, hogy a compiler szemszögéből hogyan "terjed" egy objektum típusa. A mindenki kedvencének számító "type punning"-nak ezzel lényegében leáldozott a szabványos C-ben (kivétel a union-ök mezőin keresztüli manipuláció -- de még az is csak a C17-ben (esetleg C11-ben) vált explicitté, egy lábjegyzet által).
Nyilván a "type punning" egy lényegi eleme a C-nek, ezért rengeteg projekt elutasítja az "effective type rules"-t, és kikapcsolja a TBAA-t a fordítóprogramban. Lásd például a Linux kernel gyökér-Makefile-jában:
KBUILD_CFLAGS += -fno-strict-aliasing
A gyakorlati tanulság ugyanis az, hogy a TBAA / effective type rules:
- iszonyat kényelmetlenné teszi a C-ben programozást azok számára, akik értik ezeket a szabályokat,
- és kezelhetetlen biztonsági bughalmazzá teszi a C-ben programozást azok számára, akik nem értik / nem ismerik ezeket a szabályokat,
mindezt egy meglehetősen homályos teljesítménybeli előnyért.
(Tudjuk, mi szokott történni akkor, amikor a helyességet feláldozzuk a teljesítmény oltárán...)
- A hozzászóláshoz be kell jelentkezni
buff[j++] = p[j];
Az eddig rögzített kommentek legjobb esetben is csak részben válaszolják meg azt a kérdést, hogy a fenti kifejezés miért Undefined Behavior (mert hát az). Az eddigi kommentekre csak felületesen pillantottam rá, viszont rákerestem, és senki sem idézte eddig a lényegi részt a C szabványból. A C99 6.5 Expressions p2-ben ugyanis ez szerepel:
Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.
A C17-ben (és feltehetően már a C11-ben is) egy jóval komplikáltabb megfogalmazás szerepel (6.5 Expressions p1, p2):
[...] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.
Először arról írnék, hogy a C17-ben (ill. C11-ben) található szöveg miért sokkal bonyolultabb, mint ami a C99-ben van. Azért, mert C11 óta van a C-ben többszálúság, atomi hozzáférés, és memóriamodell; ezek pedig a szekventálást elbonyolítják. A C17-ben található leírás valójában a C99-ben található leírás általánosítása. C99-ben szekvenciapontokról és adatfüggőségről beszélünk (ld. lent), C11-től viszont olyan fogalmak vannak explicite definiálva, mint sequenced before, sequenced after, unsequenced, indeterminately sequenced stb.
A C17-ből idézett első paragrafus végén található mondat az adatfüggőségről kijelenti, hogy az szekventál (The value computations of the operands of an operator are sequenced before the value computation of the result of the operator), a második paragrafus pedig megköveteli, hogy skalár objektumon bármely mellékhatás szekventált legyen bármely (ugyanarra a skalár objektumra vonatkozó) másik mellékhatáshoz képest, illeve bármely (ugyanarra a skalár objektumra vonatkozó) értékszámításhoz képest. Ez a megfogalmazás a C99-es szöveg általánosítása.
A fenti kifejezésről mindkét szabványverzió szerint beláthatjuk, hogy Undefined Behavior.
C99 szerint: az idézett kifejezésen belül nincs szekvenciapont, csak a végén van egy (van egy expression-statement-ünk, ami egy full expression; ez utóbbi végén van egy szekvenciapont). A "j" objektumot egyszer módosítjuk (ami kielégíti az "at most once" követelményt). Viszont a "j"-t kétszer olvassuk ki, és ebből nem az összes olvasás olyan, hogy szükség van rá a "j" új értékének meghatározásához. A p[j] kifejezésben a "j"-t úgy olvassuk ki, hogy a "j"-ben eltárolandó új érték nem függ ettől az olvasástól. Emiatt ez a teljes kifejezés Undefined Behavior. A lényeg az, hogy a fordító tetszőleges relatív sorrendben kiértékelhet bármely két alkifejezést, amennyiben azok között nincs sem szekvenciapont, sem adatfüggőség.
C17 szerint: a szabvány szövege bonyolultabb, viszont az alkalmazása egyszerűbb. Az adott kifejezésben nincs olyan ⓧ operátor, amelynek a "j" (akár közvetve) az operandusa lenne, és amely ⓧ operátor eredményétől a "j++" függene (és viszont). Tehát a p1-ben található "sequenced before" kitétel az adott kifejezésre alkalmazhatatlan. Ennélfogva a "j" és a "j++" kifejezések szekventálatlanok (másként: topologikusan rendezetlenek), ami a p2 szerint Undefined Behavior.
A topiknyitóban megoldásként megjelölt komment nem válasz a kérdésre. Egyrészt nem a szabványból idéz. Másrészt az ott (helyesen) hivatkozott Unspecified Behavior az adott kifejezésnél lényegtelen; nekünk itt Undefined Behavior-ünk van, nem Unspecified Behavior-ünk. Itt egy összefoglaló, a C99-ből (3. Terms, definitions, and symbols):
undefined behavior -- behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
unspecified behavior -- behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance
implementation-defined behavior -- unspecified behavior where each implementation documents how the choice is made
Magyarra ezt így lehet fordítani:
- Undefined behavior: a szabvány nem mondja meg, hogy mi történik, és az adott konstrukció hibás. A konkrét nyelvi implementációra nem vonatkozik semmilyen megkötés.
- Unspecified behavior: a szabvány nem mondja meg, hogy pontosan mi történik (több lehetőség közül), de az adott konstukció érvényes. A konkrét nyelvi implementációnak a megengedett alternatívák valamelyikét kell biztosítania (esetenként külön-külön); dokumentálnia nem kell a választást.
- Implementation-defined behavior: a szabvány nem mondja meg, hogy pontosan mi történik (több lehetőség közül), de az adott konstukció érvényes. A konkrét nyelvi implementációnak dokumentálnia kell, hogy mi történik.
Az itt szereplő kifejezés Undefined Behavior, nem Unspecified Behavior. Ugyanis:
- az idézett C99 paragrafus a "shall" szót használja, méghozzá Constraint-en kívül -- így a követelmény megszegése Undefined Behavior (C99 4. Conformance, p2);
- az idézett C17-es rész pedig explicite Undefined Behavior-t ír.
A hivatkozott komment alapját képező Unspecified Behavior létezik, de ennél a kérdésnél irreleváns. Viszont ha már beszélünk róla: így van leírva a C99-ben (6.5 Expressions, p3):
The grouping of operators and operands is indicated by the syntax. [...] Except as specified later (for the function-call (), &&, ||, ?:, and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.
illetve 6.5.2.2 Function calls, p10:
The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.
Erre egy példa:
#include <stdio.h>
static int
f(int i)
{
printf("%d\n", i);
return i;
}
int
main(void)
{
printf("%d %d %d\n", f(1), f(2), f(3));
return 0;
}
A program négy sort fog kiírni (IO hibáktól most eltekintve); az utolsó sorban "1 2 3" fog szerepelni. Az első három sorban viszont az (1, 2, 3)-nak tetszőleges permutációja megjelenhet, és ezek közül mind helyes, és a nyelvi implementációnak nem kell dokumentálnia / megígérnie a sorrendet (még azt sem kell megígérnie, hogy mindig ugyanabban a sorrendben történik az f() három meghívása -- lásd fentebb az in any instance kitételt). Ezt jelenti az Unspecified Behavior.
Az más kérdés, hogy a szabványban ez is szerepel (C99 4. Conformance, p5):
A strictly conforming program shall use only those features of the language and library specified in this International Standard. [...] It shall not produce output dependent on any unspecified, undefined, or implementation-defined behavior, and shall not exceed any minimum implementation limit.
Tehát az helyes törekvés, hogy mindhárom jellegű viselkedést elkerüljük, de összemosni azokat nem jó.
- A hozzászóláshoz be kell jelentkezni
Egyébként abból, hogy a fordító figyelmeztetése úgy van megszövegezve, hogy Unsequenced modification and access to 'j', látszik, hogy minimum a C11-re fel van készítve a fordító, ugyanis ez a nyelvezet a C11/C17-ből származik, nem a C99-ből. (C99 idejében még nem volt definiálva az "unsequenced".)
- A hozzászóláshoz be kell jelentkezni