Pre- és post increment, decrement kifejezésekben

Fórumok

Nézzük az alábbi C kifejezést:

unsigned int *ptr;

if (ptr && *ptr && --*ptr == 0) timeout();

Egyfelől logikus, amit csinál. Megnézi, a pointer nem NULL-e, utána megvizsgálja, a ptr által mutatott unsigned int változó nagyobb-e nullánál, ha ez is igaz, csökkenti azt, majd megnézi, nullává vált-e. Ha igen, hívja a timeout() függvényt. Ha a ptr == NULL, vagy érvényes, de az általa mutatott változó nulla, akkor nem nyúl semmihez. Ha a ptr által mutatott érték nem nulla, csökkenti azt, de csak akkor hívja timeout() függvényt, ha elérte a nullát.

Ilyesmit szoktam írni, de egy pillanatra megrémültem. A -- precedenciája nagyobb, mint az && operátoré, így akár az is lehetne, hogy előbb minden vizsgálat nélkül csökkent, s csak utána értékeli ki az &&-ek által határolt tagokat. Bár, ha így lenne, szinte semmire sem lenne jó az, hogy amint a logikai kifejezésről tudható annak értéke, nem értékeli ki azt tovább, hanem kilép.

Gyakorlatiasan, az elvárt módon csinálja, de ha szigorúan nézem a precedenciát, akkor szerintem nem működne ez jól.

Hozzászólások

Akkor ez már a második tagra is igaz: a * operátornak is nagyobb a precedenciája, mint a &&...

(ergo ha ptr==NULL, akkor exception)

Ez így van, eszembe is jutott, de akkor is furcsa. Tegyük hozzá, a nyelv így használható, nem pedig úgy, ha a precedencia azt az értelmezést kapná, amit említettem.

Másik érdekesség, hogy char s[2][256]; left to right asszociatív, amit úgy lenne logikus értelmezni, hogy s[2]-ből van 256 darab, holott valójában s[256]-ból van 2 db. Ez szintén logikus furcsaság, hiszen s[i] így tud hivatkozni az első vagy második egybefüggő 256 byte-os területre.

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

A && kiertekelesi pont, a precedencianak semmi koze hozza, jo a kodod.

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

Egyfelől értem, de az && ugyanúgy egy operátor, mint a többi, csak elég alacsony precedenciával. Így szoktam írni, csak belémhasított egy rossz érzés, nem kell-e esetleg szétszedni több if-re az ilyen szerkezeteket. Mindamellett megnéztem a Compiler Explorer oldalon, mivé fordítja a gcc, s azt láttam, amire számítottam.

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

Cukorka hogy elhagyhass pár zárójelet.

A "modern", fiataloknak szóló nyelvekhez illik lint-et használni. Ez pedig alap configban beszól, hogy ha egy && és || van egy kifejezésben, de nem zárójelezed a &&-t. Nem hiba, csak stílusbeli kötelezőség, hogy az új generáció nehogy ne értse meg. Hol van a 3.60-as kenyér az hogy az ASM compiler-t is érteni kellett, nem volt egy alap nodejs tudás.

Nem azért nyitottam a topikot, hogy cinikus megjegyzéseket helyezz el benne. Az világos, hogy hogyan csinálja, mert amikor az && előtt már false lesz, kilép, semmi szükség a további kiértékelésre. Ugyanakkor számomra az is logikus lenne, hogy az

a && --a == 0

kifelyezésben megnézi, minek van a legnagyobb prioritása. A --a lesz az, nosza, csökkentsük az a-t, s mivel ez preincrementes, még minden más előtt tehetné. Ha az a korábban 0 volt, most már nem az, így az && előtti rész igaz lesz.

Örülök, hogy nem így csinálja, csak felvetődött bennem, hogy akár ez is történhetne.

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

Nem azért nyitottam a topikot, hogy cinikus megjegyzéseket helyezz el benne.

Szerintem semmi cinizmus nincs benne: ha egy sima timeout változó dekrementálásán gondolkodni kell, akkor az nem jó kód, főleg nem, ha nem csak magadnak írod, hanem csapatban dolgozol, ahol más is hozzányúl a kódhoz.

C kódban mindig igyekeztem tömörnek lenni. Nem cél, hogy más is első olvasásra megértse a kód működését. A C előnye éppen a rövidség, tömörség; ereje az összetett kifejezésekben rejlik.
Alapszabály volt, hogy más függvényét sosem tákoljuk. Ha hozzá kell nyúlni, újraimplementáljuk az egész függvényt.
Az is alapszabály volt, hogy ne nagyon legyenek egy képernyőnél hosszabb függvények.

Sokszor még a magam kódjához se nyúltam hozzá. Inkább újraírom a kérdéses függvényt, és lefuttatom rajta az egységteszteket. A tákolásból, javítgatásból mindig csak a gond van.
Ez alól kivétel, ha nagyon jól beazonosítható hiba van, kb. typo szintű. Azt természetesen ,,ér" javítani.

A topikindító kérdésre visszatérve: ilyen esetben mindig meg kell nézni, hogy az aktuálisan használt C szabvány (pl. C89) hogyan rendelkezik, definiált-e a kiértékelési sorrend, vagy hogy egyáltalán a kifejezés minden tagja kiértékelődik-e. Mert lehet, hogy valami működni fog az adott fordítóval az adott architektúrán, de ha ezt a szabvány nem garantálja, akkor tud ám meglepetést okozni egy másik fordítóval, esetleg más arch-ra való fordítás.

Nem cél, hogy más is első olvasásra megértse a kód működését.

vs

A C előnye éppen a rövidség, tömörség; ereje az összetett kifejezésekben rejlik.

Mi előnye van a "rövidség, tömörség"-nek, ha nem cél, hogy "más is első olvasásra megértse a kód működését"?

Kevesebbet kell gépelni egyszer? ( Ami előnyt az újraírással el is veszted. )

Az is alapszabály volt, hogy ne nagyon legyenek egy képernyőnél hosszabb függvények.

Ezt sem értem, hogy miért kell, ha senki más nem kell, hogy megértse.

Mi előnye van a "rövidség, tömörség"-nek, ha nem cél, hogy "más is első olvasásra megértse a kód működését"?

A tömör C kód minden, csak nem könnyen olvasható. Az egyetemen gyakran volt olyan feladat, hogy rácsodálkozz: egy fél sornyi kód mennyi mindent tud csinálni, mennyi trükkös nyelvi elem lehet benne felhasználva. Minket ebben a szellemben tanítottak C-ben programozni.

> Az is alapszabály volt, hogy ne nagyon legyenek egy képernyőnél hosszabb függvények.

Ezt sem értem, hogy miért kell, ha senki más nem kell, hogy megértse.

Mert a hosszabb függvények működését két hét múlva már magad sem érted :-D
Azért szerettük nagyjából képernyőnyi függvényekre bontani a feladatot, mert (a mellett, hogy egy képernyőnyi kódot könnyen átlátsz,) így a függvények nem lesznek annyira speciálisak, és mindegyik jól körülhatárolható részfeladatot végez. Gyakran kiderül, hogy egy-egy ilyen függvény némi generalizálás után felhasználható más problémák megoldása során is.

Egyébként sem volt szokás egymás kódját tákolgatni. Mindenki a maga részének a gondozásáért volt felelős.

Ez az egy képernyős kvázi szabály elég nagy hülyeség. Van egy mérés vezérlés, benne switch () case szerkezet az egyes state-ekre, de azon is belül vannak alállapotok. Igen sok sor, 14 képernyőoldal - most számoltam meg. Lineáris vezérlés, ezt aligha lehet értelmesen szétszedni, sőt, attól válik érthetetlenné, ha nem lineárisan látod, mi következik egymás után.

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

Mi nem mikrokontrollerre fejlesztettünk. Más volt a use case, a komplexitás, mások voltak a problémák.
Az egy képernyős szabály egyébként is nagyságrendet jelöl ki: lehet jó függvény a három soros és a négy képernyős is. A lényeg, hogy monster függvények ne jöjjenek létre, hanem bontsuk tovább a feladatot.

switch() case is tud olvashatóbb lenni inline-okkal pl.
Kivétel pedig mindig minden alól van.

Szét lehet szedni, ha nem akarod látni, hogy a mérésvezérlés melyik állapota után melyik jön, s azt sem akarod látni, melyik állapotában mit csinál. Tehát, ha az a cél, hogy elveszítsd a fókuszt, mert ahelyett, hogy helyben átlátnád, hogyan működik az egész, vadul lapozgatsz más file-okban, de mire oda jutsz, ahova akartál, elfelejted, mi az ördögöt keresel, akkor valóban szét lehet szedni. Csak nem érdemes.

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

Az az igazság, hogy ilyen mindenféle megfelelőségek és szép patternek kedvéért borzasztóan el lehet bonyolítani egyszerű dolgokat is. Amikor már el van bonyolítva, akkor a 15 fős csapatból senki nem fogja érteni, hogy pontosan hogy működik az egész és így soha nem fog kiderülni, hogy egyetlen egy fájban sorban leírva az egészet összesen lenne mondjuk ezer sor, amit egy hét alatt azért be tud egy ember cache-elni, és el tud igazodni benne.

Ott van az óriási hatékonyság ugrás, amikor van egy program amit egy ember átlát, vagy van egy csapat, ami bele tud nyúlkálni, amint kimeetingelték, hogy ki az aki az adott részhez ért: ha egy ember átlátja, akkor egy változáskérést 15 perc alatt ki lehet szolgálni prototípus szinten, persze teszteléssel együtt lesz belőle 1-2 nap is mire rendesen elkészül. Ha nem látja át egy ember, akkor a csapat 2-3 hónap alatt talán megcsinálja ugyanazt.

Az az igazság, hogy ilyen mindenféle megfelelőségek és szép patternek kedvéért borzasztóan el lehet bonyolítani egyszerű dolgokat is.

A 14 oldalnyi kód az biztosan nem könnyen átlátható, egyszerű kód. Persze, egyszerű kódokat el lehet bonyolítani, bonyolultakat még bonyolultabbakká lehet tenni, de bonyolult kódokat is lehet egyszerűsíteni. A megfelelő absztrakciós szintek kialakítása a kulcs az egészhez. Pl. 14 rövid mondatban (egy pár utasítás mondatonként) leírni, hogy mit csinál a 14 képernyőnyi kód, az egy-egy mondat  részletei meg egy szinttel lejjebb vannak.

Ott van az óriási hatékonyság ugrás, amikor van egy program amit egy ember átlát,

14 mondatnyi szöveget könnyebb megérteni vagy 14 oldalnyi szöveget? 14 oldalnyi szöveget könnyebb átlátni vagy egy oldalnyit?

Persze lehetnek kivételek, de 14 oldal az nagyon soknak tűnik.

Akkor is ott van "mögötte" a 14 oldalnyi szöveg. 14 oldal nem annyira sok, és annak is van előnye, hogy minden egy helyen van. A szerkezetből adódóan meg vannak jelölve az állapotok (felteszem, hogy tabulátorokat még locsemege is használ :-), ugyanúgy látod, hogy hol vannak a logikai részek mintha külön metódusok lennének. Egy bizonyos méretig lehet ez a hatékonyabb megközelítés.

Pont ez a lényeg, hogy el kell dönteni, hogy az adott csatába melyik fegyvereket vigyük, nem mindig ugyanaz a legjobb választás.

A computed goto csak egyszerű demultiplexer, de locsemege ezt írta:

a mérésvezérlés melyik állapota után melyik jön, s azt sem akarod látni, melyik állapotában mit csinál

Az ilyen - egyszerűbb esetben - leírható switch használatával. Csak annyi kérdés marad, hogy elegendő-e a switch, vagy több switch és define: egyszerű. ;)

Létezik olyan technológia, ami gyors, jól áttekinthető és minden esetben stabil működést eredményez. Ez a végesállapotú gép (FSM), avagy automata. A feladattól függően lehet speciális esete is, pl. lézernyomtato - ahol a papír mindig végighalad a mechanikán, vagy adatgyűjtés - ahol mindig ugyanazt a feladatsort kell végrehajtani. De ebbe a kategóriába tartozhat a hálózati kapcsolat felépítése és a regexp feldolgozása is. Egy automatával bármit le lehet írni, és utána egészen kezdők is tudják az elemeit programozni, hiszen csak ki kell tölteni az akciókat. Általános szerkezete:

állapot
	esemény
		akció	-> új állapot
	...
...

Ha valakinek még nem áll kézre akkor a Libero lehet a kezdő lépés. Természetesen az állapotgépet meg lehet írni támogatás nélkül is.

Ha jól rémlik, a Libero alap sztorija szerint VMS alatt szerettek volna irdatlan mennyiségű terminált kezelni, amihez persze az időosztás kevésnek bizonyult. Ráadásul userspace programra volt szükség. Ekkor rakták le a Libero alapjait, majd később ezzel a tecnológiával készült a nagy teljesítményű Xitami webszerver is. Az vezérlő szerkezet az assemblertől a shellen keresztül az sql-ig bármilyen programnyelven működik.

Alkalmas talán még mérésvezérlésre is. ;)

Nem cél, hogy más is első olvasásra megértse a kód működését. [...] Sokszor még a magam kódjához se nyúltam hozzá. [...] A tákolásból, javítgatásból mindig csak a gond van.

Szerintem te úgy írsz C programot, hogy nem tudsz programozni.

Ítéljék ezt meg a kollégák és az ügyfelek. Mindenesetre ha valóban nem tudok programozni, akkor kurva nagy májer vagyok, mert ahhoz képest elég jól fizettek. Még szerencse, hogy a főnök nem jött rá :o
Sosem tartottam magam fősodorbeli szoftveresnek, kizárólag alacsonyszintű, hardverközeli dolgokkal foglalkoztam. Később nem-ip-alapú hálózatokkal is.

Kis cég volt, kevés emberrel, speciális (nem mainstream) feladatokkal.
Nálunk le volt osztva, ki melyik modulért felelős. Ennek is van előnye.
Távozott kollégák kódját kellett néha átvenni, meg amikor behúztunk külső forrást, annak is egy személy lett a felelőse.

Nálunk ezek voltak az elvek, hogy decomposition kb. képernyőnyi függvényekre, és függvényen max. pici fix lehet, ha elbukik teszten vagy gyanús, és ha tíz percen belül nem jövünk rá a bajára, akkor újraírjuk. Jól definiálható feladatot végző, kis komplexitású függvényeknél szerintem ez jó döntés volt.
A meglévő függvényeket igyekeztünk generalizálni és minél több feladat elemeként felhasználni.

A kódjaink sokáig futottak, jó esetben évekig nem volt megbootolva eszköz, vagy restart-olva service.
Mindezt bár x86 alapú, de beágyazott környezetben, nem szokványos hardveren.

Túlzás lenne azt mondani, hogy nálunk nem voltak lowlight-ok. Voltak.
Mégis merem azt állítani, hogy összességében működött.

12 éve kiszálltam és másba kezdtem. Most agile alapon, de nem szoftvert fejlesztek.

Nem cél, hogy más is első olvasásra megértse a kód működését. [...] Sokszor még a magam kódjához se nyúltam hozzá. [...] A tákolásból, javítgatásból mindig csak a gond van.

Én erre reagáltam. Ez egyszerűen nevetséges, sőt, bizonyos szempontból szánalmas. Komolyan. Mindegy, hogy mekkora cég és milyen területen dolgozik, ezek egyszerűen védhetetlen kijelentések.

Nem cél, hogy más is első olvasásra megértse a kód működését.

És code reviewen nem dobják vissza a kollégák? Van egyáltalán normális code review?

C előnye éppen a rövidség, tömörség;

Eddig úgy tudtam, hogy az, hogy kb. egy hordozható assembly.

Alapszabály volt, hogy más függvényét sosem tákoljuk.

Ezzel szemben a realitás az nálunk épp, hogy 2 másik csapat 5 másik kollégája küld nekem mostanság PR-eket ahhoz a servicehez, amihez az 5-ből 4-en sosem nyúltak.

De igen, abban valóban igazad van, nem tákolnak ők sem, fejlesztenek. :)

hogy az aktuálisan használt C szabvány

Szabvány egy dolog, de ahoz még tartozik egy halom best practicle is.

Nem volt code review.
Sőt, szerintem egységtesztelésre se fordítottunk elegendő erőforrást.
Ezért mindenki igyekezett a saját szakállára tesztrutinokat írni, hogy lehetőleg még nála derüljön ki, ha gebasz van, ne a rendes tesztelési szakaszban. Mert az sokkal kellemetlenebb volt, mások munkáját is akasztottad vele.
Számomra a nap azzal fejeződött be, hogy feltettem az aznapi munkámat egy tesztkörnyezetre és ráeresztettem a rutinjaimat éjszakára fingatni. Reggel meg nagylevegő a memory map megnyitása előtt.

Sokan azt gondolják, hogy csak az aktuális multinál ezerrel tolt ideológia létezik. Pedig van egy csomó kis (és nagy!) cég is, ahol konzervatívan vagy alternatívan gondolkodnak. Ez múlik a tulajdonosi körön, a cég evolúcióján, adott esetben egy-egy manager berögződésein. E mellett minden iparágnak megvannak a sajátosságai.
Itt van pl. lócsemege, aki nem gondolnám, hogy kezdő. Mégsem strukturál át egy 14 képernyős switch() case-t. Ettől még nem gondolom, hogy ,,nem tud programozni". Lehet egy csomó körülmény, amit nem ismerek, nem ismerünk.
Az emberek automatikusan a saját esetükre vetítenek mindent, a saját helyzetük szemüvegén keresztül ítélik meg a dolgokat, és ezt az (elő)ítéletet generalizálják. Aztán gyakran kiderül, hogy máshogy is van élet.

Ezzel egyetértek!

> Sokan azt gondolják, hogy csak az aktuális multinál ezerrel tolt ideológia létezik. ... ahol konzervatívan vagy alternatívan gondolkodnak.

Ugye a kurrent divatra szokás úgy tekinteni mint szent tehénre, amit még véletlenül sem szabad megkérdőjelezni. Pedig egyáltalán nem ártana, sőt szükséges volna mindin mindent megkapargatni, hogy vajon tényleg az-e a legjobb megoldás és kísérletezni is más irányok felé.

 

> Itt van pl. lócsemege, aki nem gondolnám, hogy kezdő. Mégsem strukturál át egy 14 képernyős switch() case-t.

Ugye ha átstrukturálod akkor mi lesz belőle? Lesz mondjuk kb 30 függvényed, amiben ugyanaz lesz leírva mint a switch-case-ben. Mitől lett jobb? Pláne ha valóban egymás utáni állapotokról szól a switch-case! Akkor a leges legjobban úgy lehet átlátni, ha valóban egymás alatt sorban jönnek a case-ek.

 

Hasonlóan, ha valaki egyedül dolgozik és tényleg biztosan tudja, hogy mire értékelődik ki, akkor írjon csak mellékhatásos for ciklust! Pláne ha mondjuk nagyjából determinisztikus a dolog, ha egyszer jól futott le akkor mindig jó lesz! És ha egyszer egy másik embernek meg kell értenie, akkor az majd veszi a fáradságot és megérti! Nem kell a dogmát bigottságig erőltetni.

Ugye ha átstrukturálod akkor mi lesz belőle? Lesz mondjuk kb 30 függvényed, amiben ugyanaz lesz leírva mint a switch-case-ben. Mitől lett jobb?

Erről van szó. Simán lehet olyan körülmény, amiről nem tudok vagy nem látom be, neki mégis valamiért így jobb.

Meg embere is válogatja. Én szeretek mindent agyonstrukturálni, hogy a végén legyenek ,,törzsfüggvényeim" és átlássam csak a lényeget. Ilyenkor nem érdekel, mi történik lentebb. Az egy másik nap problémája, hogy ott is minden fasza legyen, és fentről nézve már ne kelljen ezen gondolkodnom. Fejben könnyen ugrálok, egyben viszont kevés dolgot látok át. Más meg pont fordítva van vele.

Legtöbbször tényleg olyan a feladat, hogy jó a strukturáltság. Meghívod megfelelő paraméterezéssel, s tudod, hogy jól meg fogja csinálni, már rég nem törődsz vele, hogy hogyan. Amikor megírtad, még tudtad. De egy lineáris szekvencia, egy állapotgép, vezérlési struktúra nem ilyen. Eleve a call és ret felesleges stack, felesleges futásidő. Jó, akkor legyen static inline. De rosszabb, mert oda kell lapozni, ahelyett, hogy egyből látnám, mi történik. Tehát ilyenkor nem segít.

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

De rosszabb, mert oda kell lapozni, ahelyett, hogy egyből látnám, mi történik.

Ha jól van elnevezve, akkor tudod, hogy mit csinál, csak a részleteket nem tudod, hogy pontosan hogyan csinálja. Ha arra is kíváncsi vagy akkor egy gombnyomással oda tudsz ugrani, egy gombnyomással vissza, nem hiszem, hogy ez olyan nagyon problémás.

Azért nem másolom be, mert munka. Képzelj el egy hatalmas switch () case szerkezetet. Az állapotok egy enum konstansai, az állapotváltozó globális, mert a külvilág felé is hirdetni kell az állapotot, a külvilág mérés alatt nem nagyon ficereghet.

Minden case-en belül van egy újabb állapotgép, egy újabb switch () case szerkezet. Ennek már static uint8_t az állapotváltozója. A külvilágnak semmi köze hozzá. Azért van szétszedve több állapotra, mert természetesen egy multitask vezérlés egyetlen nem blokkolós task-járól beszélünk, így ha várni kell valamire - pl. ürüljön ki az USB buffer -, akkor visszatérünk, s nem lépünk a következő állapotra, ennélfogva legközelebb újra vizsgáljuk, hogy várnunk kell-e még. Persze van timeout, hibakezelés, logolás, toronyóra lánccal. Egy dolog nincs mögötte: operációs rendszer. Tehát van a csupasz fenekünk, a mikrokontroller RAM-ja, FLASH memóriája, a hardware, egy gcc és valamiféle glibc-szerűség. Ha valami nincs implementálva, akkor megírom én. Ilyen volt pl. az stpcpy().

#ifdef MY_STPCPY
char *stpcpy(char *dst, const char *src) {
    if (src && dst) {
        while (*src) *dst++ = *src++;
        *dst = '\0';
    }
    return dst;
}
#endif

Viiszatérve a 14 oldalas függvényhez, az már magas szintű függvényeket hívogat, illetve faék egyszerűségű részeket tartalmaz, de így átlátható. Lényegében a külső switch () case enum konstans nevei azok, amelyek magyarázzák a funkciót.

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

Nem állt szándékomban cinikusnak lenni. Lehet nem fejeztem ki magam elég ügyesen. Ilyesmire gondoltam: Nem elég egyértelmű ahhoz, hogy mások is könnyen, egyértelműen megértsék később.

Sajnos, amire rájön idővel az ember, az az, hogy a jó kód UNALMAS. De nem csak a kifejezésében, hanem a bonyolultságában is. Tehát a bonyolult dolgokból egyszerű dolgokat állítasz elő, és ez az ÉRTÉK.

Abból érdemes kiindulni, hogy egy átlagos kódot kb 10x annyiszor olvasnak mint írnak, vagy valami ilyesmi az arány. Bár, nem tudom, hogy mérték ezt le.

Ez már-már igaz, de ha ezt túltoljuk, akkor egész egyszerűen a túl sok sor, a túl sok „zaj” felfogása, értelmezése teszi nehezen érthetővé az egészet. Ha például a *p++ = value; kifejezés helyett azt írod, *p = value; p++;, az egész egyszerűen értelemzavaró, nehezebben emészthető, pedig szét lett szedve.

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

Nem érdemes egyszerűen egy kis zárójelezéssel egyértelműbbé tenni?

(*p)++

Ami egyébként a legjobban zavar engem a * operátorban, az az, hogy nagyon más a jelentése, mint a két változós * operátornak, és akkor néha elkövet az ember olyan kódrészleteket (bocs, C++, C-t nem használtam már vagy 15 éve):

auto [minPosn, maxPosn] = minmax_element(v.begin(), v.end());
var x = (*minPosn) * (*maxPosn);

Ez [fixme] a második sorban lévő zárójelek nélkül ugyanazt csinálná, de sokkal zavaróbb lenne.

Nem, mert a példában tényleg a pointert post incrementálom. :)

Ja, ilyen poénokat lehet csinálni:

x = a ** b;

Ami ugye valójában x = a * *b;

Valódi kódban a legelkanászodottabb, amit írtam, talán ez volt:

valtozo.tag = *--*p;

Meg van az, amikor a függvény a pointer címét kapja meg azért, hogy a pointert is tudja mozgatni:

x = *(*p)++;

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

'Rövidzár kiértékelés' a dolog neve: ha az && baloldala nulla, akkor a jobboldalt nem is számoljuk ki, hasonlóan a || esetén: ha a baloldal nem nulla, akkor a jobboldalt nem számoljuk ki.

Én például azon a néven ismerem, hogy "lusta kiértékelés" (lazy evaluation). És ha precízek akarunk lenni, akkor _logikai_ kifejezés esetén szépen balról jobbra haladva annyit (és csak annyit) számol ki, amenyi feltétlenül szükséges a kifejezés igaz vagy hamis értékének eldöntésére. Azaz && esetén ha baloldalt HAMIS, akkor nem vacakol a jobboldallal. || esetén pedig ha a baloldal IGAZ, akkor hagyja abba.

Ez "shortcut evaluation" or "short-circuit evaluation" angolul. A "lazy evaluation" teljesen mas. Az jellemzoen funkcionalis nyelvek sajatja, es arrol szol, hogy pl ha egy valtozot egy kifejezessel inicializalsz, az nem ott rogton ertekelodik ki, hanem majd ha arra a valtozora szukseg lesz. (Ha egyaltalan.)

Más dolgot is nevezhetnek "lazy evaluation"-nak, de ezt is. Google első:

The Free Pascal compiler compiler directive {$boolEval} determines, whether short-circuit evaluation (also known as “lazy evaluation”) of Boolean expressions is performed.

https://wiki.freepascal.org/$boolEval

Nekem nincs Pascal a talalatok kozott. Az elso ez:

 - https://en.wikipedia.org/wiki/Lazy_evaluation

A rovidzar kiertekeles oldal pedig ez:

 - https://en.wikipedia.org/wiki/Short-circuit_evaluation

Ahogy lathato, az utobbi szol pontosan a logikai kifejezesek kiertekeleserol.

Az elobbin egy helyen szerepel a "short-circuit" kiefejezes, ott is azt mutatja be, hogy azokban a nyelvekben, ahol van lusta kiertekeles, egyszeruen definialhatok a (rovidzar) logikai operatorok es az if utasitas, a szokasos szemantikaval.

Szoval a short-circuit-et lusta kiertekelesnek forditani hibas.

Igen, ez megvan, tudom, használom is. A gondom az volt, hogy nincs-e, nem lehet-e olyan szabály, hogy a műveleti sorrendben előrébb lévő dolgok kiszámításával kezdjük, mert az megváltoztathatja az operandust, s akkor a műveleti sorrendben hátrább lévőhöz jutva már megváltoztak a feltételek. De szerencsére nem így van.

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

Van még egy bűvös szó, a szekvenciapont: az && és a || is ilyen; ez köznapi szóval azt jelenti, hogy a baloldal kiértékelését (mellékhatással mindennel együtt) be kell fejezni, mielőtt a jobboldal kiértékelését elkezdenénk (lásd még az előző pontot: lehet, hogy a jobboldal kiértékelése teljes egészében kimarad). Ilyen például a vesszőoperátor is [de a függvényparaméterek elválasztó vesszőre ez nem vonatkozik], meg a függvényhívás, pl.:

y= f(x++), g(x++);

Itt a fordítóprogram be kell tartsa az alábbi sorrendet:


$xtmp=x
x++
f($xtmp)
$xtmp=x
x++
y=g($xtmp)

Köszönöm az infót. Sohasem tanultam a C-t, csak amikor kellett, elkezdtem C-ben programozni, így ezek az egyébként fontos apró részletek teljesen kimaradtak. Azt sem tudtam például, hogy signed változó jobbra shiftelése arithmetikai shifteléssé fajul, s unsigned-dé kell cast-olnom, ha logikait szeretnék. Megszívtam egyszer, kidebugoltam, megtanultam. Azóta tudom.

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

Half-topic

Valamikor az őskorban, a freepascal, turbopascal 6.0 érában volt egy kapcsoló, aminek full-evaluation volt a neve. Pont arra szolgált, hogy az OR kifejezésekben mindkét oldalt kiértékelte még akkor is ha az első már false volt. Mert hátha az OR utáni rész egy olyan fgv, amelyet mindenképp meg kell hívni. Ez a 90-es évek vége volt, más gondolkodás. C és Cpp volt, ha találtunk 256byte ramot akkor örültünk.

Szerkesztve: 2022. 02. 12., szo – 09:51

Bezzeg a mostani trend szerint már kerülik a Kernighan&Richie-féle fifikás, viszont rutin hiányában félreolvasható kifejezéseket.
Rust-ban például a pre-dekrementáló operátor hiányzik, ott csak így körülírva tudod megoldani: https://rust.godbolt.org/z/hT5xKaGYf
Bár a tömör íráshoz egy dec() függvénnyel ez is orvosolható: https://rust.godbolt.org/z/v7c3PdY3v

Viszont a legyártott assembly kód ettől nem lesz lassabb futású, ellenben a forráskód nem lesz a rutintalanoknak sem félreérthető.

Szerkesztve: 2022. 02. 12., szo – 11:15

Az operátor precedencia szabályok arra vonatkoznak, hogy amikor nem egyértelmű, akkor hogyan kell fává alakítani a sorban leírt kifejezéseket, vagy másképpen hogyan kell kitenni a kispórolt zárójeleket. Pl:

a+b*c = a+(b*c)

A te eseted leegyszerűsítve arra ami a kérdés:

a && --b

Ezt csak így lehet zárójelezni, mivel a -- be van ékelve az && és a b közé: a && (--b)

Ha ezt írnád, akkor lehetne gondolkodni rajta: a&&b--, ez már lehetne kétféle: (a&&b)-- vagy a&&(b--), mivel a -- precedenciája nagyobb, ezért az utóbbi lesz. De ez pont hogy lehetővé teszi azt, hogy a b-- ne legyen kiértékelve, ha az a hamis.

Én egyébként nem vagyok híve a mellékhatásos kifejezések feltételbe írásának, én ezt így valósítanám meg:

if(ptr!=NULL)
{
 if(*ptr>0)
 {
  (*ptr)--;
  if(*ptr==0)
  {
   timeout();
  }
 }
}

Nem kedvelem az implicit pointer->boolean vagy integer->boolean konverziót sem, a mellékhatásos feltételt sem, és pláne nem, ha a "rövidzár kiértékelés"-en belül van mellékhatásos kifejezés. Persze sokkal több sor, de egyrészt minden sorra gondolkodás nélkül meg tudjuk mondani, hogy mit csinál, másrészt ha esetleg debuggolásra kerül sor, akkor soronként látjuk a lépéseket. Release módban fordítva a kódot pedig pontosan ugyanaz lesz az eredménye, mint az összezanzásított kifejezésnek, futásidőben mindegy.

Szerk.: és még a (*ptr)--; sorba is tettem egy zárójelet, hogy egyértelmű legyen, hogy nem a pointert csökkentem, hanem azt amire a pointer mutat.

Ez a zárójel nem az egyértelműségmiatt kell, hanem azért, mert a *ptr--; valóban a pointert decrementálná, hiszen a post decrement magasabb precedenciájú.

Engem jobban zavar a részletes kiírás, áttekinthetetlenebb, gondolkodósabb. A tömör írásmód szerintem világosabb. Nem vagyunk egyformák. :)

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

Nem, nem vagyunk egyformák. Nem egyszer fordult elő velem, hogy valamit debuggoltam, és akkor döbbentem rá, hogy ezt a sort szét kellene szedni 3-ra.

Sokszor elsőre én is tömör sorokat írok, aztán később alakítom át többre, vagy ha nincs vele probléma, akkor úgy marad.

Például Java-ban ha Null Pointer Exception-t kapsz, akkor a logba bekerül, hogy melyik fájl melyik sorából jött az exception. Igen ám, de ha két referencia feloldás is van abban a sorban, akkor nem lehetsz biztos benne, hogy melyik volt a hibás! Ezért referencia feloldásból is csak egy lehet egy sorban. Persze általában én is csak akkor írom így át, amikor először gond van az adott sorral...

Engem

Ez egészen addig érv, ameddig a kódhoz más nem nyúl. Pl. egy junior.

A tömör írásmód szerintem világosabb

Akkor miért született meg egyáltalán a topic, ha egyértelmű és nem kell rajta gondolkodni?

áttekinthetetlenebb

Ld. előző kommentben a debugolás. Van pl. ez a kód:

var p = ...;
var foo = new Foo(new Bar(p), new Baz(p), p);

Szép tömör, Foo-nak átadod mindkét paramétert. Majd kapsz egy ArgumentNullException-t, amiből annyi derül ki, hogy a p nevű paraméter hiányzik (tfh. mindhárom osztálynál ugyanaz a neve). Mondd meg kérlek, hogy most a Foo, a Bar vagy a Baz osztály ctora dobta.

var p = ...;
var bar = new Bar(p);
var baz = new Baz(p);
var foo = new Foo(bar, baz, p);

Ez lehet, hogy kevésbé tömör, több változó lesz benne, de már egy logban lévő call stackból egyértelműen látni fogod, hogy melyik dobta. Mert közel sem biztos, hogy mindegyik elvárja, hogy a p ne legyen null. De az is lehet, hogy valamelyik ArgumentOutOfRangeExceptiont-t fog rá dobni. Stb. Ezen kívül debugolni is egyszerűbb, hisz mire itt a negyedik sorig eljutsz ott lesz neked a local variable-kben a p-re a bar-ra meg a baz-ra egy egyszerűen használható referencia, amit bármely modern IDE-ben egy hoverrel megnézel kb.

Azért, mert villamosmérnök vagyok, nem pedig programozó matematikus. Sohasem tanultam hivatalosan a C-t, valami rejtélyes oknál fogva Fortrant és Pascalt tanítottak, de ezeket sem alaposan. Majd nézegetve a kódomat, szöget ütött a fejembe, hogy a precedencia nem okozhat-e problémát. Tehát ez kötelezően viselkedik úgy, ahogy megszoktam, vagy csak a gcc kegyes hozzám, s egy másik fordítóval megbánhatnám-e ezt az írásmódot.

Például egy function(buff[idx++], buff[idx++]); már nyilván csinál valamit, csak ki tudja, mit. Simán lehet, hogy a második paraméter a buffer korábbi elemét kapja meg, meg az is lehet, hogy az első, szóval ilyet nem szabad csinálni. Ennek kapcsán méláztam ezen. De választ kaptam, rendben van a nyitóban írt forma.

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

Azért, mert villamosmérnök vagyok, nem pedig programozó matematikus.

Viszont szoftvert állítasz elő. Édesmindegy, hogy mikrokontroller vagy szuperszámítógép. És teljesen mindegy, hogy mit tanítanak meg "hivatalosan", egyetemen, OKJ-n rengeteg szoftverfejelsztéssel kapcsolatos dolgot nem tanítanak meg, amit kellene. (Amit tanítanak, annak a felét is helyből el kell felejteni, vagy legalább kontextusában helyére tenni.)

Majd nézegetve a kódomat, szöget ütött a fejembe

Igen, általában ez az amikor egy juniorból medior lesz. Senior szint meg az, amikor eljut az ember oda, hogy ha eleve nem írunk olyan kódot, amin gondolkodni kell, sideeffectes, shared adatok több szál között (tudom, mikrokontrollered van, blablabla) stb., akkor elő sem fordulhat egy csomó olyan probléma, ami ezzel igen. Egy sima inkrementálás/dekrementálás helyett lehetne ott egy komplexebb függvényhívás, amit lehet, hogy valaki egyszer át fog írni, stb.

Közben módosítottál. A C++-t hagyjuk, mikrokontrollerre épeszű ember nem ír objektumorientált nyelven szerintem. Jellemzően assembly vagy C jöhet szóba. PC és efféle egzotikumok nem érdekelnek, ezekre programozzon az, akinek software-es a szakmája. :)

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

Próbaképpen végigtoltam Rust-ban STM32-re egy céges projektet. Átadtam, működik. Nem reménytelen az irány, részemről belevágnék a következőbe is Rust-ban.
C viszont 8 biteseknél a legkisebb low endtől a 32 bites dual core mikrovezérlőig (például stm32h7) még mindig jó választás. Rust viszont a 8 bites low end mikrovezérlőkre nem igazán praktikus szerintem.

Viszont ne feledd, napjainkban gyakran mikrovezérlő helyett/mellett a beágyazott elektronikánál beáll a sorba a single board computer is. Itt általában Linux feletti programozásra lehet számítani. Egyúttal színesedik a programozási paletta. Több esetben találkoztam szkriptnyelvekben megírt főfeladattal is. SBC-k esetén mondjuk jobban szeretem a Rust nyelvet, C-hez hasonlóan a kevés erőforrásból sokat ki tudsz a lapkákból hozni.

A C++-t hagyjuk

Ez igazából Java/C# volt, De lehetne C++ is. Vagy akár C is, C-ben is lehet (és szoktak) objektum orientált kódot írni. De látom nem értetted meg a lényeget: kapsz egy hibaüzenetet, ami mellé kapsz egy call stacket. Hogy fogod megmondani abból egyszerűen, hogy melyik része halt meg a kódnak, ha 3-4-5 különféle dolog történik ugyanabban a sorban?

PC és efféle egzotikumok nem érdekelnek, ezekre programozzon az, akinek software-es a szakmája. :)

Értem én, csak erre most hadd idézzek egy volt kollégámtól, aki szintén dolgozott már mikrokontrollereket is felvonultató környezetben, estefele írta nekem:

Lolcsemege topikot olvasom

MISRA C:2004- et javaslom neki, hogy amit a nyelv megenged arra szabad a pálya

A fosa olyan helyen ahol minőséget követelnek egyszerűen tiltott, automatizált static check írja ki a nevét a szégyenfalra

Mármint a bevezetőben írt írásmódot tiltják egyes cégek minőségbiztosítási szabályai? Hála a Teremtőnek, még nem ment el mindenkinek teljesen az esze, ennélfogva a munkahelyemen

1) nincs dress code
2) nincs gendersemleges WC
3) nincs szabályba foglalva, hogyan szólíthatjuk meg a hópihéket, sárga pelyhes kiskacsákat, stb., illetve szerepelhet függvénynévben, commentben master, slave
4) nem mondják meg, mit írj a kódba, az sokal fontosabb, hogy működjön, az sem baj, ha Fortran emlékek miatt 'i'-vel kezded a ciklusszámlálók elnevezését
5) mégis van jó pár ISO szabványnak megfelelés, de ez a világ márcsak ilyen

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

Hála a Teremtőnek, még nem ment el mindenkinek teljesen az esze

Értem, szóval minőségbiztosítási szabályokat felállítani, veszélyes, könnyen hibát okozható kódfordulatokat kerülni = mindenkinek teljesen elment az esze. Nincs több kérdésem. Főleg, hogy ezt párhuzamba állítod a dress codeal meg a gendersemleges wc féle, szakmához nem igazán kapcsolódó ökörséggel. (Gondolom kifogytál az érvekből, aztán jött a szalmabáb).

Mindegy, van ahol nem elégséges szint az, hogy "csak működjön", az is kell, hogy holnap is karban lehessen tartani és a kód valóban azt csinálja, amit szeretnénk tőle, mást ne.

Viszont egyvalamit elfelejtesz: attól, hogy más nézi a kódom code review alkalmával az nem jelenti azt, hogy én ne nézném ugyanúgy más kódját és "ne szólnék bele", hogy hogy csinálja. Tudod, csapatmunka. Megértem, hogy van, aki erre alkalmatlan, de az inkább menjen a rétre kecskét legeltetni.

könnyen hibát okozható kódfordulatokat kerülni = mindenkinek teljesen elment az esze

Ezzel ugyanaz a baj, mint a liberalizmussal. Vannak valakik, akik megmondják, mi a jó,  s ha megfeszülsz, sem tudod elmondani nekik, hogy te másként gondolod, teszem azt, ahogy írtam is, számomra könnyebben olvasható az általam bevezetőben írt kód, mint a terjengős, sok soros trágya, amibe belefáradok, a fókuszt is elveszítem, mire elolvasom.

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

Eléggé logikai bukfenc liberalizmussal példálózni, miután te akarod itt individuálisan felülírni egyéni érdek mentén azt, amire a szakma már rájött, hogy antipattern. Mert NEKED jobban olvasható. Képzeld, van olyan, ami nekem is jobban olvasható, aztán mégsem úgy írom, mert felfogom, hogy mi vele a baj. (Egyébként szerintem te a liberál-fasizmusra gondoltál, de mindegy.)

De hadd kérdezzek valamit: ha vezetékeket, kábeleket kell kötögetni, figyelembe veszed a szakma színkódolásait vagy "nekem így tetszik" alapon tolod azt is? UTP kábeleket is saját, neked kényelmes színsorrendben krimpeled?

a szakma már rájött

Erről beszélek. A woke kultúra is rájött, hogy pusztítsunk el minden értéket, ami eddig volt.

Ha valamit szabvány ír elő, akkor igazodom, de ha én tervezem a gép elektronikáját - és ez a helyzet -, akkor élek a tervezői szabadságommal.

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

Épp ezt mondom, hogy vannak, akik azt mondják, ők a szakma, ők rájöttek, s amit ők mondanak, az úgy jó. Aki ettől eltér, az mind marginalizálandó hülye. Ugyanaz, mint amikor megmondják, mit kell gondolni a történelemről, mert van hivatalos álláspont, másként ne merészeld gondolni a megtörténteket, mert az tabu.

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

Megmondják ideológiai alapon vs érvek mentén eljutottak oda, hogy az egyiket szignifikánsan többször lehet elbaszni, mint a másikat.

Hülye szalmabábok helyett vissza tudunk térni oda, hogy szakmailag mi az indok a side effectes kód írására azon túl, hogy neked olvashatóbb? Elárulom, szerintem is van olyan kód, ami szerintem olvashatóbb máshogy, mégse úgy írom, mert tudom, hogy mi vele a baj.

Viszont ez szándékos. Éppen ezért érzem túlzásnak a mellékhatás kifejezést. A mellékhatás pejoratív, úgy hangzik, mint valami hiba, amelyre nem számítottunk. Abban a sorban az lenne a hiba, ha a feltételben ez az adatmódosítás nem következne be.

Egyébként a feltétel vizsgálata a kiértékelt kifejezésre vonatkozik, a kiértékelés pedig tartalmazhat adatmódosítást.

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

Már hogy a viharba ne lenne genderspecifikus!

i=0;
f(++i;++i;++i);

Itt az átadott értékek lehetnek (1,2,3), (3,3,3), (3,2,1)!!, sőt több...

Ennek az az oka, hogy a cpu lehet big/low/pdp endian, a stack frame állhat lefelé és felfelé, valamint a C fordító is viselkedhet akárhogy.

Azok, akik képesek pártot alapítani arra, hogy a tojást melyik végén törik fel és ezen elvek szerint képesek akár vérre menő küzdelmet folytatni, az már eléri a genderspecifikusság fogalmát. (Swift idején még nem volt PDP. :-D) Ugyanerre létezik Molnár Ferenc óta egy gyönyörű magyar kifejezés: gittegylet.

A gittegylet/endianess/gender tilos abban az esetben, ha C-ben egyértelmű kódot szeretnél írni. Ha valaki ezt nem érti, akkor csatlakozik valamelyik párthoz. Az ilyeneket pedig nem lehet saját hülyeségükről meggyőzni. :(

Mondom, hogy szándékos. Szoktál te assemblyben programozni? Ott igazán szép, fantáziadús megoldásokat tud csinálni az ember. C-ben legalább van type cast, assembly-ben viszont észrevétlenül kezdheted ugyanazt az adatot más értelemben, más típusként használni, vagy tényleg bármi.

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

A C-t régen struktúrált assemblernek hívták. Mindazt, amit magyarázni próbálsz, ugyanúgy megteheted C-ben is. Csak egy kicsit tanulni kell hozzá. ;)

Barátod először az union és az align packed. Utána jöhet a bit és bitfield definíció.Ezek után nem kell mindenféle zárójeles castolásokkal foglalkozni, viszont (részben) hordozható lesz a kód. Ha nincs eszközkészlet, akkor még mindig szúrhatsz be asm utasításokat - mint pl. a nagyobb processzoroknál az altivec és a cache direkt kezelése.

Sőt, a telefonszámokat unsigned int30+2 jogosultsági bit struktúrában tároltam, egyéb biteket meg 2 bit x soksok elemű tömbben. Ehhez assemblerben már programozni is kell. ;) De semeddig sem tart a 4 karakteres ASCIIZ stringben tárolt irányítószámokat int32-ként összehasonlítani.

A C és assembler között csak annyi a külöbség, hogy az előbbi esetben meg kell mondanod mit csinálsz. Ennek köszönhetően valamivel jobb minőségű ellenőrzést ad a fordító.

A side effect használatáról az én történetem:

  • Programoztam 19 évig POWER-en IBM C-ben. Ott a cpu big endian, a stack frame "egyenes". A deklarált side effect ellenére a fordító+cpu mindent pontosan olyan sorrendben hajtott végre, amilyen sorrendben leírtam. Mottó: Nem akarom én sehova hordozni a kódot! :-D
  • Majd 15 évvel ezelőtt az Intel/linux platformon ugyanezek a side effectek már rosszul fordultak, mert a cpu little endian és a stack frame "fejjel lefelé áll" és mas a fordító.
  • Manapság már harmadik féle módon fordul hibásan ugyanerre a platformra.

Eldöntheted, hogy ugyanezt az utat szeretnéd bejárni, vagy programozol normálisan.

De bármilyen fondorlatosan kérdezed, a side effectet tartalmazó kód nem egyértelmű, tehát kerülendő. Az ilyenek a jó dokumentációban egyértelműen le vannak írva. Ha ennek ellenére teszeled, jól működik, akkor használd, de ne várd el, hogy dícsérjek is. :-D

Szerintem tárgyilagos. A C-ben nincs szubrutin, csak függvény, a függvény pedig, ha precízen értelmezzük, veszi a változóit, abból kiszámol egy eredményt, és azt visszaadja. Ez a hatás, és ha valami más is történik (eltér a C függvény a matematikai értelemben vett függvénytől), az mellékhatás. Matematikailag az operátor is csak jelölés egy függvényre ( tehát x-- értéke x, és a mellékhatása az, hogy x-et csökkentjük). Ez nem jelenti azt, hogy az x---ban nekünk esetleg nem fontosabb és hasznosabb a mellékhatás, mint a függvényérték. Ugyanez van a printf()-el, általában senkit nem érdekel, hogy mit ad vissza, az a fontos, hogy kiírja a kiírnivalót.

Ez mind nem igaz az olyan nyelvekre, amiben van szubrutin és van függvény (pl. a Fortran). A szubrutinnak az a dolga, hogy veszi az argumentumait, csinál valamit azokkal, majd visszatér. C-ben ilyen nincs, van helyette a void függvény. És ez nyilván semmi más, mint terminológia.

Van olyan nyelv, amiben nincs szubrutin, és mellékhatás sem. Egy csomó hiba elkövetését megnehezíti, de az I/O implementálását is.

Globális változó hátrányai:
   - átláthatatlan az "életútja". Függvényben pattogtatva következőbb, hogy mikor ki és mit akar a változóval/struktúrával.
   - multithread, multicore ... jöhet a "mit ír ki" című klasszikus programozó vizsgafeladat

Globális változó két előnye:
   - legkisebb 8 bites architektúráknál, amikor azért a nyeszlett 1..2 órajelért küzdesz, fixen allokált memóriacímre direktben tudja az adatelérést fordítani a fordító, ezáltal kimarad egy indirekciós lépés.
   - valós rész és interrupt közötti adatátadás ... de óvatosan. Amikor valós részből piszkálod, tiltsd le az IRQ-t arra a néhány utasításnyi időre, ellenkező esetben képbe jön a side effect.

Rust esetén globális írható változó unsafe nyelvi kiterjesztésben használható, értelemszerűen a nyelv erre semmiféle biztonsági garanciát nem ad.
Viszont mikrovezérlőre (stm32) is használtam már Mutex-et a főprogram és IRQ közötti adatcserénél. Tapasztalat vele: korrekt, kétirányú szemafor implementáció. Viszont mivel 100 000 IRQ/másodperces tempóval dolgoztam, ott már kimutatható volt, hogy néhány órajellel lassabb egy sima IRQ tiltásos, egyirányú lock módszernél. Viszont vannak olyan esetek, ahova tuti a Mutex-et építeném be, mert kell a kétirányú lock. Mikrovezérlőre egy Rust Mutex példát itt láthatsz: https://docs.rust-embedded.org/book/concurrency/

Erről egy vicc jut eszembe.

Egy felhőkarcoló legfelső emeletén áll egy tábla: Emberek! Ne dohányozzatok! Ne felejtsétek az 1906-os nagy San Franciscó-i tűzvészt! - Valaki melléirta: Emberek! Ne köpködjetek! Ne feledjétek a Mississippi 1927-es nagy áradását!

Ennyi hasznos tanács láttán bennem is felmerül: Emberek! Ne programozzatok! Bízzátok inkább olyanra, aki átlátja a feladatot és az azt megvalósító rendszert!

Azért globális változó, hogy más is hozzáférjen. :) Ha azt akarom, hogy ne látszódjék kívülről, akkor lokális. Ha csinálok egy volatile változót, amelyet megszakítás rutinnak is el kell érnie, meg alapszintről is, akkor ez is szándékos, nem tréfából hoztam így létre. Hasonlóképpen nem optimalizálhatja ki a fordító, ha hardware regisztert írok, amelyet látszólag sehol sem használok később. Az a hardware-t vezérli.

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

Pont más a szerepe. Legalábbis az IBM C-ben letiltotta azt a fícsört, hogy "úgy is hülyén programozol, ezért minden lokális változót IS lementek, hátha elrontod". Tehát megadhatod, hogy a függvényed side-effect mentes.  Avagy nem is az, de direkt csinálod. :-D

Ezzel tudatosan lehet módosítani a lokális és globális változókat is, de overhead nélkül. Ha az egyszerűség miatt külön függvényben írtam le egy inline-olt tevékenységet, akkor potosan és csak az a kód fordult be, amit akartam.

Igaz, azt is írták, hogy a rengeteg regiszteres processzornál ne használd a register direktívát. ;) Mégis sikerült olyan helyen alkalmaznom, ahol lényegesen gyorsított.

Igaz, azt is írták, hogy a rengeteg regiszteres processzornál ne használd a register direktívát. ;) Mégis sikerült olyan helyen alkalmaznom, ahol lényegesen gyorsított.

Persze. Többnyire egy-egy feature nem véletlenül van egy nyelvben. Lehet is őket használni. Csak a probléma ott kezdődik amikor a "néha jó, de egyébként inkább ne csináld" dolog válik az általánossá.

Ide vonatkozó kedvencem a gets() kriminalizálása. :)

Hasonló a shellben a var=NULL => var=0, 50 éve használatos fícsör nagy büdös lila warningokkal ellátása. Az alkotó nyilvánvalóan az egyik kezét a seggébe dugta, miközben a másikkal kódolt és azon elmélkedett: Mekkora szabványos zseni vagyok én! :-D

char *gets(char *s);

Sok évtizedig frankó volt, mert faék egyszerű és működött, működik a gets().
Ellenben ma már magától értetődő az a kérdés, hogy mekkora változóméretet foglalj, hogy a bemenettel ne tudj a változón kívüli memóriára írni? Erre pedig gets() esetén nincs válasz, látszik hogy nem tehető biztonságossá.

Azért éled meg  így, mert a gyógyszerek esetén megszoktad, hogy a mellékhatás mindig rossz. Kivéve, amikor nem, pl. az eredetileg lázcsillapításra szánt aszpirin vérhígító hatása. Attól még, hogy a nyelv tervezői úgy döntöttek, hogy függvénynek nevezik ezeket az alprogramokat, és azoknak a fő hatása az eredmény visszaadása, te még használhatod a mellékhatásukért. Nincs ezzel semmi baj, csak tudd, hogy valami olyat csinálsz, amit a nyelv tervezői nem a "szabálynak" szántak, hanem a "kivételnek". A C-ben ezek használata nincs megtiltva, csak sokak tapasztalata szerint nem érdemes használni, hacsak nincs rá valami jó okod. (Ha úgy gondolták volna, hogy soha nem szabad használni, akkor kivették volna az újabb szabványból. Még akkor is maradhatnál a régi változatnál (-std=c89), vagy használhatnál olyan compilert, ami megengedi ennek ellenére (Fortran-ban van egy-két deleted feature, és egy csomó obsolescent, az előbbire hibát adhat, az utóbbira warningot kell adnia a compilernek, de attól még szinte minden compilernek van olyan opciója, hogy továbbra is fogadja el.))

Amúgy a protected mode is a szabadság beszűkülése, ill. a világ romlása? Vagy a szigetelt vezeték?

Azért éled meg  így, mert a gyógyszerek esetén megszoktad, hogy a mellékhatás mindig rossz.

Épp írod is, hogy nem mindig ;-) A mellékhatások is hatások, csak éppen olyanok, hogy nem mindenkinél, nem minden esetben jönnek elő. Épp ez a rossz bennük, mert nem célszerű a mellékhatások miatt használni, mert ki tudja, hogy jön-e vagy sem. Az aszpirin vérhígító hatása pont rossz példa, mert az rendes hatás, az mindenkinél jelentkezik, pl. aszpirin protect (kis dózisú aszpirin).

Épp ezért sokkal jobb olyan eszközöket használni, aminek nincs mellékhatása és csak kevés számú hatása van, lehetőleg csak 1 (pl. vitaminok, ásványi anyagok, immutable adatok, pure függvények, ...). ;-)

A mellékhatások is hatások, csak éppen olyanok, hogy nem mindenkinél, nem minden esetben jönnek elő.

A gyógyszereknél a hatás és a mellékhatás is sokszor valószínűségi, van, akinél a gyógyszer hat, van, akinél nem. Nyilván annál "jobb" egy gyógyszer, minél inkább biztos, hogy hat. A különbség szerintem a hatás és a mellékhatás között az, hogy az egyik az, amire eredetileg szánták, a mellékhatás pedig az, ami még mellesleg történik. Az is igaz, hogy ha valaminek csak hatása van és mellékhatása nincs, akkor könnyebb vele tervezni, célozni. De lehet olyan helyzet, amikor egyáltalán nem baj egy mellékhatás (ha tudod, hogy arra is szükség van, és arra használod, hogy két legyet üss egy csapásra, pl. ha egy tömb elemein kell végigmenni egy ciklussal, mindaddig, míg teljesül rájuk egy feltétel, akkor hasznos lehet egy for(i=0; i < arraysize, feltetel(array[++i])) { ... ).

A mondatot tényleg rosszul írtam, azt kellett volna írnom, hogy "mert a gyógyszerek esetén megszoktad, hogy a mellékhatások, amikről sokat beszélnek, mindig rosszak."

> Akkor miért született meg egyáltalán a topic, ha egyértelmű és nem kell rajta gondolkodni?

Mert kíváncsi volt, van-e garancia arra, hogy a logikai-és(&&) baloldala teljes egészében kiértékelődik, mielőtt a jobboldal kiértékelése elkezdődik. Szerencsére van.

Az igaz, hogy Nyomosek Bobó compiler-íróként sokszor úgy optimalizál, hogy abban nincs sok köszönet, például, ha Bobó meglát egy ilyen programrészt:

unsigned n= strnlen(NULL, 0);

akkor boldogan felkiált: NULL-pointer! UB-optimization! és szándékosan rossz kódot generál.

Vagy ha én használat után memset-tel felülírnám a privátkulcsomat a memóriában, ő kioptimalizálja a memset-et, hiszen már úgysem használom azt a memóriaterületet.

Más: a lokális változók ilyen nemhasználatát Java-kódolóknál szoktam néha látni, szerintem ez valami olyan babona, hogy ha sok a lokális változó, akkor lassabb lesz a GC gráfbejáró algoritmusa.

Jó kérdés, nem tudom. Mindenesetre lett egy explicit_memset. Mármint egyes platformokon.  Mondjuk ez annyiban jó lehet, hogy [remélhetőleg] tartalmaz memory-barrier-t, vagyis nem csak az aktuális thread aktuális CPU-jának gyorsítótárában lesz nullázva a terület, hanem a RAM-ban is.

1. Jelen. De tény, hogy megfigyelhető eltérés nyelvenként

2. Jobban vezeti a szemet, egyértelműbb, hogy melyik utasítás hol van. Láttunk már olyan bugot, hogy valaki leírt egy if (...) a(); b(); -t, és if (...) { a(); b(); }-t akart írni.

3. Amit írtál példakódot, az szerintem is korrekt. Bár én kiírnám, hogy if (NULL == ptr && 0 < *ptr), egyértelműsítve, hogy mi a cél. (Meg a timeoutot új sorba, zárójelezve).

Nézzük másik oldalról!

Pl. a function(x,--x) bizony nem a -- operátor precedenciájáról szól, hanem side effectnek hívják. Általában a C fordítók tiltják az ilyen megfogalmazást.

Szerintem ilyen agyalás helyett azt kéne írni a programba, amit szeretnél csinálni! Igaz, akkor nem lehetne beszélgetni róla. ;)

Gondolom, azért jutnak ilyenek az eszedbe, mert assemblerisztikusan gondolkozol. :-D

Ha az alábbi módon írod le, akkor sokkal olvashatóbb és nincsenek kérdések. És hidd el, egy mai C fordító ugyanazt a kódot fogja előállítan!

if ( ptr != NULL )
	if ( *ptr > 0 )
	{
		*ptr--;
		if ( *ptr == 0 )
			timeout();
	}

Tehát ez az a kétméteres bot, amelynek nem ismerjük a méretét, de bármekkora, akár több féle méretű is lehet. Az a lényeg, hogy valid.

Ez az állapot a Freud szerint a skizofrénia. :-D

Ha a "valid" azt jelenti, hogy a fordító nem ad hibaüzenetet, akkor igazad van.

Ha olvastál már C fordító manuált, ... na abban kiemelik, hogy nem szabad ilyet csinálni.

Bár a fordítók is változhattak, mióta utoljáta olvastam...

Van egy népmozgalom a fordítóprogram-készítők között 'UB optimization' néven, aminek a lényege az, hogy ha a compiler meglát egy ilyen 'undefined behaviour'-t, akkor jogosultnak érzi magát, hogy szándékosan hibás kódot generáljon. Például a memcmp(NULL, NULL, 0) az egyesek szerint UB, tehát például konstans 77-et lehet behelyettesíteni visszatérési értéknek.
Pl.: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475

Ebben mi a hiba? Nem definiált a viselkedés, akár generálhatna olyan kódot is, ami éppen kilépteti a programot. Ha UB-t használsz (ami nem azonos a platformfüggő dolgokkal, például az int méretével), akkor ne számíts semmilyen értelmes viselkedésre a programban utána. Ez pontosan így van jól, normális programban nincs UB kód. Amiben meg van, ott nem szabad meglepődni semmin.

memcmp(NULL, NULL, 0)

A nulla hosszt kellett volna először ellenőrizni, és konstans nullával ('egyenlőek') helyettesíteni az egészet.

A másik meg az, hogy a nulla, mint memóriacím, egyes platformokon (Aix, Bs2000), spec. esetekben lehet valós, használható cím. Tehát eleve nem "UB"-nek kellett volna besorolni, hanem "platformfüggő"-nek. (Természetesen ahol az van a leírásban [például egy láncolt listánál], hogy a NULL jelentése 'nincs' vagy 'nem használt' ott azt nem gondolom felkavarni, de olyan alacsonyszintű műveleteknél, mint a mem??? igen.)

A nulla hosszt kellett volna először ellenőrizni, és konstans nullával ('egyenlőek') helyettesíteni az egészet.

Nem. Egész egyszerűen azért nem, mert nem ezt írja elő a szabvány. Ha a memcpy első két argumentuma átlapolódó területre mutat, akkor az UB. Ilyenkor akár olyan kódot is fordíthat a fordító, amelyik leállítja a processzt. Megteheti, mert megengedted neki. Függetlenül attól, hogy mi a harmadik argumentum értéke, azt figyelembe se kell venni, hogy UB-t kapjunk.

Akár fordíthat végtelen ciklust is, meg van neki engedve.

 

> Egész egyszerűen azért nem, mert nem ezt írja elő a szabvány.

Persze, igen, ez a végső kérdés: hogy miért nem olyanok csinálják a szabványt, akik képesek és hajlandók rendesen átgondolni a dolgokat?

Off: érdekes lenne egy lista az ilyenek erről. Sőt, egyszer már el is kezdtem. (Kettő a durvábbak közül: miért nincs benne a timegm(3) a standardban? miért nincs visszatérési értéke a syslog(3) függvénynek? (Még be kellene vennem a PHP-ból a htmlspecialchars-t, amit valamikor a fejlesztés során kiegészítettek egy 'és utf8-validság ellenőrzés' funkcióval, de úgy, hogy hibaüzenet sincs, csak üres string a válasz.))

Kieg: egyszerű feladat azoknak, akik szeretnék érteni a felületes gondolkodás és az alapos gondolkodás közötti különbséget: a magyar nyelvben melyik évszak nevében van csupa azonos magánhangzó?

Kieg: egyszerű feladat azoknak, akik szeretnék érteni a felületes gondolkodás és az alapos gondolkodás közötti különbséget: a magyar nyelvben melyik évszak nevében van csupa azonos magánhangzó?

Kérdésed sajnos aluldefiniált. Először is, definiálni kéne, hogy melyek a magyar nyelvben az évszaknevek. Ugyanis a kontinentális éghajlat évszakait így hívják: "tavasz", "nyár", "ősz", "tél". A trópusi és szubtrópusi éghajlat évszakait így hívjuk magyarul: "esős", "száraz". Más éghajlaton "meleg", "nedves" és "hideg" évszakok vannak. Ha az ókori Egyiptom évszakairól beszélünk magyarul, akkor az "áradás", "vetés", "aratás" évszakneveket használjuk. Ha azt szeretnéd mondani, hogy a "tavasz", "nyár", "ősz", "tél" legyen évszaknév, akkor fogalmazz pontosabban.

Más kérdés a "csupa azonos" kifejezés jelentése, ezt a magyar nyelv nem definiálja pontosan. Érthetjük alatta azt, hogy "mind megegyeznek" (ebbe belefér, hogy valamiből egy vagy nulla darab van), és azt is, hogy "legalább 2 darab, amelyek mind megegyeznek". Embere  válogatja, hogy ki hogyan értelmezi, a magyar nyelv aluldefiniált ezen a téren. Emberi nyelven megfogalmazott kérdésre sok esetben nincs pontos válasz, ha azt géppel akarod elvégeztetni, akkor pontosan definiálni kell, hogy mit értesz alatta.

hogy miért nem olyanok csinálják a szabványt, akik képesek és hajlandók rendesen átgondolni a dolgokat?

Hidd el, ők alaposan átgondolják a dolgokat. A memcpy definíciója teljesen jó, és pont azért van a nyelvben sok UB, hogy rengeteg architektúrára hordozható legyen hatékonyan. A lényeg, hogy azokban az esetekben, amikor nem UB és nem platformfüggő a viselkedés, teljesen jól van definiálva, hogy hogyan működik, a kód hordozható. Aki pedig mást használ, az magára vessen, ha az általa elképzelt viselkedés, és a fordító/futtatókörnyezet által implementált viselkedés eltér egymástól.

A te esetedben például a memcpy(NULL, NULL, 0)-nak miért kéne 0-t visszaadnia? 0-t nem tudhat visszaadni, mert ha visszad valamit, az az első paramétere kell legyen, ami esetünkben NULL, ami nem egyezik meg a 0-val, hiszen a NULL-nak más a típusa, mint a 0-nak, nem lehet azonos a kettő. Az egy dolog, hogy a 0 void*-ra cast-olva ekvivalens kell legyen a NULL-lal, mindkettő az adott platform null pointer konstansa. De ettől még a NULL nem ugyanaz, mint a 0, mivel más típusúak.

Ebben az a gyönyörű, hogy ha az x esetleg egy pointer, akkor még a variable aliasing-et is meg lehet valósítani vele (ez gyakoribb probléma olyan nyelvekben, amik nem érték szerint adnak át, pl. Fortran-ban). Ha írsz egy szép nagy függvény,

function(whatever *x, whatever *y){
  ...
  ...(*x)
  ...
  *y = ....
}

Akkor lehet, hogy az optimalizáláskor a fordító a *x-et felhasználó dolog elé veszi a *y-nak értékadót. Nem tudom, hogy a C szabvány szerint sír-e amiatt, ha egy ilyen függvényt meghívsz function(&x, &x) módon, de Fortran-kódot láttam már, ami emiatt adott más eredményt különböző optimalizálási opciókkal fordítva. Egy, a hetvenes évek óta sokak által használt matematikai szubrutingyűjtemény tesztjében volt a bug. Vagy valami viszonylag új gcc volt az első compiler, ami ennyire átrendezte a kódot, vagy mindenki bízott a kódban és a compilerében 50 éve, és nem futtatta a tesztet.

Erre van egy eszköz, a restrict, amit a függvény pointer típusú paramétereinél adhatunk meg módosítóként, és olyasmit jelent, hogy a 'hívó felelőssége, hogy az így megjelelölt paraméterek mind különböző objektumokra mutassanak, a compilernek nem kell ezen aggódnia'. Például a 'memcpy' esetén esetleg mondhatjuk ezt, de a 'memmove' esetén nem, annak működnie kell átfedő tartományok esetén is.

És még egy hiba is van benne. :(

Talán a sok && helyett próbáld meg így olvasni:

if timer defined
	if timer active
		decrement timer
		if timer inactive
			do timeout

Függőlegesen olvasva egyértelmű, hogy mikor mi történik.

Ezt a folyamatábrát tetszőleges nyelvre át lehet ültetni, és ugyanarra fordul, mint amin okoskodni kell.

Látom, elcsípted a lényeget. ;)

Történetesen az utóbbi időben főként assemblerben programoztam. Ennek ellenére egyes algoritmusokat awk-ban vagy C-ben írtam és teszteletem. Utána csak be kell emelni az awk vagy C kódot a programba és azonnal át lehet emelni assembly/assemblerbe.


	if ( ptr )
		if ( *ptr ) {
			--*ptr
			if ( *ptr==0 )
				timeout(); }

(	FSR0==ptr)
	movf	FSR0, w		; if ( ptr )
	iorwf	FSSR0+1, w
	bz	NoTimer
	movf	INDF0, f	; if ( *ptr )
	bz	NoTimer
	decfsz	INDF0, f	; if ( --*ptr )
	bra	NoTimeout
	call	Timeout		; timeout()
NoTimer
NoTimeout

Kis processzorra nem engedem meg a ptr pazarlást, hanem inkább inline (macro) oldom meg.

	if ( timer )
		if ( --timer==0 )
			timeout();

(	movlb	timer)
	movf	timer, f	; if ( timer )
	bz	NoTimer
	decfsz	timer, f	; if ( --timer )
	bra	NoTimeout
	call	Timeout		; timeout()
NoTimer
NoTimeout

Ez utóbbinak további előnye, hogy nem csak az FSRx, hanem a W regiszter használatát is megspórolod. Meg gyorsabb is a kód. Több timer esetén macro, mert elfér és a futásidő is rövidebb.

Ha ez szerinted áttekinthetetlen, akkor nem tudod mire fordul a kód. ;)

Megjegyzem, amikor még fiatal voltam, az öregek kifejezetten utálták az ilyen flag/pointer változó megoldást. Mint a timer==0 ->inaktív, timer>0 ->számláló. A ptr==NULL helyett meg egy flag lenne a helyes, pedig én is pont így használom.

Viszont gondolkodás nélkül olvasható, oda lehet adni egy juniornak is a kódot, az is érteni fogja. És leginkább: nem kutyul össze műveletet feltétel vizsgálattal. Jobb helyeken ez már helyből red flag egy code reviewen, ha egy feltételvizsgálatban adatot módosító művelet vagy függvényhívás van.

Ezzel nagyon nem értek egyet. A nyelv megengedi. Amit a nyelv megenged, azt szabad. Gondolod, amikor assembly-ben vagy C-ben programozok mikrokontrollerre, ahol futásidő is, memória is kritikus, érdekelni fog egyesek kényeskedése, de legalább vacak lesz a kód? Ha fontos a futásidő, akkor 256 byte lesz a buffer, uint8_t az index, aztán a túlcsordulás megoldja, hogy a vége után az eleje jöjjön. Igen, tudom, hogy nem általános, de nincs mindig idő arra, hogy az index vizsgálata sizeof()-ra, meg efféle lassú megoldások.

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

Miből feltételezed, hogy hosszabb lesz a kód azáltal, hogy ugyanazt a kifejezést több sorban írod le? Konkrét példa itt van pár hozzászólással feljebb: https://hup.hu/comment/2743278#comment-2743278 (Mivel a két megoldás számomra műveletekben ekvivalens - bugoktól eltekintve - , számomra kétséges, hogy egyáltalán más kód fordulna csak azért, mert több sort írtál a C kódba.)

Másrészt a fordítókat is a tipikus kódfordulatokra készítik fel, azzal, hogy te valamit megpróbálsz nagyon trükkösen megfogalmazni, koránt sem biztos, hogy tömörebb vagy optimálisabb kódot csinálsz, mert lehet, hogy pont azzal teszel keresztbe a fordító optimalizálójának. Láttam már több helyen ilyen "agyonoptimalizált" kódot, ami miután meg lett írva "hagyományos" módon, egy junior is el tudta olvasni és még gyorsabb is lett vele, mert nem tolt ki a fordítóval.

Igen, tudom, hogy nem általános, de nincs mindig idő arra, hogy az index vizsgálata sizeof()-ra, meg efféle lassú megoldások.

De ki beszélt sizeofról? Arról van szó, hogy szedd külön sorba azt, ami módosít és azt, ami összehasonlít.

Nekem mást jelent az érthetőség, de ez azért van, mert sokkal többet kódoltam assembly-ben, mint bármilyen más nyelven. A C nagy könnyebbség az assembly-hez képest, de assembly szemlélettel nagyon könnyen megtanulható és érthető a nyelv. Vedd úgy, hogy C-ben is assembly-ben programozok, csak C szintaxissal.

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

Azt értem, hogy jó a fordító optimalizálása, de ha előttem több betű van, több sor, azt nekem nehezebb megértenem, mint a tömör írásmódot. A tömör írásmód kifejezi, mit szeretnék, a több soros meg habosítás, bonyolítás, amely mögött hátsó szándékot sejtek, de a katarzis elmarad. Ez ízlés kérdése. Írom kényelmesen, áttekinthetően, úgy, ahogyan szerintem elegáns, egyszerű. Attól nem lesz érthetőbb a kód, ha óvodás a stílusa. De tudom, minden szakmára jellemző ma már, hogy nézzünk másokat is ostobának, meg magunkról is feltételezzük.

Látod, én inkább megkérdeztem, mert meg akartam tanulni, hogy ez garantált működés, vagy csak szerencsém volt. Azért akartam megtanulni, hogy magabiztosan használhassam. Workaround lehetett volna, ha több sorba írom, s akkor kérdeznem sem kell, de ha így járok el, buta maradok. Ugyanúgy irritál a sok soros írásmód, mint a felesleges zárójelezés, mintha nem lenne a műveleteknek precedenciája. Jó, néha zárójelezzen az ember, de attól nem lesz olvashatóbb a kód, amikor a sor vége felé van nyolc záró zárójel.

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

Attól nem lesz érthetőbb a kód, ha óvodás a stílusa.

Már hogy a viharba nem? Pont attól lesz egyértelmű: nem keveredik a módosítás és az összehasonlítás. Nem fordulhat elő side-effect. Nem kell rajta gondolkodni.

de a katarzis elmarad

Wtf. katarzis? Terméket állítunk elő, nem pedig katartikus élményeket keresünk.

Azért akartam megtanulni, hogy magabiztosan használhassam.

Ezzel nincs gond. Csak hosszú távon a magad doglát nehezíted, ha side effecttes kódot írsz.

Jó, néha zárójelezzen az ember, de attól nem lesz olvashatóbb a kód, amikor a sor vége felé van nyolc záró zárójel.

Erre ismét az a megoldás, hogy szét kell szedni. Tudod, keep it simple, stupid.

De tudom, minden szakmára jellemző ma már, hogy nézzünk másokat is ostobának, meg magunkról is feltételezzük.

Mert bakker, emberek vagyunk, hibázunk. Van, hogy fáradtak vagyunk már nap végén. Vagy mert nem gondol mindenki mindenre. Velem is volt olyan, hogy ebéd után még eszembe volt, hogy valahova be kell tennem egy inicializálást, elkezdtem megírni a kódot, tesztet, közben válaszoltam két kolléga kérdésére, aztán a nap maradék nagy részében azzal szenvedtem, hogy miért nem megy. Másnap standupon mondom kollégáknak, mire egyik csapattag "nem felejtetted el az inicializálást?" Bakker, de. (Aztán a megoldás az lett, hogy fél óra alatt megoldottam, hogy keresse meg automatikusan, mit kell inicializálni.)

mert sokkal többet kódoltam assembly-ben, mint bármilyen más nyelven.

Remek, de ha C-ben vagy kódolj C-ben, ne ASM-ben.

Egyébként pont a topic léte mutatja azt, hogy miért nem jó az, hogy ha egy sorban írod le az egészet: még te is elgondolkodtál, hogy most előbb dekrementál, vagy előbb vizsgál. Ha nem írod egyben, hanem szétválasztod a logikailag nem összetartozó dolgokat rendesen (és ismétlem: mivel meg kell csinálni mindkét lépést, ASM kódot nem valószínű, hogy befolyásolja*), akkor fel sem merülhet olyan probléma, hogy most melyik fog előbb történni. Ezt jobb esetben még egy junior is megérti.

Azt érzem, hogy te azt hiszed, hogy azért, mert valami tömörebbnek tűnik optimálisabb is lesz. De ennél bonyolultabb a helyzet. Eleve mire optimalizálsz? Memóriára? Tárhelyre? Processzoridőre? Munkaidőre? Általában ezek egymásnak ellentmondó dolgok. Elkezdesz beépíteni egy cachet, több memóriát fogsz igényelni és a kódod is nagyobb lesz. Elkezdesz memóriára optimalizálni, lehet, hogy valamit többször kell kiszámolni, lassabb lesz a kód.

* Ha mégis, ráérsz vele foglalkozni akkor.

Aztán add oda egy fordítónak.

Egyébként így írnám. Azon se lepődnék meg, ha gyorsabb lenne, de most nem érek rá méricskélni. :P

printf( i > 0 ? "nem negatív\n" : "negatív\n");

Mondjuk ha nem csak logolás eredménye, még az is lehet, hogy kiszedném egy változóba, egyszerűbb debugolni.

A nyelv megengedi. Amit a nyelv megenged, azt szabad.

Ez csak akkor lenne igaz, ha a nyelv tökéletes lenne. Ráadásul most, a valóságos nyelvek pedig még akkor sem voltak tökéletesek, amikor kitalálták  vagy szabványosították őket, és még csak nem is minden ötlet öregedett szépen. Pl. a Fortran COMMON blockjai vagy EQUIVALENCE-e a maga idejében jó ötlet volt, csak eljárt felette az idő. Az aritmetikai GOTO valószínűleg már akkor sem. Mondjuk szerintem a () ? : vagy a ++ és a -- a C-ben jó ötlet, ma is az, de nem kell mindenütt, feltétlenül használni.

A trükkök, amiket mondasz, akkor jók, ha muszáj úgy megírni (mert különben nem bírná a hardware), de pl. ha az uint8_t-t azért használnám, hogy a túlcsordulással visszaérjen az elejére, akkor azért valahogy úgy írnám meg, hogy

typedef uint8_t SmallPtr;
#define SMALLPTRMAX ...
.
.
SmallPtr k;
.
.
... var[k % SMALLPTRMAX]...
.
.
.

aztán megnézném, hogy a fordító optimalizálta-e a cuccot. Így legalább érteni lehet, hogy visszamegyek az elejére anélkül, hogy nagyon gondolkoznék, évek múlva, álmosan olvasva is. És aztán ha a kódrészletet újra felhasználod más gépen, más hosszúságú számokkal, akkor is menni fog.

Ha valós időben kell mérned, feldolgoznod hárommillió mérési eredményt másodpercenként egy mikrokontrollerrel, és az eredményeket elküldeni, akkor szerintem nem írsz holmi % jelet, modulo-t. :) Persze, akár sqrt()-t is írhatnál, ha matematikailag indokolt lenne, csak minek. :) Működni garantáltan nem fog.

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

Eltakarítja azt a modulót, sőt ennél még okosabb. Te hogyan csinálnál 10-es osztást?

A div hardverben is nagy késleltetésű művelet, gondoltál erre? Na így kell 10-zel osztani ARM mikrovezérlőn:
    https://c.godbolt.org/z/cqTEaxcz3
Assemblyből látszik, hogy amire jobban érdemes figyelni, szükséges-e foglalkozni negatív számokkal, vagy csak pozitív számra lesz?

Rust esetén ugyanezt látod (csak x86 assembly fordító van a godbolt-on), de le van kezelve a nullával való osztási hiba, észrevétlenül nem maradhat a hibás eredmény.
    https://rust.godbolt.org/z/G83fcMdY9
Persze ha felhasználod és ott tutira nem nulla az osztó, akkor nincs mit hibakezelni, a fordító kihagyja a hibakezelést végző kódokat.

Ezzel nagyon nem értek egyet. A nyelv megengedi. Amit a nyelv megenged, azt szabad.

Hadd találjam ki: keveset vagy soha nem dolgoztál csapatban, ahol adott esetben óráid vagy szélsőséges esetben perceid vannak arra, hogy megértsd és átlásd más valaki kódját, mert áll minden és akár percenként több ezer dollár a veszteség.

Elegendő erőforrás általában van, legyen olyan a kód, aminek első ránézésre is teljesen egyértelmű a működése, akkor is, ha ez miatt több sorból áll leírni. Ráadásul a legtöbb fordító megoldja végül azokat az optimalizálásokat, amelyek olvashatatlanná teszik a forrást és ugyanaz lesz a végrehajtás erőforrásigénye.

Ha fontos a futásidő, akkor 256 byte lesz a buffer, uint8_t az index, aztán a túlcsordulás megoldja, hogy a vége után az eleje jöjjön.

Ha fontos a futásidő, akkor mindez legyen odaírva kommentként, bő magyarázattal.

Azért ezt árnyaljuk: a kódnak nem feladata, hogy kezdőket (pláne kívülállókat) programozni tanítson. (Vagy elhitesse velük, hogy tudnak programozni, ha nem tudnak.)

Például egy if (hajo && *hajo) printf ("Hajó neve='%s'\n", hajo); szerű sornak a jelentése első ránézésre evidens kell legyen egy C-programozó számára, különben nem C programozó.

Az autógyártó sem ragaszt 'ezt tekerni kell' feliratú címkét a kormányra, hanem elvárja, hogy aki nem ért hozzá, az ne akarja vezetni a járgányt.

Azért ezt árnyaljuk: a kódnak nem feladata, hogy kezdőket (pláne kívülállókat) programozni tanítson. (Vagy elhitesse velük, hogy tudnak programozni, ha nem tudnak.)

Bocsánat, de itt most egy hete megy a rugózás és a gittrágás azon, hogy minden C fordító ugyanazt fogja-e érteni-e az adott soron és elvilen senior C/C++ fejlesztők n+1 év tapasztalattal rugóznak ezen, hogy jó-e ez így és érthető-e ez így. Ha éles helyzetben van erre öt perced egy ismeretlen kódbázisban, akkor hogyan akarod lekezelni ezt a helyzetet?

Az autógyártó sem ragaszt 'ezt tekerni kell' feliratú címkét a kormányra, hanem elvárja, hogy aki nem ért hozzá, az ne akarja vezetni a járgányt.

Nyilván lehet eltartott kisujjal elmélkedni azon, hogy ki az igazi C programozó, de a helyzet az, hogy nincs elég bármilyen tudású programozó és úgy néz ki, hogy ez nem igazán fog változni a közeljövőben. Ha pedig nincs elég ember, akkor úgy érdemes dolgozni, hogy még egy balfasz is meg tudja érteni és hatékonyan tudjon dolgozni a kódbázison, még ha az a munka valami favágás is. Nem beszélve arról, hogy stresszes esetben is sokat segít, ha nem kell ilyen faszságokon agyalni, hogy vajon mit is csinál az adott sor. Szóval legalább legyen felette egy sor megjegyzés, hogy mi a lófaszra gondolta a költő és milyen side effect van itt kihasználva és ezzel megbaszva az olvashatóság a teljesítmény oltárán.

Feltéve, hogy csapatmunkáról van szó, mert úgy néz ki, hogy a 'jóvanazúgy' hozzáállásúak magányos farkasok, akiket nem lehet csapatba tenni, mert öt perc alatt öngyilkos lesz a csapat fele és megáll a munka, szóval hiába a szakmája csúcsa az illető, ha csapatban való munka esetén nullával való szorzás a teljesítménye.

Azért ezt árnyaljuk egy kicsit: amióta elhangzott a két varázsszó (hogy rövidzár kiértékelés és szekvenciapont), azóta nincs több kétség az ügyben, hogy egyértelmű-e a topinyitó kódja. Azóta már csak arról van szó, hogy kinek mi tetszik. Ami szintén fontos és kérdés, de azért valahogy súlyozni kellene azzal, hogy az illető mennyire van benne a C-programozásban.

Azért ezt árnyaljuk egy kicsit: amióta elhangzott a két varázsszó (hogy rövidzár kiértékelés és szekvenciapont), azóta nincs több kétség az ügyben, hogy egyértelmű-e a topinyitó kódja.

És ez bruttó-nettó mennyi idő után dőlt el? Öt perc? Fél nap? Két nap? Ki fizeti majd egy cégnél mindezt?

Ha ugyanezt három külön if-ben írja le, akkor első ránézésre látszik, hogy a mialófasz történik és a fordító igen nagy eséllyel amúgy ugyanazt fogja fordítani mind a két esetben.

Azóta már csak arról van szó, hogy kinek mi tetszik. Ami szintén fontos és kérdés, de azért valahogy súlyozni kellene azzal, hogy az illető mennyire van benne a C-programozásban.

Továbbra is kihangsúlyoznám: ha csapatban dolgozol, akkor sokkal-sokkal-sokkal fontosabb a kód bárki számára bármikor és bármilyen körülmények között való egyértelmű értelmezése, mint az, hogy kinek mekkora a C-fasza és azt mennyiszer mutogatja azt a kódbázisban.

Ha valaki ilyen kódot ír csapatban, akkor rövid idő alatt eljut oda, hogy tulajdonképpen negatív munkát tesz a projektbe, mert minden egyes kódmódosítás során emberórák mennek a szemétbe annak értelmezésével, hogy mit írt és az mit csinál.

De milyen mélységig akarsz lemenni azon a létrán, hogy bárki, bármikor, bármilyen körülmények között tudja olvasni? Mivel én K&R C-n nőttem fel, lehet, hogy nekem az ANSI C16 (hasból mondtam) minden sallangja nincs meg, de számomra pl. fenti && és || szépen olvasható (és olvashatóbb, mint a 3x egymásba ágyazott if). És láttam olyan C-programozót, aki meg a pointerekkel volt nagyon rossz viszonyban, és amikor ponterek tömbjére mutató pointerek jelentek meg, befelhősödött a tekintete.

Szerintem itt kezdődik a C

main(){printf(&unix["\021%six\012\0"], (unix)["have"]+"fun"-0x60);}

(A Korn-shell-féle David Korn IOCCC-beli egyik kis gyöngyszeme. Én nagyon szeretem.)

De milyen mélységig akarsz lemenni azon a létrán, hogy bárki, bármikor, bármilyen körülmények között tudja olvasni?

Amilyen átlagos emberanyag szembejön egy átlagos projekten. Szerintem te is évek óta egyedül dolgozol, ha ilyen kérdést felteszel...

Szerintem itt kezdődik a C ...

Na, ez a faszméregetős része, aminek egy élő, aktív és sok ember által fejlesztett kódban semmi-de-semmi keresnivalója. Ha az általam vezetett bármelyik projekten ilyen jellegű kódot adnál le, akkor simán repülnél a csapatból, mert hiába te vagy a legnagyobb faszú a környéken, ezek miatt a csapat összteljesítménye egyszerűen rosszabb, mint nélküled. És végeredmény az, hogy a csapat összességében olcsóbb és gyorsabb, ha nem dolgozol.

Ezen lehet évekig keseregni minden egyes korsó sör felett, hogy milyen világ van már, csak amíg K&R idejében a teljes UNIX forráskódja 20 ezer sor alatt volt és egy jó képességű ember egy év alatt meg tudta írni, addig jelenleg olyan komplex feladatok vannak, amire több száz emberév elmegy és itt kiemelkedő képességű senior emberekről van szó. És nincs elég ember.

De mint magad is utalsz rá, az emberanyag gyengül, azaz a ma még az átlagnak jó konstrukciók 5 év múlva simán emészthetetlennek fognak tűnni - holott reményeim szerint 5 év alatt nem fog teljesen eltűnni minden ma fejlesztett szoftver.

Pont ez a bajom ezzel: ha elkezdünk tetszóleges nyelvből tetszóleges konstrukciókat elhagyni, akkor nincs megállás.

(Ami pedig a fenti printf-et illeti: szerintem egy fejlesztőnek igenis látnia és megértenie is kellene - az általa preferált nyelven - hasonló kódokat. Ez a holtig tartó tanulás része kéne legyen. Ha már C-ről beszélünk itt, akkor olvasson régi IOCCC versenyfeladatokat. Az se baj, ha a magyarázatát is elolvassa / megérti. )

Ja, ez lemaradt:

azzal egyetértek, hogy egy sokfejlesztős kódba ilyesmi nem való. Viszont jó, ha egy sokfejlesztős gárdában van 30-50 %, aki képes megérteni.

De mint magad is utalsz rá, az emberanyag gyengül

Nem gyengül, ugyanannyi vagy még több zseni van a területen, mint volt 50 éve, csak kb. százezerszer több a feladat, amihez kb. százezerszer több zseni kellene, mint amennyi van. De mivel nincs százezerszer több zseni, ezért be kell érjük pár millió kevésbé zsenivel és pár tízmillió balfasszal. A többi következtetésed ezen a rossz alapfeltevésen vérzik el.

Felteszem, a preprocesszor definiálja 1-nek. De én is úgy látom, hogy ez az egofényezés kategória (szépen fogalmazva;)

Szerk: Ha valami haszna van, az az, hogy felhívja a figyelmet arra, hogy a preprocesszor kimenetét is érdemes néha a hibakereséskor megtekinteni.

cpp -dDI  unix_demo.c unix_demo.E

Ilyen egyszerű részekkel érdemes kezdeni a feldolgozását, hogy, mi a fenét jelent C-ül az, hogy  ize["ecet"], vagy épp mire lehet ezt átalakítani; vagy épp mi az eredménye annak, hogy "szöveg" + 3. (Anno volt olyan AIX-es gép, amin muszáj volt a cc-nek odaadni a -Dunix paramétert a sikeres fordításhoz. (Rossz nyelvek szerint ez is azt bizonyította, hogy a kék rózsa rendszere nem igazi Unix :-) )

A kód Unix-like rendszereken és ASCII kódolást (vagy annak valami kibővített verzióját) feltételezve működik. Működése a fent linkelt www.ioccc.org oldalon lépésről lépésre le van vezetve.

Nem az egymás utáni && -ekkel van a gond, hanem azzal, hogy értelmes ember nem tesz IF-be olyan kódot, ami módosít valami adaton. Ha feltételt vizsgálsz, vizsgálj feltételt, ha műveleteket hajtasz végre, műveleteket csinálj. Ha valakinek magyarázni kell, hogy miért baj a side effect, az inkább ne menjen szoftverfejlesztőnek.

Szerintem itt kezdődik a C

Persze, ez tök jó egy kávé/tea mellé fejtörőnek, ha egy ilyen kód jön szembe code reviewen, akkor reflexből megy rá a reject, hogy ne húzza az időmet a kolléga.

Igazad van, DE.

Nem csak projektmuka és kezdő programozók esetében. Az ember csinálja meg "szépre" és könnyen olvashatóra a kódot, már csak a maga kedvéért is!

Egyszer a heapsort-ot tanulmányoztam, míg eljutottam az igazi gurukig, akik két sorban is meg tudták fogalmazni a feladatot. :-D

A kérdéskör nem erre szólt, hanem erre a részére:

... && --*ptr == 0

Másrészt, én személy szerint a nested if invertálását szeretem jobban:

if (!ptr) return;
if (*ptr == 0) return;

*ptr--;
if (*ptr == 0) timeout();

Ezt beleteszed egy inline függvénybe és bárki, bármikor és bármilyen körülmények között ránéz, látja, hogy mi és milyen sorrendben történik, a fordító pedig pont ugyanazt a tényleges kódot generálja, ami az egysoros megoldás, ami azért rossz, mert még egy ezer éve C nyelven fejlesztő senior ember is azzal nyit egy fórumtémát, hogy "Ilyesmit szoktam írni, de egy pillanatra megrémültem." Egyszerűen Q.E.D., hogy ez a fajta forgalmazás nem egyértelmű, ami kockázat, kerülendő.

Nem a konkrét nyelv a téma itt, ebben a szálban, hanem a clean code elvek vs faszméregetés; illetve az, hogy ha ebből a kérdéskörből egy feltehetően több évtizedes tapasztalattal rendelkező senior C fejlesztő fórum témát kreál a kétségei miatt, akkor igencsak sántít az, hogy ez márpedig jó így és mindenki számára teljesen egyértelmű.

Na, ez a profi [= pénzért csinálja és ez a fő profilja] C-programozó kreálta a témát és vannak kétségei, mondhatni teljesen jogosan, mert kiélezett helyzetben könnyen félremehet az értelmezése.

A többiek meg kibicelnek és méregetik a faszukat az asztalon, hogy ki tud trükkösebbnél trükkösebb kódot írni, ami minél több embert szopat meg és elismerést keresve nézelődnek körbe-körbe.

Végülis a GCC ma már gyakran szól (-Wall), hogy valami félrement például a *ptr--; kapcsán, de nem a valós problémáról szól illetve gyakran egyáltalán nem is szól. Ezért erre sem lehet alapozni.
Lássunk egy példát, amely egy szemléletes példája a pointerrel elkövetett információszivárgásnak: https://code.sololearn.com/cR2cs4Usrn0i
Itt mindössze az alábbi warning jön, de ettől lefordul és fut. A probléma, hogy mindeközben a "titkos" változó tartalma is kiírásra kerül.

teszt.c:14:5: warning: value computed is not used [-Wunused-value]
   14 |     *ptr--;
      |     ^~~~~~

Kontrasztként a Rust, mint  C vetélytársa normál (safe) módjában nem engedi, hogy varázslattal a változód által foglalt memóriaterületen kívül olvass vagy írj. Lehet próbálkozni.

Ha a pointerrel rácímzel a másik változóra, nyilván el tudod rontani azt, meg hozzá tudsz férni. Én ebben nem látok problémát, senki sem mondta, hogy túl kell címezni tömbön, rá kell címezni olyan változóra, amire nem szeretnénk. Szedtem én már fel bufferből 24 bites változót úgy, hogy uint32_t-be tettem, majd maszkoltam 0xffffff értékkel, hiszen a legfelső byte-jában már a következő változó alja volt ezáltal. Ez teljesen legális, semmi baj nincs vele, ha tudod, hogy mit csinálsz.

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

senki sem mondta, hogy túl kell címezni tömbön

Nézd globálisabban. Az a probléma, hogy az internetes eszközöknél a low level sérülékenységek >70%-a mégis ilyen túl lesz címezve problémából fakad.
Szerintem ezeknek a programozóknak se mondták, hogy címezzenek túl a tömbön, ha több adat érkezne. Mindössze nem figyeltek mindenhol oda a korlátozására és elég csak egyetlen helyen figyelmetlennek lenni.
Fordítsuk meg: miért nem lehet alapértelmezett az, hogy nem enged ki a változó által lefoglalt területből a nyelv?

Ha ez el tud dőlni fordítási időben, valamint kikapcsolható, akkor legyen. Ha csak egyetlen órajel vagy byte is kárát látja futásidőben, akkor ne legyen. Nem csak oprendszer fölött programozunk sok gigabyte RAM-ot felhasználva sok magon, sok gigahertzes CPU-n, ahol igazából pár MHz is elég lenne, hanem van, ahol minden órajel és minden byte számít.

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

Nem az lenne a megoldás, hogy nem pointer aritmetikával érdemes tömböt kezelni, ha nem csinálsz olyat, amihez tényleg szükséges, hogy tudd, hogy mi hol van a memóriában? Hanem pl. csinálsz egy struktúrát, és függvényeket az elérésére, sőt, C++-ban a boost::numeric::ublas::vector tudja is (ha nem kapcsolod direkt ki).

typedef class{
  int *ptr;
  size_t size;
} IntArray;

void IntAlloc(IntArray *a, size_t n){
    a->ptr = (int *)malloc(n * sizeof(int));
    if(!(a->ptr){
      fprintf(stderr, "Allocation error.\n");
      abort();
    }
    a->size = n;
}

void IntDealloc(IntArray a){
  if(!a.ptr) return;
  free(a.ptr);
}

int IntArrayGet(IntArray a, size_t n){
  if(n > a->size){
    fprintf(stderr, "Array read access violation.\n");
    abort();
  }
  return a.ptr[n];
}

void IntArraySet(IntArray a, size_t n, int v){
  if(n > a->size){
    fprintf(stderr, "Array read access violation.\n");
    abort();
  }
  a.ptr[n] = v;
}

Biztos van benne hiba, nem próbáltam ki. Az is biztos, hogy egy rutinos C programozó sokkal szebbet ír, meg az is, hogy kell még egy csomó minden (pl. egy csomó, egymás utáni elem írásakor nem akarsz minden elemnél ellenőrizni, és akkor még nem is elég intből intbe, hanem lehet, hogy konverzió és valami függvény alkalmazása is kell közben, ha mondjuk egy float vectort akarok int-ekből feltölteni, vagy egy float arrayt egy másik float array elemeinek a gyökével). De ilyesmire eleve érdemesebb C++-t használni. Olyasmire meg C-t és pointereket, ha pl. egy memory mapped device-hez írsz éppen drivert.

Sírok. Mondom, hogy az sem fér bele sokszor a futásidőbe, hogy függvényt hívjunk, mert az CALL, RET, meg memória másolás. Lehet szépészkedni, meg akár shell scriptet írni, de nem akkor, amikor valós időben dolgozol fel hárommillió mérési eredményt, s az eredményt el is kell küldened.

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

Önmagában nem zavar az offolás, ha az a szakma keretein belül jut eszébe valakinek. De valóban, az eredeti felvetésem más volt. Való igaz, az && alacsony precedenciája miatt önállóan kiértékelhetővé teszi annak argumentumait, s mehetünk balról jobbra az első false-ig, ahol kilépünk. Ez jó, hogy így van, a problémám az volt, hogy ha a precedenciát nézzük, előbb módosulhat a pointer által mutatott változó, s akkor a balról jobbra kiértékelés is más eredményt ad. Mondjuk akkor a nyitóban írt kifejezés NULL pointer dereference is egyben.

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

Ez a rész szerintem már marhára nem a microcontroller-programozásról szólt, hanem arról, hogy C-ben is lehet olvasható és biztonságos kódot írni. Azt elismerem, hogy ára van. Ahol nem fér bele a futásidőbe, ott nyilván nincs más megoldás, mint hatékony kódot írni. Kevesebb tere marad a programozónak hibázni.

Bizony, ez a rugózásról szól. ;) Meg arról, hogy C++, Java és Rust nyelven is osztják egyesek az észt. :-D

Az "áttekinthető kód" - ami locsemeg barátunknak áttekinthetetlen - az pont olyan, amit gondolkodás nélkül át lehet ültetni pl. PIC16 mikrokontrollerre. (Ami véletlenül locsemege barátunk kedvenc és csak asm-ben programozott autómárkája. :-D)

Ehhez a kódhoz két peremfeltétel kell:

  • balról jobbra értékelünk
  • "boolean rövidzár" szerint (Ami régen a túróspaszkálban csak opció volt.)

Lényegében egy jó C fordító megfelelő optimalizálással az idézett kódot fogja előállítani. Ha "visszafelé" elkészíted belőle a folyamatábrát, akkor nehezen kapod meg a topicnyitó if szerkezetet. Ez is azt bizonyítja, hogy nem jól van megfogalmazva.

A "Kevesebb tere marad a programozónak hibázni." annyiban jó megátás, - egyszerűbb cpu-t feltételezve - hogy pontosan azt kell leírni, amit csinálni szeretnél. És ettől kezdve egyszerűsödik a helyzet: Ha mást írsz le, akkor más fog történni. :-D

Most pedig már nem értem, miért molyol egy asm programozó a C side effect szintaxis elemzésével. A többieket inkább értem, mert már a C-t is elfelejtették, nemhogy az asm-et.

Nem ez volt a kérdés, hanem a side effect, ami mögötte van. Ilyen vizsgálatnál én is inkább a másodikat választanám. Mondjuk valószínűleg kiírnám a feltételvizsgálatot (== NULL/== 0). Nem azért, mert a fordított kódban bármi változást okoz, hanem azért, hogy egyértelműen jelezzem más, a kódot olvasónak, hogy mi volt a szándékom.

Elnézést, hogy belekotyogok ebbe a kissé akadémikus vitában. Szeritem itt a magányos farkasok és a nagy-multi_csapat tagjai közötti vitának lehetünk tanúi. Azt mindkét félnek tudomásul kell vennie, hogy más-más feladat más-más munka- és programozási stílust követel. Ha használsz egy nyelvet más a szókészleted a családban, egy tudományos előadáson és a kocsmában. Hát valami ilyesmiről van szó itt is...

A magamrészéről igyekszem - úgymond - didaktikusan programozni. Vagyis szeretném megérteni 4-5 év múlva is, hogy mit is akartam akkor, évekkel ezelőtt végrehajtatni a géppel. Ugyanakkor megértem azt, ha valaki küzd az órajelekért, mert ilyen dolgokkal is foglalkoztam. Alapvetően nagyon fontos lenne tudnia minden valamire C programozónak, hogy mire is fordul a kódja.

Akik team-ben dolgoznak ott nincs mese, van egy vállalati elvárás és ahhoz kell igazodni.

A magam részéről örülök, hogy olyan munkám van amilyen, nagyon kevés alklamazkodást igényel és a olyan programírási stílust alakítottam ki, amely számomra a legáttekinthetőbb. Utánam sem jön senki, hogy belejavítson, hibát keressen a kódban, egyszerűen az a megközelítés, amit használok (nem kizárólag a programírási stílusra gondolok) velem - pontosabban: a mi generációnkkal - kihal.

 

UFF, beszéltem.

> Sol omnibus lucet.

Ugyanakkor megértem azt, ha valaki küzd az órajelekért, mert ilyen dolgokkal is foglalkoztam.

Alapvetően erre írtam pár napja, hogy legalább legyen felette egy sor megjegyzés, hogy mi a lófaszra gondolt a költő és milyen side effect van itt kihasználva és ezzel megbaszva az olvashatóság a teljesítmény oltárán... :)

Már ne haragudj, de ez hülyeség. Egy villanyszerelő sem fog máshogy szerelni villanyt ha egy családi ház garázsába köti be, vagy ha egy irodaházat köt be. Vagy az UTP kábelt sem fogod máshogy bekötni.

Minden szakmának megvannak a maga szabványai, szabályai, best practice-ei. Ezekre az esetekre is vannak.

Ráadásul itt meg konkrét ASM kóddal is bizonyítva lett, hogy szimplán nem igaz az sem, hogy szarabb kód fordulna, ha side effect mentesen írta volna meg az if-et.

Vagy nem, mert vannak részek, amik nem változnak, függetlenül attól, hogy ilyen szoftver vagy olyan szoftver. Ráadásul ha már megmondani a tutit a téma: ha már előkerült a MISRA C is: Its aims are to facilitate code safety, security, portability and reliability in the context of embedded systems, specifically those systems programmed in ISO C / C90 / C99. Ez ugyan autóiparból jött, de találsz máshova is hasonló rendszereket. De elég csak felcsapni egy antipattern gyűjteményt.

Mi használjuk ezt a szabályrendszert, de ettől még ha mondjuk játékból írok magamnak egy kis programocskát, akkor simán lehet, hogy nem tartom ezeket be. Eleve nem rakok össze statikus kódelemzőt sem a játék projektjeimhez, így nem tudom garantálni, hogy nem csúszik-e be valami apróság.

Szóval részemről a hasonlat nem teljesen áll meg.

Nekem bejon az egysoros. Olyan tagokbol al, ami gyakran elofordul, az elso ket tag (pointet null? Ertek null?) konkretan unalmas. Az ember szeme vegigsiklik rajta, megnyugszik a lelke, hogy le van ellenorizve, majd ranez a harmadik tagra, hogy mi a lenyeg.

Ezt szerintem meg egy juniornak is ertenie kell. Ha nem erti, akkor meg nem junior, hanem csak tanulo. Ez egy jo programsor, lehet belole tanulni.

Locsemegenek pedig pirospont, hogy volt batorsaga a ketsegeit is megosztani, es nem azzal a kijelentessel jott ide, hogy csak az jo amit o mond.

A programozas absztrahalasi szintekrol szol. Ha valaki nem tudja az elmejeben elhelyezni a fenti egysorost, akkor ne alljon neki python lamda fuggveny mukodeset megerteni, - csak masolja be Ctrl-C, Ctrl-V a stackoverflowrol a gugli elso talalatat a probkemajara, es remenykedjen. Hiszen ez a junior, nem?

En leginkabb bash-ban irok dolgokat. Ott is lehet hasonlo szornyusegeket irni, harom soros csatakigyot benne grep, awk, sed, tr, es ami meg az eszembe jut, pl. bash beepitett string manipulacio.

Nem szeretem, mert fel ev mulva nehez seggberugni erte magamat, nem hajlik valami jol a terdem. Szet szoktam szedni fel soros reszekre, atmeneti valtozoba gyomoszolom a kimenet, es van, hogy meg ra is ellenorzok, mert miert ne. De nincs az azvisten, hogy minden elemi parancsot kulon sorba irjak, az mar a lo masik oldala lenne.

Szoval van egy egeszseges hatar, hogy kinek hol huzodik ez, azt embere valogatja. En Atari BASIC -en szocializalodtam, ergo lazan parametereztem a GOTO-t valtozoval, es sokaig nem ertettem, hogy miert dagad a programozas tanar feje, amikor panaszkodtam, hogy a Pascal szar, mert nem donthetem el futas idoben, hogy hanyas sorra ugorjon a vezerles, pedig az milyen elegans. Ehelyett az a sok IF... Tiszta ovoda. :-)

En a commentek hive vagyok. Ahol nem trivialis a kod, vagy nem eleg beszedesek a valtozok nevei, ott leirom, hogy mire gondolt a kolto. Es pesze megesik, hogy futas kozben ugy megyek tovabb, hogy bash kodbol a valtozok ertekei alapjan kigeneralok nehany tucat masik bash kodot, es rajuk adom a vezerlest, hogy na akkor fussatok bolondok, de a hatterben am, mert ettol leszunk gyorsabbak. Es gyonyoruen mukodik. Nem trivialis, ezt alairom, de ott a comment, hogy mit miert csinalok. Ha ezt mind szet kene bontanom, mert az onmodosito kod ordogtol valo, akkor morcos lennek.

Az eszkozoket szerintem nem kell felni hasznalni. Egy if -ben tobb ellenorzes es egy-ket valtozo egyszeru modositasa siman belefer.

Szerintem.

Te felfogtad, hogy itt nem az egy sor vs több sor a probléma, hanem a side effects kód?

Lehetne ez is a köd:

if (cucc && *cucc && Kutya(--(*cucc)) && Cica(--(*cucc)) {...}

ez is tök jól olvasható épp csak tippelni lehet, hogy vajon mi fog itt történni, nem módosít-e valahol valami olyat, ami miatt már nem az fog történni, amire számít az ember.

Ebben a sorban ket nezopont utkozik:

- 1, mit akar a kolto?

- 2, mit csinal belole a compiler?

A fenti szalban sokan az egyes ponton porogtek, hogy szegeny juniorok agya fel fog robbani ha ilyet latnak, tessek ezt szebben, csoportmunkasabban megirni. Reszben egyet is ertek veluk, de ezt a problemakort meg lehet oldani egy ertelmes kommenttel.

A masodik pontra pedig az ipar mar megszulte a megnyugtato valaszt: ugy hivjak, hogy teszteles. :-)

megnyugtato valaszt: ugy hivjak, hogy teszteles.

A biztonságos programozáshoz szükséges ezen kívül a forráskód auditálás, ugyanis a tesztelés önmagában nem tud abszolút 100,0%-ot biztosítani.
Side effect, a fordítófüggő "most éppen jó" és a kód félreolvashatóság az mind kockázat növelő tényező, a biztonságos programozás elve ellen dolgozik.

Az olyan kód, ahol egy feltétel vizsgálatban valami adatmódosítás van, szintén taposóakna. Szerinted újabb, modernebb nyelvekben miért nem lehet ezt ebben a formában leírni? Vagy amit még le lehet (pl. mikor módosító függvényt hívsz az if-ből), ott is miért vannak ajánlások, konvenciók, hogy ne tedd?

No es? Ezen kivul meg ezer fele elrontasi lehetoseg van, ami mind tanulsagos. Nem kell ezert aggodni. Vagy esetleg vegyuk ki a parancskeszletbol a dontott vonalat is, mert ha nulla erteku valtozo kerul a jobb oldalara, akkor meghal egy tunder? A programozas mar csak ilyen veszelyes szakma. :-)

+1, a kérdésben a --*ptr mint "side effect" ez a szint, és egyértelműen

nem módosít valahol valami olyat, ami miatt már nem az fog történni, amire számít az ember.

Egyet tudok érteni azzal, hogy beleírják egy szabványba hogy ők ilyet nem adnak ki a kezükből/nem fogadnak el a partnerektől, de ha az embert nem köti az adott szabvány, akkor el tudja dönteni hogy mi a jobb megoldás. A

if (ptr && *ptr && --*ptr == 0) timeout();

jobb, mint szétszedni zárójelekkel együtt 11 sorba, olvashatóbb, egyszerűbben látszik hogy jó, és sokkal kevesebb elírásra ad lehetőséget.

Nem a juniorok agyáról van szó, hanem arról, hogy miért vegyítesz összehasonlítást végző kódot olyannal, ami adaton machinál.

oldani egy ertelmes kommenttel.

Komment oda kell, ahol valami smelly vagy nem triviális dolog van. Ha meg valamit meg lehet írni triviálisan is, akkor azt úgy kell megírni. Ilyenkor hol van a sokat hangoztatott Keep it simple, stupid elv?

az ipar mar megszulte a megnyugtato valaszt: ugy hivjak, hogy teszteles. :-)

Vagy inkább azt, hogy don't do it. Ld. MISRA C.

A programozas lenyege, hogy vegyited a az osszehasonlitast vegzo kodot azzal, ami az adaton machinal. Ez by design ilyen.

A kerdes cdak az, hogy ezt egy zarojelen belul teszed, vagy kiveszel onnan reszeket?

De ha gondolod, akkor huzzuk karoba azt, aki nem atall egy zarojelen belul letrehozni egy valtozot, inicializalni, valtoztati, es feltetelesen ellenorizgetni? Itt egy pelda erre a felettebb trukkos, es ordogtol valo dologra:

for (register byte i=0; i<42; i++) {...}

Tenyleg az az ajanlas, hogy ez legyen harom sor? Szerintem eleg, ha csak az elborultabb esetek eseten vagjak nyakon a programozot, es a trivialis eseteket megengedik neki. Nehogymar antipattern legyen egy for ciklus, banyek.

OT:

3 sorból amúgy se úszod meg, mert akkor nem lesz lokális változó: sztem minimum 5. (ha elhagyod a külső { } -eket, akkor maradt egy i változód)

{

register byte i = 0;

while ( i < 42 ) {

...

i++;

}

}

Ráadásul ezzel a formával vacakolni kell, mert máris nem lehet egy jó kis continue a ciklusmagban - ellentétben az eredeti for-ral. Szóval ez minden szempontból rosszabb, mint az átírás előtti forma.

/OT

De gyaníthatóan nem pont erre gondoltak.

Rust-ból, mint C alternatívára törekvő modern nyelvből például száműzött ez a for() univerzalitás.
Így itt nem tudsz leírni ilyet: for (int i=5, j=3, k=4; a[i] && b[j++] == '\r'; i-=2*j, k+=2) { ... }

Ami lehetőséged van, az kb. ennyi:
    for normál növekménnyel: https://rust.godbolt.org/z/zWzE7PjW4
    for esetleg i+=3 módon: https://rust.godbolt.org/z/3o8af6q1f

    for amolyan foreach jelleggel: https://rust.godbolt.org/z/qTxoManxf
    erdekességképpen ugyanez iterátorral megvalósítva: https://rust.godbolt.org/z/Ecn9zrePh

Szegenykeim. Nem irigylem oket. Szar lehet eletlen kessel dolgozni, de persze ertem, hogy magas nyelvi formakat akarnak hasznalni. :-)

En konkretan meg bash parancsorban sem szegyellem hasznalni a C tipusu for ciklust. Szerintem tokeletes kompozicio, benne van minden lenyeges, es semmi sallang. Ugyan mi egyeb kivanalmam lehetne meg ezen kivul?

Nem vagyok benne biztos, hogy erre gondolsz, (szolj, ha mast kellett volna eszrevennem) de a foreach mar megjelent C++ 11 -ben, std::for_each neven. A foreach valoban kenyelmes, megsporolja a sizeof lekerest, es az index valtozoval sem kell torodni, igy tenyleg tomorebb a kod.

En amugy a yield -et hianyolom bash alol (C++ -ba mar beleheggesztettek) mert azt nagyon megszerettem C# alatt.

Van egy csomo jo cucc mindenfele nyelvekben, de szerintem nem veletlen, hogy ennyi nyelv van. Mind masban penge, es masban nyogvenyelos. A C/C++ eleg hardverkozeli, hogy ne kelljen ugy megvedeni a kezdoktol, hogy kiherelik a funkcionalitasat, a patternek tuleroltetesevel. Aki nem szeret sok parancsot latni egy sorban, az hasznaljon assemblert! :-)

Mind masban penge, es masban nyogvenyelos.

Ez így van. Ez is egy komoly tervezési szempont, mikor melyik programozási nyelvekre építve kezdjünk egy projektet.
Gyakran egyazon projekten belül is több nyelvre támaszkodunk. Amelyik amire praktikusabb, na és persze képes benne programozni a csipet-csapat.

Nem, a programozás lényege az, hogy megoldást nyújts a feladatra. Ezen kívül szokott elvárás lenni az, hogy a kód legyen lehetőség szerint minél inkább hibamentes, karbantartható, meg a többi.

És igen, a for is egy nagyon rossz konstrukció, pont azért, mert össze-vissza lehet kutyuni a cikkusváltozód. Nem véletlen, hogy más nyelvekből egyre inkább próbálják írtani.

A C for ciklus egy borotvaeles kes. Veszelyes es hasznos, de szerintem hasznosabb mint amilyen veszelyes, szerinted pedig forditva. ;-)

A hibamentesseget en is sokra tartom. A topic nyito pelda sorban en latok egy hibat, amit eddig senki nem forszirozott. Mi van akkor, ha a ptr null?

Az elso valasz, hogy semmi baj nincs, hiszen az if elso tagja miatt nem cimzunk ra, hibas.

Ugyanis ez csak az ellen ved, hogy itt es most nem robbanunk fel, azonban egy ilyen kod eseteben mar elvarjuk, hogy a ptr mutasson valahova, marpedig, ha ez nem teljesul, akkor mar gondok vannak.

En ugy vagyok vele, hogy a fontos if -eknek ki kell irni az else agat. Ez a mentalitas szerintem tobb hibatol ov meg, mint az, hogy dontesi blokkba teszunk adatmodositast.

Ha szetszednem tobb sorba, akkor emiatt lenne, hogy kezeljem a pointer null erteket is, es ne soporjem a szonyeg ala. Mas miatt szerintem nem kell, ha itt a ptr uzemszeruen lehet null, mert mondjuk ezzel kezeljuk azt az esetet amikor a szamlalo ki van kapcsolva, vagyis nem kell vele foglalkozni, akkor teljesen jo ez igy.

Már oda sikerült eljutnod, hogy maga a nyelv a rossz? Ja, mint egyesek szerint az is rossz, hogy az apa férfi, az anya nő. Szerintem meg ezek értékek, jók. Épp, mint a C-ben az if-en belüli adatmódosítás lehetősége, tudniillik ettől általános, s nem korlátozott a nyelv.

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

Nem nekem. hanem a szakmának, nem véletlen, hogy a modernebb nyelvekből egy csomó mindent kivágták a kukába, abból, amit a C tudott, bár azt elismerem, hogy ezeknél a vezérelv sok esetben az volt, hogy a C egy szar alkalmazásfejlesztő nyelv. Igaz, nem is annak találtak ki, hanem rendszerprogramozásra.

Ld. Tony Hoare is pl. "billion dollar mistake"-nek nevezte a null pointer megalkotását.

Az meg, hogy mit enged a nyelv: egy férfi segget is meg lehet dugni, főleg, ha tranyó és nőnek vallja magát és megengedi az illető. Egyesek szerint ez is mekkora érték mert éljen a diverzitás, de a nagy többség ezt még mindig csak szimplán buzulásnak hívja. (Szalmabábozhatsz még itt a woke szarsággal, de elég rossz ajtón kopogtatsz.)

Arra utalok, hogy szerintem nagyon félre mentek a dolgok. Meg akarjuk védeni a programozót saját magától, mert feltételezzük, hogy hülye, ezért alkotunk vacak, nem általános nyelveket, de legalább oda a hatékonyság. Véletlenül se menjen a programozó assembly, vagy ANSI C közelébe, mert még baja esik szegénynek.

Ezzel szemben azt állítom, picivel jobban meg kellene tanulni programozni, s nem fércműveket alkotni, nem összeollózni innen-onnan dolgokat. Az internetről fogalmatlanul copy-paste-elés nem programozás.

A NULL pointer egy zseniális dolog. Tudod vele jelezni annak aktuális érvényességét, tudod ezt vizsgálni. Assembly-ben is bevett szokás, hogy kiveszel egy elemet a halmazból, s azt használod hibajelzésre, érvénytelenség jelzőnek, végejelzőnek. Mint pl. a C stringekben a '\0'.

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

Ja, annyira zseniális dolog, hogy a saját készítője mondja, hogy bocs, elkúrtam. Mindegy. Erősen úgy látom, hogy annyira oda vagy gyógyulva a mikrokontrollerekre, hogy el sem tudod hinni, hogy az absztrakció ellenére a generált kód még lehet hatékony. Vagy az, hogy a konkrét megvalósítás, vagy amit kifejezel a nyelvben szemantikailag az elváljon.

A meg lehet tanulni programozni dologra: ha jól emlékszem, pont te írtad, hogy sosem tanultad a C-t, hanem magadtól tanultad. Ennek tükrében teljesen vicces a "picivel jobban meg kellene tanulni programozni" kijelentésed, mert pont azok mondják, hogy ne írjunk ilyen kódot, akik picivel jobban megtanultak programozni. Esetenként részt is vettek egy-két programnyelv kifejlesztésében. Nem azért, mert hülyék lettek volna, vagy hülyékre akarták méretezni a dolgokat, hanem azért, mert rájöttek, hogy baromi sok problémát okozhat egy ilyen. 50 éve jelent meg a C, azóta rengeteget változott az informatika, egynéhány dologra már rájöttek az emberek.

Amit én látok az az, hogy juniorok általában örülnek, ha egyáltalán működik, amit csinálnak. Aztán jön a medior szint, ahol előkerülnek a mindenféle trükkös dolgok, nyelvi elemek, ahogy egyre jobban és magabiztosabban megismer egy nyelvet, technológiát. Aztán tapasztalataim szerint mire valaki elér a senior szinthez, elkezdi ritkítani a mindenféle "okos" kódot és a felesleges komplexitást. Mert szar karbantartani, és általában több a nyűg vele, mint a haszon. Nyilván mérlegel, hogy kell-e a plusz teljesítmény, vagy sem (már ha egyáltalán van, mert sokszor az okos kód valójában nagyon nem okos, csak a sok trükktől nem látszik, hogy egyébként szar). Architect szinten meg kb. tűzzel-vassal írtja. Kellemetlen dolog egy több millió eurót/dollárt kifizetni kártérítésekre vagy ne adj Isten, egy szoftverhibával elkövetett gondatlanságból elkövetett emberölés miatt rendőrségre járni. Sorolhatnék neked tonnaszám eseteket, szerintem egy jó részéről te is hallottál már. Persze mondogathatod tovább, hogy "jaj, oda a hatékonyság" meg "okosabb programozók kellenének".

Nem a dolgok mentek félre, csupán a szoftveripar fejlődik és jön rá dolgokra, hogy mi az ami nem jó. 50 évnyi tapasztalat meg nem kevés, főleg nem az informatikában, ami gyakorlatilag épp, hogy csak kialakult.

Ezzel szemben azt állítom, picivel jobban meg kellene tanulni programozni, s nem fércműveket alkotni, nem összeollózni innen-onnan dolgokat. Az internetről fogalmatlanul copy-paste-elés nem programozás.

Szerinted meg tud valósulni, hogy a programozó csapat SOHA ne hagyjon el, illetve SOHA ne implementáljon tévesen egy alapvető ellenőrzést?
Én még mindig a fordítottjában hiszek: alapból a rendszer ellenőriz és ahol az optimalizáló feleslegesnek látja, ott a rendszer elhagyja az ellenőrzést.

Nézd meg ezeket a példákat és a belőle generált assembly-t:
    https://rust.godbolt.org/z/6KMzPoj1f

Ahol az 1000 helyből mégsem kezel le a fejlesztőcsapat 1-et, ott a rendszer biztosítja hogy ne férhessen hozzá a szoftver a változón kívüli memóriaterületre.

Nyilván nem. Én is szúrtam már el, de semmi baj, kijavítom a hibát.

ott a rendszer biztosítja hogy ne férhessen hozzá a szoftver a változón kívüli memóriaterületre

Félek, hogy ez csak futásidőben ellenőrizhető, s akkor elrontottuk a sebességet meg a memóriafelhasználást is.

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

Én is szúrtam már el,

És itt válik el a történet, van aki megvonja a vállát és van, aki próbálja kerülni azt, hogy olyan kódot írjon, amit el lehet szúrni.

de semmi baj, kijavítom a hibát.

És az okozott károkat is? Könnyű egy szoftverhibát csak úgy, önmagában nézni és eltekinteni attól, hogy az a szoftver, valahol, valamilyen körülmények közt futni fog, emberek használják majd.

s akkor elrontottuk a sebességet meg a memóriafelhasználást is.

Ezen kívül létezik még jó pár más fontos tényező és elvárás egy szoftvertől is.

És az okozott károkat is?

Miért ír az ember bootloadert? Unaloműzésként, vagy azért, hogy távoli gépen firmware-t lehessen cserélni?

Ezen kívül létezik még jó pár más fontos tényező és elvárás egy szoftvertől is.

Persze, csak ha másodpercenként hárommillió mérési eredményt kell valós időben előfeldolgozni valamiképpen, akkor hirtelen mégis ez válik a legfontosabbá.

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

Szerintem szinte mindegyik nyelvi elem veszélyes, de a használatuk elkerülhetetlen.

Az if, mert elfedhet egy hibát else ág nélkül. Mégis gyakran látom így. 
A ciklus, mert végtelen lehet​​​​, akárhogy is írod. Mégis kell. 
A rekurzivitás, mert a végtelenbe mehet, mégis használják, mert elegáns.
Az osztás is veszélyes, mert nullával felrobban, de mégis gyakran elosztunk minden marhaságot. 
A goto mert mindenki üldözi, pedig az apple féle goto fail sem a goto hibája volt. 
A longjump is ördögtől való, bár megkérdezném a magas nyelvek művelőit, hogy a kivételkezelést hogyan oldják meg nélküle?
A poiterek, referenciák is gyakran okoznak gondot. Adjunk át mindent érték szerint? Juj, de jó lenne!

Tudsz olyan utasítást, amivel nem lehet galibát okozni? Mert én nem. Még egy összeadás is okozhat túlcsordulást.

Igen, a C tipusú for egy veszélyes eszköz. Pont mint minden más parancs. Viszont a a funkciókészlete nagyon széles felhasználási kört enged meg. Nem kötelező bolond módra használni, azonban lehet vele elegáns tömör kódot írni, ami segít a csökkenteni a hibákat, mert az ember könnyebben azonosít egy gyakran használt rövid nyelvi elemet, mint ugyanazt, de sokkal több sorban leírva. Egyszerűen több figyelem juthat így a lényegi részre.

Igen, a C nyelv szintaxisa megengedi az adatmódosítást a feltételvizsgálaton belül, mert ez egy gyakran használt eszköz. Mint egy éles kés a fiókban. Én gyakran vágok kenyeret, húst, zöldséget, hadd ne kelljen mások félelme miatt ilyen elemi dolgok miatt elballagnom a boltba ahol van kenyér meg hússzeletelő masina.

Tényleg félünk egy " while (--i) " kifejezéstől? Tényleg félünk egy " for (i=200; i<=400; i++) " kifejezéstől? Ezeknél mi a side effect? Miért félnek tőle más programnyelvekben? Felnőtt már egy-két generáció, akik elég magabiztosan voltak képesek használni a C for ciklust a mindennapokban. Mikor vált veszélyforrássá? Nem lehet, hogy az éles késeket használni nem képes embereket kéne korlátozni, és nem a kés gyárakat gonosznak beállítani?

Nekem továbbra is az a véleményem, hogy a tévedésre, hibára lehetőséget adó nyelvre, vagy nyelvi elemre nem az a válasz, hogy "Tiltsuk be!", vagy hogy "Ne használjuk!", hanem a képzés, hogy hogyan használjuk, és a megfelelő tesztelés.

Nekem továbbra is az a véleményem, hogy a tévedésre, hibára lehetőséget adó nyelvre, vagy nyelvi elemre nem az a válasz, hogy "Tiltsuk be!", vagy hogy "Ne használjuk!", hanem a képzés, hogy hogyan használjuk, és a megfelelő tesztelés.

A "ne használjuk" meg a "hogyan használjuk" az ugyanaz, csak más a tárgy. Ha valamire megmondod hogy hogyan kell használni, akkor az ekvivalens valami másnak a nem használatával, és fordítva: ha valaki megmondja hogy mit ne használj, az ekvivalens valami másnak a hogyan használatával.

Miért félnek tőle más programnyelvekben? Felnőtt már egy-két generáció, akik elég magabiztosan voltak képesek használni a C for ciklust a mindennapokban. Mikor vált veszélyforrássá?

A for ciklusnál tömörebb, olvashatóbb, hibabiztosabb és egyszerűbben összefűzhető a kód többi részével a map/filter/fold/foreach.

Gyűlölöm azokat a nyelveket, amelyekben van foreach. Szerintem sokkal gondolkodósabb. Ilyen nyelvekben is inkább for-t vagy while-t használok. Egyszerűbb, átláthatóbb, jobban megmutatja, mi történik, közelebb van az assemblyhez.

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

Én kedvelem a foreach -et. Amikor nincs más igény, csak végiggyalogolni a tagokon, akkor kényelmes. Azon belül is van break, continue, szóval az esetek többségében teljesen megfelelő. De amikor másra van szükség, például egy tag olvasása után tudom, hogy a következő érdekes tag nem a következő, hanem mondjuk tízzel odébb lesz, akkor a for jobb, mert abban növelhetem a ciklusváltozót, és máris ott vagyok. De az ilyen ritka.

Gyűlölöm azokat a nyelveket, amelyekben van foreach.

Es errol mit gondolsz? 

struct whatever
 {      struct  whatever *prev,*next;
        ...
 };

...

struct whatever *first,*something;
first=whatever_new();
something=whatever_new();
list_add(first,something);
...

struct whatever *curr;
for ( curr=first ; curr != NULL ; curr=curr->next )
 {      whatever_function(curr);
        ...
 }

Szoval ki-ki eldontheti hogy ez most mennyire foreach es mennyire for :)

Azt nem mondtam, hogy nem lehet olyan konstrukciót csinálni, amely végigmegy egy pointerekkel láncolt listán az egyes elemeken, struktúrán. Sőt, akár valamilyen szűrés szerint lehet egy olyan láncolást is csinálni, amelyben csak azok az elemek vannak, amelyek megfelelnek a szűrés feltételeinek.

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

Kb ez (lehet) a gyakorlati kulonbseg: a for() az egy lista indexein halad vegig, a foreach() meg a lista elemein. Gyk. egy indirekcios szinttel lejjebb. Olyat ugye ritka(bba)n csinalunk, hogy:

for ( i=0; i<i_max; i++ )
 {    function_something(whatever,i);
 }

es inkabb ez a gyakoribb:

for ( i=0; i<i_max; i++ )
 {    function(whatever,something[i]);
 }

Es ezutobbi mar inkabb a foreach()-hez all kozelebb, mintsem a for()-hoz a gyakorlati alkalmazast illetoen. 

Ha az indexet nem használod semmire a ciklusban, pl. olyan a ciklus, hogy

for(int i =0; i < v.size(); i++){
  do_something(v[i]);
}

akkor az index olyan, mint egy feleslegesen csupaszon hagyott vezeték, csak arra jó, hogy figyelni kelljen rá, hogy nehogy megfogd, pl. nehogy i-nek nevezz egy belső ciklusban egy változót. Nem sokkal szebb ennél, hogy

for(auto vElement = v.begin(), vElement != v.end(); vElement++){
   do_something(*vElement);
}

amiben a ciklusban benne csak az a változó létezik, amit használsz is, ráadásul, ha később rájössz, hogy nem array az optimális konténer, hanem láncolt lista, akkor csak az elején kell átírni. Tudom, hogy ez C++, de simán emulálható C-ben, és még overheadje sem lesz, ha nem egy kalap szar a compiler elintézi.

Nyilván nem azt akarom mondani, hogy ne legyen a nyelvben normális for ciklus, van amire az az optimális, van amire ez. Az a jó, ha van választék.

Azt sem mondom, hogy embedded rendszerre így kell megírni, vagy C++-ban kell megírni, ahol egy függvényhívás is megengedhetetlen overhead, az más speciális eset. De van a másik véglet is, amikor nyugodtan feláldozható pár százalék CPU-idő azért, hogy csökkentsük a hibázás valószínűségét, vagy hogy olcsóbb legyen majd 15 év múlva továbbíratni a programot valaki mással.

Épp az szokott zavarni, hogy eltüntetjük az indexet. Akkor előbb-utóbb kell az elejére egy i = 0; a belsejébe egy i++; mert szinte biztos, hogy számolnom kell az elemeket, mert hivatkozik valami másra, ami az iterációs indextől függ. Akkor nem nyertünk semmit, csak duplikálva lesz. Előlem elfedődik a ciklusvátozó, ami van, csak nem látszik, majd én létrehozom láthatóan még egy példányban. Pazaroljuk a RAM-ot, futásidőt, ráadásul utálom, ha valami kettős módon van könyvelve, rossz a tudat, hogy ezek szétcsúszhatnak. Például egyszerre pointerrel és indexszel.

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

Mondanék egy egyszerű példát: az a feladat, hogy keressük meg a tömb maximumát. A naív megoldás (a példa kedvéért PHP, mivel ott kétféle foreach van):

$max= false;
foreach ($arr as $ai) {
    if ($max===false || $ai>$max) $max= $ai;
}

A nem naív megoldás az, ha rögtön visszakérdezünk, hogy 'és ha egynél több helyen veszi fel a maximumot, az oké?' ugyanis jó eséllyel az lesz a válasz, hogy 'hát... hát-hát... no igen, akkor inkább legyen az eredmény egy újabb tömb, ami a maximális elem(ek) indexeit tartalmazza'. Na ekkor kezdünk el kódolni, hogy pl.:

$maxp= [];
foreach ($arr as $i => $ai) {
    if ($maxp==[] || $ai>$arr[$maxp[0]]) $maxp= [$i];
    else if ($ai==$arr[$maxp[0]]) $maxp[]= $i;
}

Persze nem mindig derül ki ez elsőre, ilyenkor csak az ösztönünkre hallgatva kezdjük rögtön egy olyan változattal, amit kevesebb átalakítással lehet fejleszteni, pl.:

$maxp= false;
foreach ($arr as $i => $ai) {
    if ($maxp===false || $ai>$arr[$maxp]) $maxp= $i;
}
$max= $maxp===false? false: $arr[$maxp];

Ha szakítunk a C szintaktika maszirozásaival és for ciklus helyett például Rust iterátor szintaktikával esünk neki, az így néz ki:

Maxérték:
   https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gi…

Ez nem tűnik erőforrás pazarlónak a for ciklusos iránnyal összevetve.
   https://rust.godbolt.org/z/G4cEq13cv  - méretre optimalizálva
   https://rust.godbolt.org/z/a73sf34av   - sebességre optimalizálva

A folytatás, ahol az egy vagy több maxérték pozícióját kell meghatároznunk, iterátorral is bonyolódik:
   https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gi…

De ez igazából párhuzamosítható feladat, mint az összeg, maximum kiválasztás, rendezés stb: két részre osztom, megoldom a problémát mindkét részen, kapok (maxval_1,[keys]_1)  (maxval_2,[keys]_2)  és innen már értelemszerű. Vagy n részre osztom. Vagy ahogy a legkezelhetőbb algebrailag.

Keresgéltem hogy minek kell teljesülnie egy rekurzív, párhuzamosítható függvényre hogy valami képes legyen magától párhuzamosítani, de nem találtam semmit, talán még nem tart itt a tudomány.

(És akkor a probléma megoldása is csak ennyi: a két/n részre osztásos rekurzió több algoritmusnál előjön, hasznos, biztosan valaki megírta már, és akkor csak azt a részt kell megírnunk nekünk hogy mivel térjen vissza f ( (maxval_1,[keys]_1)  , (maxval_2,[keys]_2) ) . )

És itt jön be a másik kérdés: a maximumkeresés vajon mennyire gyorsítható a gyakorlatban?
Elvileg van 8 magod, 8-szorosára gyorsul kockáspapír-ceruza szerint.
Gyakorlat viszont keserűbb. Az ilyen egyszerű, egyetlen összehasonlítás és lépünk a következőre mert nem nagyobb, szóval itt a CPU mag helyett gyakran a RAM tempó lesz a valódi korlát.

Valójában nehéz dolog néha optimalizálni. Jelfeldolgozásnál ARM NEON esetén és gyakran az x86 AVX használatakor a RAM tempója fogja vissza az algoritmusaimat, a CPU-ban lenne még kraft.

for(auto vElement = v.begin(), vElement != v.end(), vElement++){
  do_something(*vElement, vElement - v.begin());
}

Nyilván úgy néz ki, mintha most elpazarolnánk egy kivonást, de a compiler feltehetőleg nem hülye, ha attól tényleg lassabb, majd elintézi, és a csupasz vezeték sincs ott.

Engem meg épp az szokott zavarni, hogy minek van indexem, hogy ha egyszer nincs mit indexelni? Főleg, ha mondjuk nem egy tömböm van, hanem láncolt listán? Nem indexet akarok kezelni, hanem a gyűjtemény elemeivel dolgozni.

Egyébként a pazarlásról: anno olvastam egy nagyon kiváló cikket "mire optimalizáljunk?" címen. Ha jól emlékszem, a feladat egy tömb elemeinek szummázása volt, talán némi csavarral, csóka megírta többféleképp is, kezdve C-s fortól, C# foreach, linq, sum, stb.-ig, megnézve a generált ASM kódot is. Legmókásabb az volt, hogy a for-ra sikerült messze ráverni valamelyik felállásban, szimplán azért, mert volt más megoldás, ami a prefetchinget figyelembe véve gyorsabb kódot generált. Vagy egyes esetekben, mikor megpróbált optimalizálni a kódon, nem hogy javult, de romlott a teljesítmény. Mégpedig azért, mert a "frissen végzett egyetemista" kódot felismerte a fordító, arra ki volt optimalizálva, az "okos" kódra viszont nem, emiatt butább asm kódot generált.

Szóval ennyit ér az, hogy ha magad akarsz kezelni mindent: false sense of control.

Egyébként ha már ennyire szóba hozod a pazarlást: az egyetlen létező erőforrás, amit te eddig említettél az a hardver. Igen, megértem, hogy mikrokontrollernél ez fontos. Megjegyzem, nem csak ott, csak más súllyal. Mi pl. logisztikai optimalizáción dolgozunk. Nálunk kb. a legszűkebb erőforrás a fejlesztők száma és azok ideje. Munka van dögivel, fejlesztendő featurek sorban állnak. Viszont ezek a featurek a vevőinknek éves szinten több millió euróban mérhető üzemanyag-költség megtakarítást jelentenek vagy azt, hogy 5-10-15%-al kisebb flotta is elég a fuvarok elszállításához a jobb kihasználtság miatt. Tedd kérlek mérlegre azzal, hogy spórolnánk mondjuk éves szinten 1-2 ezer eurót valamelyik cloud providernél. Különösen ajánlom végiggondolásra, hogy a szállítmányozás mekkora részt foglal el a gazdaságban és milyen hatása van a környezetre is.

