Funkciók visszatérő húrok, jó stílus?

A C programjaimban gyakran szükségem van arra, hogy karakterlánc ábrázolást adjak az ADT -imről. Még akkor is, ha nem kell kinyomtatnom a karakterláncot a képernyőn történő megjelenítéshez, az ilyen hibakeresési módszer elegendő. Szóval gyakran felmerül ez a fajta funkció.

char * mytype_to_string( const mytype_t *t ); 

Valójában rájövök, hogy (legalább) három lehetőségem van a memória kezelésére, hogy a karakterlánc visszatérjen.

1. alternatíva: A visszatérő karakterlánc egy statikus char tömbben tárolható a függvényben. Nem kell sok gondolkodás, kivéve, hogy a húr minden híváskor felülíródik. Ami bizonyos esetekben problémát okozhat.

2. alternatíva: A függvényen belül mallocszal osztja be a halom karakterláncát. Nagyon ügyes, mivel akkor nem kell gondolkodnom a puffer méretéről vagy a felülírásról. Emlékeznem kell azonban arra, hogy amikor elkészültem, felszabadítottam () a karakterláncot, majd hozzárendelnem kell egy ideiglenes változóhoz is, hogy Fel tudok szabadítani. És akkor a kupac-allokáció valóban sokkal lassabb, mint a verem-allokáció, ezért legyen szűk keresztmetszet, ha ezt egy ciklusban megismétlik.

3. alternatíva: Adja meg a mutatót egy puffernek, és hagyja, hogy a hívó allokáljon ez a puffer. Mint:

char * mytype_to_string( const mytype_t *mt, char *buf, size_t buflen ); 

Ez több erőfeszítést jelent a hívó számára. Azt is észreveszem, hogy ez az alternatíva az argumentumok sorrendjében ad nekem egy másik lehetőséget. Melyik érv legyen az első és az utolsó? (Tulajdonképpen hat lehetőség)

Szóval, melyiket részesítsem előnyben? Miért miért? Van valamilyen íratlan szabvány a C fejlesztők között?

Hozzászólások

  • Csak egy megfigyelési megjegyzés: a legtöbb operációs rendszer a 3. opciót használja – a hívó mindenképp elosztja a puffert; megmondja a puffer mutatót és a kapacitást; a callee kitölti a buffot er és visszaadja a karakterlánc tényleges hosszát is, ha a puffer nem elegendő. Példa: sysctlbyname OS X és iOS rendszereken

Válasz

A legtöbbet látott módszer a 2 és a 3.

A felhasználó által biztosított puffer használata valójában meglehetősen egyszerű:

char[128] buffer; mytype_to_string(mt, buffer, 128); 

Bár a legtöbb megvalósítás megadja a használt puffer mennyiségét.

A 2. opció lassabb lesz, és veszélyes, ha dinamikusan összekapcsolt könyvtárakat használnak, ahol használhatnak különböző futási idők (és különböző halmok). Tehát nem szabad felszabadítani azt, amit egy másik könyvtárban malloced. Ehhez egy free_string(char*) függvényre van szükség a kezeléshez.

Megjegyzések

  • Köszönöm! Azt hiszem, a 3. alternatívát is szeretem a legjobban. Szeretnék azonban olyan dolgokat csinálni, mint: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));, és ezért nyertem ‘, hogy visszaadjam a felhasznált hosszúságot, hanem a mutatót. a húrra. A dinamikus könyvtári megjegyzés valóban fontos.
  • Nem ‘ t ez sizeof(buffer) - 1 kell, hogy megfeleljen a \0 terminátor?
  • @ Michael-O nem, a null kifejezés nem szerepel a puffer méretében, ami azt jelenti, hogy a maximálisan behelyezhető karakterlánc 1-rel kisebb, mint az átadott méret. Ezt a mintát használja a biztonságos karakterlánc a szokásos könyvtárban, például a snprintf használatban.
  • @ratchetfreak Köszönjük a pontosítást. Jó lenne kibővíteni a választ ezzel a bölcsességgel.

Válasz

További tervezési ötlet a # 3

lehetőségekhez, adja meg a mytype ugyanabban a .h fájlban, mint a mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256 

Most a felhasználó ennek megfelelően kódolhat.

char buf[MYTYPE_TO_STRING_SIZE]; puts(mytype_to_string(mt, buf, sizeof buf)); 

Rendelés

A tömbök mérete, ha először, lehetővé teszi a VLA típusokat.

char * mytype_to_string( const mytype_t *mt, size_t bufsize, char *buf[bufsize]); 

Egy dimenzióval nem annyira fontos, de 2 vagy többnél hasznos.

void matrix(size_t row, size_t col, double matrix[row][col]); 

Emlékszem, hogy a következő C-ben előnyben részesített idióma az, hogy először a méretet választottam. Meg kell találni ezt a hivatkozást …

Válasz

A @ratchetfreak kiváló válaszának kiegészítéseként rámutatnék, hogy a 3. alternatíva hasonló paradigmát / mintát követ, mint a standard C könyvtárfunkciók.

Például: strncpy.

 char * strncpy ( char * destination, const char * source, size_t num );  

Ugyanazon paradigma követése segíthet hogy csökkentse az új fejlesztők (vagy akár a jövőbeli önmaga) kognitív terhelését, amikor használniuk kell az Ön funkcióját.

