Functies die snaren teruggeven, goede stijl?

In mijn C-programmas heb ik vaak een manier nodig om een stringvoorstelling van mijn ADTs te maken. Zelfs als ik de string op geen enkele manier naar het scherm hoef af te drukken, is het handig om een dergelijke methode voor foutopsporing te hebben. Dus dit soort functie komt vaak naar voren.

char * mytype_to_string( const mytype_t *t ); 

Ik realiseer me eigenlijk dat ik hier (ten minste) drie opties heb voor het omgaan met het geheugen voor de string die moet worden geretourneerd.

Alternatief 1: het opslaan van de retourstring in een statische char-array in de functie Ik hoef niet veel na te denken, behalve dat de string bij elke oproep wordt overschreven. Dat kan in sommige gevallen een probleem zijn.

Alternatief 2: Wijs de string op de heap toe met malloc binnen de functie. Echt netjes, want ik hoef dan niet te denken aan de grootte van een buffer of het overschrijven. Ik moet echter onthouden om de string vrij te geven () als ik klaar ben, en dan moet ik ook een tijdelijke variabele toewijzen, zodat Ik kan vrijmaken. En dan is heap-toewijzing echt veel langzamer dan stack-toewijzing, daarom een bottleneck als dit in een lus wordt herhaald.

Alternatief 3: geef de aanwijzer door aan een buffer en laat de beller toewijzen die buffer. Zoals:

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

Dit brengt meer moeite voor de beller. Ik merk ook dat dit alternatief mij een andere optie geeft in de volgorde van de argumenten. Welk argument zou ik als eerste en laatste moeten hebben? (Eigenlijk zes mogelijkheden)

Dus, welke zou ik verkiezen? Enig waarom? Is er een soort ongeschreven standaard onder C-ontwikkelaars?

Opmerkingen

  • Gewoon een observatie-opmerking, de meeste besturingssystemen gebruiken optie 3 – beller wijst hoe dan ook buffer toe; vertelt de bufferwijzer en capaciteit; aangeroepen wordt buff er en geeft ook de werkelijke lengte van de string terug als de buffer onvoldoende is. Voorbeeld: sysctlbyname in OS X en iOS

Antwoord

De methoden die ik “het meest heb gezien, zijn 2 en 3.

De door de gebruiker aangeleverde buffer is eigenlijk vrij eenvoudig te gebruiken:

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

Hoewel de meeste implementaties de hoeveelheid gebruikte buffer zullen retourneren.

Optie 2 zal langzamer zijn en is gevaarlijk wanneer dynamisch gekoppelde bibliotheken worden gebruikt waar ze verschillende looptijden (en verschillende hopen). Zodat je niet kunt bevrijden wat er misleid is in een andere bibliotheek. Dit vereist dan een free_string(char*) functie om ermee om te gaan.

Opmerkingen

  • Bedankt! Ik denk dat ik Alternatief 3 ook het leukst vind. Ik wil echter dingen kunnen doen als: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf)); en daarom heb ik ‘ geen zin om de gebruikte lengte terug te geven, maar liever de aanwijzer aan de snaar. De opmerking van de dynamische bibliotheek is erg belangrijk.
  • Moet niet ‘ t dit sizeof(buffer) - 1 zijn om te voldoen aan de \0 terminator?
  • @ Michael-O nee de null-term is opgenomen in de buffergrootte, wat betekent dat de maximale string die kan worden ingevoerd 1 kleiner is dan de doorgegeven in grootte. Dit is het patroon dat de veilige string functioneert in de standaard bibliotheek zoals snprintf gebruiken.
  • @ratchetfreak Bedankt voor de verduidelijking. Het zou leuk zijn om het antwoord met die wijsheid uit te breiden.

Antwoord

Extra ontwerpidee voor # 3

Geef indien mogelijk ook de maximale grootte op die nodig is voor mytype in hetzelfde .h-bestand als mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256 

Nu kan de gebruiker dienovereenkomstig coderen.

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

Order

De grootte van arrays maakt VLA-types mogelijk.

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

Niet zo belangrijk met enkele dimensie, maar wel bruikbaar met 2 of meer.

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

Ik herinner me dat lezen met de grootte eerst een voorkeurstaal is in de volgende C. Moet die referentie vinden …

Antwoord

Als aanvulling op het uitstekende antwoord van @ratchetfreak, zou ik erop willen wijzen dat alternatief # 3 een vergelijkbaar paradigma / patroon volgt als standaard C-bibliotheekfuncties.

Bijvoorbeeld strncpy.

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

Het volgen van hetzelfde paradigma zou helpen om de cognitieve belasting voor nieuwe ontwikkelaars (of zelfs je toekomstige zelf) te verminderen wanneer ze je functie moeten gebruiken.