Néha egy kicsit érdemes kihúzni az ember fejét az assembly kódból is kicsit a kontextust is nézni, hogy hol, mikor milyen környezetben van használva az ember munkája.

(Btw. ha valakinek megvan a link a cikkre, akár internet archiveből, légyszi linkelje, évek óta keresem, de szerintem konkrétan a domain is megszűnt már. Valamilyen Zoltán volt azt hiszem a szerző, alapvetően C#-os témákkal foglalkozott.)

Néha egy kicsit érdemes kihúzni az ember fejét az assembly kódból is kicsit a kontextust is nézni, hogy hol, mikor milyen környezetben van használva az ember munkája.

Igen, de ez fordítva is igaz, mert itt talán többen is olyan szemszögből kommunikáltok, mintha csak szerverre meg PC-re objektumorientált kód írásáról lenne szó, ahol CPU és RAM van rogyásig, ráadásul mindegy is, mennyi idő alatt készül el, mert aszinkron, s van alatta oprendszer. Én ezzel szemben oprendszer nélkül, MCU-ra, valós idejű feldolgozásról, natív C-ben vagy assembly-ben programozva, kevés programtárral, kevés RAM-mal, kevés gépi ciklussal megoldva beszéltem a feladatról, s embedded környezetben meg ez az igény. Az én munkám ilyen.

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

"A "ne használjuk" meg a "hogyan használjuk" az ugyanaz, csak más a tárgy."

Ezzel nem értek egyet, mert például itt: https://hup.hu/comment/2748983#comment-2748983 elhangzott, hogy a Rust nyelvben, mint C/C++ alternatíva nyelvből száműzték. Ha valamit nem tudok használni, akkor ott már nincs szó arról, hogy hogyan használjam.

Nem az a baj, ha a kés éles, hanem ha a nyele is. (Amúgy szerintem az vele a helyzet, mint a GOTO-val: akkor kell használni, ha szükség van rá.,  Egy modern compilerrel nem egyáltalán nem biztos, hogy gyorsabb lesz valami attól, ha trükkösen írok meg valamit, mert elég jól optimalizálnak, úgyhogy olvashatatlan trükkös kódot elég akkor írni, ha egy profiling során kiderült, hogy ott lassú, ott van egy bottleneck. )

Aztán jöhet egy ilyen kódolási stílus, mert megengedi:

for (
  előkészítés,
  előkészítés,
  előkészítés,
  előkészítés,
;
  feltétel &&
  (feltétel || feltétel) &&
  függvény1() &&
  feltétel
;
  ciklusvégi utasítás,
  ciklusvégi utasítás,
  ciklusvégi utasítás,
  ciklusvégi utasítás,
  függvény2(),
  ciklusvégi utasítás,
  ciklusvégi utasítás,
);

Végülis nem is kell a { ... } rész, ha nincs break és continue.

Rustban is tetszőlegesen sok kódot beírhatsz akármelyik kifejezés helyére. Ha van egy programod, meg van benne valahol egy kifejezés, akkor berakhatod az egész programkódot a kifejezésbe. Nem látom, hogy ez miért érdekes?

szerk: na jó, elgondolkodtam. Az járt a fejemben, hogy a for ciklusban az 1..length-et kicserélheted akármire, ami egy tömböt ad vissza, és közben olyan side effecteket hajt végre, amilyeneket nem szégyell. De ez talán pont rustban pont nincs így, például változók értékeit nem tudja megváltoztatni (?)

Mindegy, minden más nyelv megengedi, és a rust is megengedi, hogy a kifejezések helyére, ahova alapesetben 1, néha 2 dolgot írsz, ha akarod tetszőlegesen sokat írhass.

változók értékeit nem tudja megváltoztatni

Igen, amit a C-hez képest megfordítottak:
   - C-nél "pluszmunka" kitenni az a "const" kulcsszót. Alapból minden írható. Gyakran nem "pluszmunkázunk", jó az írható pointerként.
   - Rust-nál "pluszmunka" kitenni a "mut" kulcsszót. Alapból semmi nem írható. Ahol kell, ott "pluszmunkázunk".

Egyúttal a Rust odafigyel arra, hogy kétszer nem tudod egyazon változót betenni a függvénybe, ha egyike írható és előrébb van. Sem a róla származó referenciát, stb.
Lásd: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist…

Itt mindenki PC-re programozik? A const MCU-n egyúttal azt is jelenti, hogy flash memóriába, programtárba van allokálva az a valami, nem pedig RAM-ba. A RAM drága, mert kevés van belőle, ezen felül oda be kell másolni programtárból előbb, tehát futásidőben is rossz lenne, ha RAM-ban tárolódna. Sőt, ugyanúgy elfoglalná a helyet a programtárban, mert onnan lehetne RAM-ba inicializálni a konstanst. Szóval a const egészen más ahhoz képest, mintha nem írom oda.

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

Na egy újabb szépség: const ... és nem ugyanaz a const, amiről mi beszélünk. Mikrovezérlőn ugyanígy.

Van egy adathalmazod, amit egymás után több kiértékelőnek akarsz átadni. A kiértékelőket a csapat más tagjai írják, te csak headerfájlt kapsz.

uint8_t kiertekelo1(uint8_t *adat, uint8_t len);
uint8_t kiertekelo2(const uint8_t *adat, uint8_t len);

Melyik esetben lehetsz nyugodt, hogy a kiértékelőnek átadott 100 byte-os adathalmazod változatlan marad a függvényhívás után is?
(Implementáld ... változtasd meg mondjuk az adat[1] értékét és próbáld lefordítani.)

Rust esetén ugyanez egyébként így fest:

fn kiertekelo1(adat: &mut[u8]) -> u8 // ha nem módosítanád, warning-ol a fordító az indokolatlan "mut"-ért.
fn kiertekelo2(adat: &[u8]) -> u8    // "const", ha nem veszed a fáradságot a "mut" kiírására.

Önmagában ez a függvény-deklaráció nem biztosít semmit, a függvény belsőleg typecast-olhatja a pointert nem-const-ra. (Const ügyben érdekes lehet a C++ FQA 18. pontja is (A fejlődésnek hála újabban mobilon nem tudok URL-t bepasztázni. Ez bizonyára valamiféle biztonsági intézkedés. Vagy csak Nyomasek Bobó újabb sikere.))

Végülis átcastolhatod (C-ben csináltam próbából pár éve én is ilyet) vagy akár inline assembly-ben is tutira írhatod a memóriát.
De ezek nem azok a műveletek, amit az adott függvényre végzett kód auditon olyan könnyen kimagyarázol.

Rust esetén szintén hasonló a helyzet, csak ott akad egy figyelemfelhívó unsafe kulcsszó is az alábbi mindkét esetben.
  - inline assembly - ezzel tutira megírhatod, ahogy C-ben is.
  - szintén cast-olhatsz: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist…

Már látom szemeim előtt a kód auditon a kérdést: "Miért nyúltál a "const uint8_t *data" / immutable &u32  alá?
Mi volt a célod, hogy mégis más értéket adj vissza, amikor megígérted hogy nem bántod?
Eleve miért nyúltál "unsafe" kulcsszóhoz? Unsafe kulcsszó nélkül tényleg megoldhatatlan a rád bízott feladat?

Sőt, a módosíthatóságot cast-tal ráadni Rustban még a szokásos unsafe-es „vigyázz a saját algoritmusodra” figyelmeztetésnél is erősebb probléma.  A fordító annyit optimál, hogy nem megbízható akkor sem, ha ez az egyszerű példa most épp működik.  (mondjuk most nem sikerült 10 perc alatt hibás kódot prezentálnom, de korábbról emlékszem ilyenre).

Az UnsafeCell pont jó ide, arról tudja, hogy hogyan (ne) optimáljon.

https://doc.rust-lang.org/nomicon/transmutes.html

„Transmuting an & to &mut is UB.”

https://stackoverflow.com/questions/54237610/is-there-a-way-to-make-an-…

C++ (17-től?...) is csinál olyanokat, amiről érdemes tudni alacsonyszintű programozáskor, pl. struktúrát konstans mező esetén optimálhat - ha a memóriában ábrázolt képe érdekel (rákasztolod valahova), akkor std::launder-t kell használni.

Ez jogos, kiveve ha azert nem akarod a flash-ben tarolni mert annak az elerese lassabb... es kb architekturatol fuggetlenul megszakad akkor az instruction pipeline. Ezert emiatt jobb lehet ha pl #define-olod, vagy ilyesmi, mert akkor a fordito kioptimalizalja attol fuggoen hogy mire (sebesseg vs kodmeret) akarsz optimalizalni. Vagy nem minden esetben, mert pl pc-relativ ldr-re forditja... ha meg inline tolti be a konstanst akkor meg stall lehet a pipeline-ban... szoval ez azert egy nehez szakma :) 

Még ettől is rosszabb lehet a helyzet. ;)

Bár nem hangzott el konkrétan, de talán 8 bites PIC-ről lehet szól. Abban nincs pipeline, de van egy 24 bites (hardver) pointer, amivel a flash olvasható, de nem közvetlenül, hanem egy átmeneti regiszterbe.  A pointer beállításával (általában 1-2 bájt) egy utasítással felolvashatod a a bájtot és dolgozhatsz vele.  Az olvasó utasítás lehet pl. auto increment, tehát egy string elemeinek beolvasása is csak egy-egy utasítás. Ha a pointer interrupt alatt is használatban van, akkor törődni kell a mentés-visszaállítással még az átmeneti regiszter esetén is.

Szóval nem árt tisztába lenni azzal is, hogyan műkődik a szerkezet. Ezért van az, hogy aki több ezer vagy tízezer class-t használ, magassszintű programnyelven dolgozik - aminek szintén kismillió szabálya és side effectje létezik -, és mégsem boldogul a 30..80 assembly utasítással. Mert ott pontosan érteni kell mit csinál.

Vagyis a magasszintű nyelv jó, mert elfedi előtted a tudnivalókat. Ezért viszont olyan súlyos árat fizethetsz, amihez képest a "kiveve ha azert nem akarod a flash-ben tarolni mert annak az elerese lassabb" kismiska. Az mcu-ra készült C fordítók - kellő optimailzálás mellett is - 3-5x nagyobb és lassabb kódot generálnak. Ez sok esetben elfogadható, de még ennél is súlyosabb a helyzet! A C alapvetően nem ilyen architektúrára készült.

A C-hez itt hiányzik a sok regiszter , a szoftver stack, a lineárisan címezhető (és elegendő) memória, de még a vektoros interrupt is. Sok esetben érdemes a programot "vezérlés" és "szoftver" részre bontani. Az előbbi készüljön assemblerben, az utóbbi ne legyen érzékeny a sebességre.

Bizony, annak csak a neve PIC. :(

Viszont nem kell két pécét kötnöd a műszerhez! :-D

Ezzel a kategóriával valószínűleg nem fogok dolgozni. A pic18-ig megy a -O3, ami már szinte olvasható kódot fordít. ;)

A fordítóknak igen jó az ára. Ha belegondolok, 1995-ben vettem C fordítót AIX-re 160 eFt-ért. Ahoz képest a $2900 az XC32-ért soknak tűnik.

PIC32MZ, ez talán valami ARM core.

Pontosabban a PIC32 az MIPS architektúra. MIPS cég ugyanúgy licenceli a dolgait, mint az ARM, csak kedvezőbb áron. Az ARM-hoz hasonlóan a mikrovezérlős ág mellett szintén van alkalmazásprocesszor ága is. Ez utóbbival találkozol rengeteg SOHO hálózati eszközben. Olyanokban, amikre előszeretettel rántunk OpenWRT-t.

root@OpenWrt:~# cat /proc/cpuinfo 
system type		: MediaTek MT7621 ver:1 eco:3
machine			: Ubiquiti UniFi nanoHD
processor		: 0
cpu model		: MIPS 1004Kc V2.15
BogoMIPS		: 581.63
...
...
isa			: mips1 mips2 mips32r1 mips32r2
...

Kár egyébként a MIPS-ért. Kb. 2 évtizede megrekedt a fejlődésük. Kb. azidőtájt, amikor ARM a modern ARM Cortex mikrovezérlőivel és alkalmazásprocesszoraival előrukkolt.
És hogy miért találod a MIPS-et az eredeti Microchip termékágban és miért a hálózati eszközökben? Ahogy imént írtam, a licencelése kedvezőbb az ARM tarifánál.

A harmadik versenyző még a pályafutása elején áll, ez pedig a RISC-V architektúra. Ez még leginkább csak bontogatja a szárnyait, de ígéretes versenyző.

Szerencsére a GCC és CLANG egyaránt fordít ARM, MIPS, RISC-V (mikrovezérlő, alkalmazásprocesszor 32/64 bit) magokra egyaránt. Benne van az Ubuntu disztróban mindhárom architektúrára a fordító.

Sőt a Rust is fordít rájuk. Alábbi parancs mutatja a listát mindháromra. Benne van mindhárom architektúrára való fordítás.

$ rustc --print target-list | grep -E 'arm|mips|riscv'

További hasznos eszköz amikor a fejlesztői lapka nincs nálad:

$ apt-cache search qemu-system | grep -E 'arm|mips|riscv'
qemu-system-arm - QEMU full system emulation binaries (arm)
qemu-system-mips - QEMU full system emulation binaries (mips)

RISC-V emuláció úgy néz ki, az új QEMU-ban már van: https://wiki.qemu.org/Documentation/Platforms/RISCV

Még ettől is rosszabb lehet a helyzet. ;)

Ez ketsegtelen :) Mindig van lejjebb!

Bár nem hangzott el konkrétan, de talán 8 bites PIC-ről lehet szól

Konkretan AVR es ARM-nal is igy van, de barhonnan is nezzuk, ez sztem architekturatol fuggetlen allitas. Talan nativ CISC-nel lehetnek olyan implementaciok ahol kevesse szamit (regi klasszikus x86, 386, 486). Pelda: ARMv6-M-nel (lasd: Cortex-M0) 3 felekepp is betoltodhet egy konstans, az erteketol fuggoen: https://godbolt.org/z/rYn9zzv3b. A masodik es harmadik kozotti kulonbsegbol latszik hogy egy 8-bites konstans betoltese + egy kis matek meg mindig gyorsabb mint egy 32-bites konstans flash-bol valo betoltese. Raadasul az lr vermelesnek kesleltesvel/felcserelesevel a meg a konstans betoltes + matek kozotti stall-t is probalja minimalizalni a fordito ;) Viszont 3 darab 8-bites szam matekjabol mar lassabb oszetenni a 17+ bites konstanst mintha (varhatoan) flash-bol toltenenk be. 

A C-hez itt hiányzik a sok regiszter , a szoftver stack, a lineárisan címezhető (és elegendő) memória, de még a vektoros interrupt is. Sok esetben érdemes a programot "vezérlés" és "szoftver" részre bontani. Az előbbi készüljön assemblerben, az utóbbi ne legyen érzékeny a sebességre.

Ez is azert erosen architekura-fuggo allitas. Peldaul ARM-ben siman meg tudod irni a _start-ot is C-ben, pont azert mert a Cortex-Mx garantalja hogy egy system reset utan elobb tolti be az xSP regisztert a vektor tablabol mint ahogy a futtatast elinditja. Es ennek az architekturalis felepitesnek pont ez is volt a celja hogy mar bare metal programokat is tudjunk tisztan C-ben irni ;) Hasonloan, a C ABI is illeszkedik a hardveres interrupt entry/leave felepitesehez: azaz az ISR-ek is ugyanolyan C fuggvenyek mint barmi mas, azt leszamitva hogy persze void (*)(void); tipusuak lehetnek csak. Mindezt kizarolag annak koszonhetoen hogy az interrupt entry lementi az {r0-r3,r12,lr,pc,xPSR} regisztereket, a C ABI meg pont az {r4-r11} elmenteserol kell gondoskodjon, barmilyen fuggvenyt is irunk. 

Mas architekturaknal (pl AVR-nel) mar valoban figyelned kell arra hogy a _start az tartalmazza az SP inicializalasat, illetve az ISR-ek nem csak ugy siman fuggvenyek hanem specialis fuggvenyek (ld. ret vs. reti, naked attributumok jelentosege, stb). Itt mar nyilvan nem usszuk meg az asm-et, akkor sem ha akarnak, foleg a _start eseten - es ez valoban igy van jol. Es vsz PIC-nel is igy van, de azt nem ismerem sajnos egyatalan. 

No jó, locsemege bevallotta, hogy a mikrokontroller == MIPS32M, 252MHz, 5 stage pipeline, 32 bit CPU+DSP+FPU. Ez olyan, mint két pécé, 10 speciális kártyával, szupergyors összekötésekkel. Kiválóan alkalmas "szoftverrel történő hajtásra".

Amiről én írtam - ezt nem tudván - jóval kisebb szerkentyűk: PIC10..18, PIC24 és dsPIC - az utóbbiak 16 bites illetve DSP képességekkel felruházott mcu-k. Ezekben az a jó naaagy szoftver képességek előtt, hogy igen nagy választékban gyártják speciális, uniformizált perifériákkal. Ugyanezeket a perifériákat rakják a vásárolt MIPS core köré is - ennyi az átjárhatóság.

A 32 bites nagyágyúk másra valók. Ha olyan feladatod van, amit hardverrel kell megvalósítani, arra a PIC16/18 (14 és 16 bitess utasítás szélesség) eszközök kiválóan alkalmasak. A rengeteg beépítet perifériát "összekötözgeted" - persze programból - és szinte tetszőleges hardvert kialakíthatsz. A kialakított szerkezetet elindítod és teszi a dolgát. Utána alláthatod szoftverrel, kommunikációval is.

Csak egy példa a seciális perifériára a PIC16F1615-ben a Math Accelerator, ami nem más, mint egy 35 bites PID DSP. Felprogramozod és kész is van mondjuk egy precíziós motorvezérlés - amihez az összes egyéb elemet is megtalálod a tokban.

hogy a mikrokontroller == MIPS32M, 252MHz, 5 stage pipeline, 32 bit CPU+DSP+FPU.

És ez még közel sem durva cucc. Nekem az STM32H757 a kemény mikrovezérlő. Ez a termék tartalmaz egy 480 MHz-es ARM Cortex M7 mikrovezérlőt + egy 240 MHz-es ARM Cortex M4 mikrovezérlőt a tokjában.
Hardver float már ARM Cortex-M4 tartozék, de a benne levő ARM Cortex M7 mikrovezérlő miatt már hardver double is van.
De ennél még komolyabb ARM mikrovezérlő fog jönni, amibe beleteszik a Heliumot is, ezáltal mikrovezérlővel még összetettebb valósidejű számítást (még több adattal futó jelfeldolgozás, szabályozókörök) el tudunk végezni. A Helium az ARM Cortex M55 alapokon gyártott mikrovezérlőknek lehet opcionális része, itt látható többféle kombinációban.

Szerintem ez rovid es tomor, de osszessegeben nagyon rosszul kovetheto/olvashato. Tul sok a hibalehetoseg refactor eseten, es nem szembeotlo, hogy modositas tortent.

Mondjuk nem hasznalok c/c++-t munkam soran, de egy ilyen MR-t a jelenlegi tudasommal visszadobnek.