Az egyetlen különbség a bejegyzésében szereplővel az lenne, hogy a argumentum általában elsőként szerepel az argumentumlistában.Tehát:

 char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen );  

Válasz

A legtöbb esetben visszhangozom @ratchet_freak (lehet, hogy sizeof buffer felett 128) de furcsa válaszokkal szeretnék ide beugrani. Mi lenne, ha furcsa lennék? Miért ne, azon túl, hogy kollégáink furcsa pillantásokat kapnak, és külön meggyőzőnek kell lennünk? És ezt ajánlom:

// Note allocator parameter. char* mytype_to_string(allocator* alloc, const mytype_t* t) { char* buf = allocate(alloc, however_much_you_need); // fill out buf based on "t" contents return buf; } 

És példa a használatra:

void func(my_type a, my_type b) { allocator alloc = allocator_new(); const char* str1 = mytype_to_string(&alloc, &a); if (!str1) goto oom; const char* str2 = mytype_to_string(&alloc, &b); if (!str2) goto oom // do something with str1 and str2 goto finish; oom: errno = ENOMEM; finish: // Frees all memory allocated through `alloc`. allocator_purge(&alloc); } 

Ha így csinálta, nagyon hatékonyá (hatékonyabbá) teheti az elosztót mint malloc mind a kiosztási / elosztási költségek, mind pedig a memória-hozzáférés javított referencia-helyének szempontjából. Ez lehet egy aréna-elosztó, amely csak egy mutató növelésével jár a kiosztási kérelmek általános eseteiben és a memóriát szekvenciálisan összegyűjti nagy összefüggő blokkokból (az első blokkhoz nem is szükséges halom hely – a veremhez rendelhető). Ez leegyszerűsíti a hibakezelést. Szintén ez lehet a legvitatottabb, de azt gondolom, hogy ez gyakorlatilag teljesen nyilvánvaló abban a tekintetben, hogy világossá teszi a hívó számára, hogy memóriát fog lefoglalni az általad átadott allokátornak, és kifejezett felszabadítást igényel (allocator_purge ebben az esetben) anélkül, hogy ezt a viselkedést minden lehetséges függvényben dokumentálnia kellene, ha ezt a stílust következetesen használja. A lefoglaló paraméter elfogadása remélhetőleg meglehetősen nyilvánvalóvá teszi.

Nem tudom. Itt olyan ellenérveket kapok, mint a lehető legegyszerűbb arénaallokátor megvalósítása (csak az összes kéréshez használja a maximális igazítást), és ezzel foglalkozni túl sok munka. Tompa gondolatom az, hogy mi vagyunk mi, Python programozók? Használhatja a Pythont is, ha igen. Kérjük, használja a Pythont, ha ezek a részletek nem számítanak. Komolyan beszélek. Sok olyan C programozó kollégám volt, akik nagy valószínűséggel nemcsak helyes kódot, de esetleg még hatékonyabban is írnának a Pythonnal, mivel figyelmen kívül hagyják az olyan dolgokat, mint a referencia helye, miközben balra és jobbra létrehozott hibákba botlanak. Nem látom, mi van olyan ijesztő néhány egyszerű arénaallokátorral kapcsolatban, ha C programozók vagyunk, akik olyan dolgokkal foglalkoznak, mint az adat lokalitása és az optimális utasításválasztás, és erre vitathatatlanul sokkal kevesebb a gondolkodás, mint legalábbis az olyan típusú interfészekre, amelyek megkövetelik hívók, hogy kifejezetten felszabadítsanak minden egyes olyan dolgot, amelyet a kezelőfelület visszaadhat. Tömeges elosztást kínál az olyan típusú egyedi elosztásokkal szemben, amelyek hajlamosabbak hibázni. Egy megfelelő C programozó kihívást jelent a malloc számára, ahogy látom, főleg, ha hotspotként jelenik meg a profiljukban. Az én álláspontom szerint több ” oomph ” kell, hogy legyen indoklása, hogy 2020-ban továbbra is C programozó lehessünk, és megtehetjük ” ne zárkózzon el többé olyan dolgoktól, mint a memóriafoglalók.

És ennek nincsenek olyan éles esetei, hogy fix méretű puffert allokálunk, ahol mondjuk 256 bájtot osztunk ki, és az így kapott karakterlánc nagyobb. Még akkor is, ha függvényeink elkerülik a puffer túllépését (például sprintf_s esetén), még több kód van helyreállítva az ilyen hibáktól, amelyek szükségesek, amelyeket elhagyhatunk az allokátor esettel, mivel nem “nincsenek ilyen éles esetek. Az ilyen esetekkel nem kell foglalkoznunk az allokátor esetében, hacsak nem merítjük ki igazán hardverünk fizikai címzési terét (amelyet a fenti kód kezel, de nem kell ezzel foglalkoznunk ” az előre lefoglalt pufferből ” külön a ” memóriából “).

Válasz

Azon kívül, hogy amit javasol, az egy rossz kódszag, nekem a 3. alternatíva hangzik a legjobban. Én is úgy gondolom, mint a @ gnasher729, hogy rossz nyelvet használsz.

Megjegyzések

Válasz

Hogy őszinte legyek, érdemes áttérni egy másik nyelvre, ahol a karakterlánc visszaadása nem összetett, munkaigényes és hibára hajlamos művelet.

Fontolja meg a C ++ vagy az Objective-C lehetőségeket, ahol a kód 99% -át változatlanul hagyhatja.

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük