[Megoldva] Bármilyen típusú pointer átadása ne mondjon warningot

Fórumok

Írtam egy ilyet:

void nfree(void **p) {

    if (p) {
        free(*p);
        *p = NULL;
    }
    return;
}

Nem akarok típust cast-olni minden híváskor, és nem akarok emiatt warningot látni. Valahogy a realloc(), free() megoldja, hogy a fordító nem nyüszít, bármilyen típusú pointert is adok át. Néztem az stdlib.h-t is, de talán valamilyen attribútum lesz a megoldás, viszont nem tudom mi és miképp.

Mit kell tennem, hogy például ez ne adjon warning: passing argument 1 of ‘nfree’ from incompatible pointer type [-Wincompatible-pointer-types] figyelmeztetést? Már a cast-oláson illetve a warningok globális kikapcsolásán túl.

Megoldás

Az első hozzászólásban apal által adott megoldás nyerte el tetszésemet. Lefordult warning nélkül, ami persze érthető.

Köszönöm a segítséget!
 

Hozzászólások

Szerkesztve: 2023. 08. 02., sze – 21:44
void __nfree(void **p) 
{
 /* ...*/
}

#define  nfree(x)  __nfree((void **)(x))

Nem a legszebb workaround, de ugye amit mondasz az nem teljesen igaz, mert a realloc() meg free() az void*-ot eszik es nem void**-ot. Ugyanakkor az ketsegtelen hogy egy jo kerdes miszerint egy `pointer *` tipus hogy vedheto be - siman lehet hogy van erre valami __attribute__ (( ... )).

Jaj, de egyszerű, s hogy ez nekem mennyire nem jutott eszembe! Köszönöm. :)

Azt tudom, hogy a realloc(), free() az adattípusra mutató pointert eszi. Az nfree() megoldásomnak a pointer címét kell átadni, s a hely felszabadítása után NULL pointert ír bele, ezzel biztosan el lehet kerülni a double free és a use after free típusú hibákat. Jó, az utóbbi esetben NULL pointer dereference lesz, de abba legalább azonnal belepusztul, s könnyen kiderül a baj.

Itt nem arra gondolok, hogy az ember benéz valamit, hanem előfordul, hogy hibakezelés, egyebek miatt másodjára is felszabadítanánk a helyet. A free(NULL) viszont legitim, s akkor nem kell rettentő bonyolult adminisztrálás ehhez.

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

void nfree(void* ptr) {
    void** typed_ptr = (void**)&ptr;
    if (typed_ptr == NULL || *typed_ptr == NULL) {
        return;
    }

    free(*typed_ptr);
    *typed_ptr = NULL;
}
#define foo1(x)  { do_something(); do_something_else(); }
#define foo2(x)  do { do_something(); do_something_else(); } while(0)

if ( condition ) 
   foo1(z);
else
   what_ever();

Probald ki: foo1(z) nem fog lefordulni, a foo2(z) meg menni fog (es azt fogja csinalni amire a kolto gondol!)

Ha egy instruction pipeline talalkozik mar a fetch/decode szakaszban egy hatrafele ugrassal, akkor - noha nem tudja hogy a feltetel az teljesulni fog-e mert a pipeline meg nem jutott el odaig - szoval akkor feltelezi hogy a hatrafele ugras azert nagyobb esellyel fog megvalosulni mint ahogy a kod tovabbmegy. Es pl nem dobja el az instruction cache-t mondvan hogy "ah, joesellyel ujra fog kelleni majd mindjart onnan valami, es mar azt is tudom hogy kb micsoda". Egy elorefele torteno felteteles ugrasnal meg nem fog csak azert prefetch-et inditani (a cache tobbi reszenek a karara) csak azert mert "jaj, lehet hogy elore kell majd ugrani". 

Tehát azt mondod, hogy nem a következő utasítást prefetch-eli, hanem azt, ahova ugrana vissza?

Fura, mert hardwareben agyatlanul könnyű ezt implementálni. A következő utasítást prefetch-eljük, amíg az aktuálist végrehajtjuk. Ezért, ha ugrunk, elbuktuk a prefetch-et, s az ugrás után kell egy fetch majd egy végrehajtás. Kis PIC-eknél ezért kétszer hosszabb időben az ugrás, mint a többi utasítás. Valójában nem az ugrás a hosszabb, hanem az utána lévő utasítás előtt van egy fetch, de az gyakorlatilag ugyanaz.

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

Ennel sokkal egyszerubb. Nezz elso korben egy fetch -> decode -> execute -> writeback pipeline-t. Hogy a felteteles ugras az elore vagy hatrafele mutat, az a decode szakasznal a relativ offset legfolso bitjebol mar azonnal kiderul:

  • Ha ez a bit 0, akkor az ugras elorefele mutat majd, az az "unlikely" eset, tehat nem csinalsz semmit, mert a fetch altal behozott utasitast nagy valoszinuseggel ugyis fel kell dolgoznod. Tehat itt ez a preferalt. 
  • Ha ez a bit az 1, akkor pedig egy sima offset aritmetikanak (az egy darab offset szelessegu full adderrel vett) eredmenyet vizsgalva le tudsz szolni a cache modulnak hogy "jaj, barmi is legyen, ne urits ki semmi cache-t mert ugyis kellni fog majd onnan valami." Mivel ez nem a pipeline resze, ezert az elektronikanak a kritikus uthosszat nem fogja novelni mert parhuzamosan tortenik ez az osszeadas magaval a pipeline-nal. Szoval megintcsak jol ki tudja hasznalni a mikroarchitektura a dolgot.

Es akkor gondolj bele abba hogy egy ennel hosszabb instruction pipeline eseteben a decode elotti reszt nyugodtan tekintheted egy L(-1) cache-nak is akar, vagy abba hogy pl egy szelesebb instruction busz eseten a busz kimeneti regiszteret pedig akar egy L0-s cachekent is felfoghatod. Ezek a fenti dolgok pl mar egy 16bites ISA szelesseg es egy 32bit szeles instruction bus eseten is megjelennek mar azonnal (tehat 50% hogy mar ezzel is nyersz egy orajelet, ha keveset kell visszaugranod... oke, ez a gyakorlatban busy wait-es eseteket leszamitva nem gyakori, de ugye ki hasznal mar elesben 32 bites ISA buszokat).

Ja, hogy az utasítás pipeline bizgeréli a cache controllert. Jellemzően kisebb, egyszerűbb hardware-ekben gondolkodom. :) Egyébként világos, amit írtál. Bár hozzáteszem, a while (0) simán megy tovább, szóval ott mindenféle szólás nélkül is megvan már a prefetch is, és a cache tartalom is. Előre ugrás inkább akkor válik nyűgössé, ha az valóban ugrás és nem a következő utasítás.

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

Nem feltetlen a "bizgereles" szakkifejezest hasznalnam itten, inkabb ugy mondanam hogy "hint-et ad" :) Peldaul mar az is lehet egy hint hogy nem kell "kapkodni" a prefetch szakasszal. Ezzel az "L0 cache" el tudja engedni a kovetkezo oraciklusban a rendszer busz-matrixat vagy arbiter-et, es igy egy masik varakozo (pl DMA vezerlo, vagy memory mapped periferia) fogja nyerni azt az orajelet tutira. Mert masik esetben akkor (az egyebkent nagyobb prioritasu) fetch az elveszi ugyan az orajelet mas elol, de tok feleslegesen mert joesellyel ugyis dobni fogja majd azt az utasitast amit kiszedett. Szoval ezzel mar egy olyan mikrovezerlonel is nyerhetsz, amiben multi-master konfiguracio van. Azaz pl egy DMA. De meg ez sem kell feltetlen, mert egy Neumann architekturanal pedig egy sima load/store jellegu muveletet se kell hogy feleslegesen visszatartson (ami ugye mar kicsit hatrebb van, kicsivel vagy joval a decode szakasz utan). 

Jogos, mert nem tudja a dereferált pointer által mutatott adatszerkezet méretét, de felépítését, szerkezetét sem. Ellenben itt szerintem ezzel semmi baj, mert a free()-nek tényleg csak a lefoglalt memória címe kell, a kernel nem tud arról, mit tároltunk oda, azt hogyan használtuk fel. Csak adott címtől adott hosszúságú terület.

Az viszont izgalmasabb, amit a cikkben hivatkoznak, miszerint a NULL pointer nem mindig (void *) 0, amiből az következik szerintem, hogy az if (p != NULL) lesz a hordozható alak, az if (p) nem ekvivalens vele, bár éppen PC-n jó, meg PIC32-n is, ami MIPS.

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

Ez két külön bobóság, az egyik, hogy Strupstrupnak hála a NULL esetleg nem pointer, a másik az, hogy teoretikusan lehet olyan platform, ahol 0-t (vagy NULL-t) adsz értékül egy pointernek, de a compiler valami más, nemnulla értéket generál belőle (pl. 0xdeadbeef).

Ettől még a két if-ed ekvivalens.

Mert a fordító okosan csinálja, nem pedig bután numerikusan 0-ra vizsgál? Ha így van, az viszont jó hír.

NULL esetleg nem pointer

Ha nem pointer, akkor mi? Már csak azért is pointernek gondolnám jónak, mert pointerrel hasonlítjuk össze, illetve pointer típusú változó kapja értékül.

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

PIC32 MIPS-en is 0 volt, mígnem feltalálták valamelyik verziótól, hogy legyen (void *) 0. Elég nehezen értem, hogy a 0, mint szám, hogyan hasonlítható pointerrel, bár az is igaz, hogy nem változóban van, nincs is típus adva neki, szóval kezdek kicsit elveszni, hogy is van ez.

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

Inkabb az a kerdes hogy az adott architektura mit is tarol a 0 cimen (es/vagy melyik 0 cimen a sok kozul). Ha eleve nem megengedo (mint pl az ARM), vagy nincs is oda kepezve semmi (RISC-V) vagy nagyon tudod hogy mit akarsz csinalni ha oda irsz (klasszik x86, vagy az AVR) akkor siman lehet NULL = 0 = szam, ami nulla. Ha van MMU-d, akkor is lehet NULL = 0, foleg ha a magasabb privilegizalasi szinten sem lesz gond ebbol (ld. elobbi lista). A fentebb linkelt doksi is inkabb (szamomra legalabbis) egzotikus architekturak eseten mondja azt hogy a NULL az nem a szam ami nulla. Es ott biztos megvan az oka. 

Ha jól értelek, itt az a trükk, hogy nem bármilyen számra igaz ez, csak a 0-ra.

Kicsit olyan érzésem van, mint amikor a nullvektorról azt mondjuk, hogy az mindenre merőleges, mert akkor továbbra is igaz, hogy merőleges vektorok skalárszorzata nulla. :)

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