Het enige verschil met wat je in je bericht hebt, is dat de destination argument in de C-bibliotheken wordt meestal als eerste vermeld in de lijst met argumenten.Dus:

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

Antwoord

Ik “echo @ratchet_freak in de meeste gevallen (misschien met een kleine aanpassing voor sizeof buffer boven 128) maar ik wil hier binnen springen met een rare reactie. Wat dacht je ervan om raar te zijn? Waarom niet, afgezien van de problemen om rare blikken van onze collegas te krijgen en extra overtuigend te moeten zijn? En ik bied dit aan:

// 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; } 

En voorbeeldgebruik:

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); } 

Als u het op deze manier deed, kunt u uw allocator zeer efficiënt (efficiënter dan malloc zowel in termen van kosten voor toewijzing / ongedaan maken van toewijzing als verbeterde referentielocatie voor geheugentoegang). Het kan een arena-allocator zijn die alleen inhoudt dat een aanwijzer wordt verhoogd in veel voorkomende gevallen voor toewijzingsverzoeken en geheugen op een opeenvolgende manier samenvoegen van grote aaneengesloten blokken (waarbij het eerste blok niet eens een hoop al locatie – het kan op de stapel worden toegewezen). Het vereenvoudigt de afhandeling van fouten. Ook, en dit is misschien het meest discutabel, maar ik denk dat het praktisch vrij duidelijk is in termen van hoe het de beller duidelijk maakt dat het geheugen gaat toewijzen aan de allocator die je doorgeeft, wat expliciet vrijgeven vereist (allocator_purge in dit geval) zonder dergelijk gedrag in elke mogelijke functie te moeten documenteren als je deze stijl consequent gebruikt. De acceptatie van de allocator-parameter maakt het hopelijk vrij duidelijk.

Ik weet het niet. Ik krijg hier tegenargumenten zoals het implementeren van de eenvoudigst mogelijke arena-allocator die mogelijk is (gebruik gewoon maximale uitlijning voor alle verzoeken) en ermee omgaan is te veel werk. Mijn botte gedachte is als, wat zijn wij, Python-programmeurs? Kunnen net zo goed Python gebruiken als dat zo is. Gebruik Python als deze details er niet toe doen. Ik meen het serieus. Ik heb veel C-programmeercollegas gehad die hoogstwaarschijnlijk niet alleen correctere code zouden schrijven, maar mogelijk zelfs efficiënter met Python, omdat ze dingen negeren als referentielocatie terwijl ze struikelen over bugs die ze links en rechts creëren. Ik zie niet wat er is dat zo eng is aan een of andere eenvoudige arena-allocator hier als we C-programmeurs zijn die zich bezighouden met zaken als datalocatie en optimale instructieselectie, en dit is aantoonbaar veel minder om over na te denken dan in ieder geval de soorten interfaces die vereisen bellers om expliciet elk afzonderlijk ding vrij te geven dat de interface kan retourneren. Het biedt bulk deallocation boven individuele deallocation van een soort dat meer foutgevoelig is. Een goede C-programmeur daagt loopy calls uit naar malloc zoals ik het zie, vooral wanneer het verschijnt als een hotspot in hun profiler. Vanuit mijn standpunt moet er meer ” oomph ” zijn voor een grondgedachte om nog steeds C-programmeur te zijn in 2020, en we kunnen ” Ga dingen als geheugenallocators niet meer uit de weg.

En dit heeft niet de randgevallen van het toewijzen van een buffer met een vaste grootte waar we bijvoorbeeld 256 bytes toewijzen en de resulterende string groter is. Zelfs als onze functies bufferoverschrijdingen vermijden (zoals bij sprintf_s), is er meer code om correct te herstellen van dergelijke fouten die nodig zijn, die we kunnen weglaten met het allocator-geval, omdat dit niet het geval is. “hebben die randgevallen niet. We hoeven dergelijke gevallen niet te behandelen in het allocator-geval, tenzij we echt de fysieke adresruimte van onze hardware uitputten (die de bovenstaande code afhandelt, maar het heeft niet te maken met ” geen vooraf toegewezen buffer ” apart van ” geen geheugen meer “).

Antwoord

Naast het feit dat wat u van plan bent te doen een slechte code geur, alternatief 3 klinkt mij het beste in de oren. Ik denk ook dat je net als @ gnasher729 de verkeerde taal gebruikt.

Reacties

Antwoord

Om eerlijk te zijn, wil je misschien overschakelen naar een andere taal waar het retourneren van een string geen complexe, arbeidsintensieve en foutgevoelige bewerking is.

U kunt C ++ of Objective-C overwegen, waar u 99% van uw code ongewijzigd kunt laten.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *