Funksjoner som returnerer strenger, god stil?

I C-programmene mine trenger jeg ofte en måte å lage en strengrepresentasjon av ADT-ene mine. Selv om jeg ikke trenger å skrive ut strengen til skjermen på noen måte, er det pent å ha en slik metode for feilsøking. Så denne typen funksjon kommer ofte opp.

char * mytype_to_string( const mytype_t *t ); 

Jeg er faktisk klar over at jeg har (i det minste) tre muligheter her for å håndtere minnet for at strengen skal returneres.

Alternativ 1: Lagring av returstrengen i en statisk char-array i funksjonen . Jeg trenger ikke å tenke mye, bortsett fra at strengen blir overskrevet ved hver samtale. Noe som kan være et problem i noen anledninger.

Alternativ 2: Tildel strengen på dyngen med malloc inne i funksjonen. Virkelig pent siden jeg da ikke trenger å tenke på størrelsen på en buffer eller overskrivingen. Imidlertid må jeg huske å frigjøre () strengen når du er ferdig, og da må jeg også tilordne en midlertidig variabel slik at Jeg kan frigjøre. Og så er haugetildeling veldig mye tregere enn stabeltildeling, vær derfor en flaskehals hvis dette gjentas i en løkke.

Alternativ 3: Gi pekeren til en buffer, og la den som ringer tildele som buffer. Som:

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

Dette gir mer anstrengelse for innringeren. Jeg merker også at dette alternativet gir meg et annet alternativ i rekkefølgen av argumentene. Hvilket argument skal jeg ha første og siste? (Faktisk seks muligheter)

Så, hva skal jeg foretrekke? Noen hvorfor? Er det en slags uskrevet standard blant C-utviklere?

Kommentarer

  • Bare en observasjonsnotat, de fleste operativsystemer bruker alternativ 3 – innringer tildeler buffer uansett; forteller bufferpekeren og kapasitet, callee fyller buff er og returnerer også faktisk lengde på strengen hvis bufferen er utilstrekkelig. Eksempel: sysctlbyname i OS X og iOS

Svar

Metodene jeg har sett mest er 2 og 3.

Den brukerforsynte bufferen er faktisk ganske enkel å bruke:

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

Selv om de fleste implementeringer returnerer mengden buffer som brukes.

Alternativ 2 vil være tregere og er farlig når du bruker dynamisk koblede biblioteker der de kan bruke forskjellige driftstider (og forskjellige dynger). Så du kan ikke frigjøre det som har blitt mallokert i et annet bibliotek. Dette krever da en free_string(char*) -funksjon for å håndtere den.

Kommentarer

  • Takk! Jeg tror jeg liker alternativ 3 best også. Imidlertid vil jeg være i stand til å gjøre ting som: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf)); og derfor vil jeg ikke ‘ ikke liker å returnere lengden som brukes, men heller pekeren til strengen. Den dynamiske bibliotekkommentaren er veldig viktig.
  • Skal ‘ t dette være sizeof(buffer) - 1 for å tilfredsstille \0 terminator?
  • @ Michael-O nei nullbegrepet er inkludert i bufferstørrelsen, noe som betyr at maksstrengen som kan legges i er 1 mindre enn den passerte størrelsen. Dette er mønsteret som den sikre strengen fungerer i standardbiblioteket som snprintf bruk.
  • @ratchetfreak Takk for avklaring. Ville være fint å utvide svaret med den visdommen.

Svar

Ekstra designidee for # 3

Når det er mulig, oppgi også den maksimale størrelsen som trengs for mytype i samme .h-fil som mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256 

Nå kan brukeren kode tilsvarende.

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

Bestill

Størrelsen på matriser, når først, tillater VLA-typer.

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

Ikke så viktig med en enkelt dimensjon, men likevel nyttig med 2 eller flere.

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

Jeg husker at det å ha lest størrelsen først er et foretrukket uttrykk i neste C. Trenger du å finne den referansen ….

Svar

Som et tillegg til @ratchetfreak sitt utmerkede svar, vil jeg påpeke at alternativ nr. 3 følger et lignende paradigme / mønster som standard C-biblioteksfunksjoner.

For eksempel strncpy.

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

Å følge det samme paradigmet vil hjelpe for å redusere den kognitive belastningen for nye utviklere (eller til og med ditt fremtidige selv) når de trenger å bruke funksjonen din.

Den eneste forskjellen med det du har i innlegget ditt, ville være at destination argument i C-bibliotekene har en tendens til å bli oppført først i argumentlisten.Så:

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

Svar

Jeg ekko @ratchet_freak i de fleste tilfeller (kanskje med en liten tweak for sizeof buffer over 128) men jeg vil hoppe inn her med et merkelig svar. Hva med å være rar? Hvorfor ikke, i tillegg til problemene med å få rare blikk fra kollegaene våre og måtte være ekstra overbevisende? Og jeg tilbyr dette:

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

Og eksempelbruk:

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

Hvis du gjorde det på denne måten, kan du gjøre tildeleren din veldig effektiv (mer effektiv enn malloc både når det gjelder allokering / deallokasjonskostnader og også forbedret referanselokalitet for minnetilgang. og samle minne på en sekvensiell måte fra store sammenhengende blokker (med den første blokken som ikke engang krever en haug al plassering – den kan tildeles på bunken). Det forenkler feilhåndtering. Også, og dette kan være mest diskutabelt, men jeg tror det er praktisk talt åpenbart når det gjelder hvordan det gjør det klart for innringeren at det kommer til å tildele minne til tildeleren du sender inn, og krever eksplisitt frigjøring (allocator_purge i dette tilfellet) uten å måtte dokumentere slik oppførsel i hver eneste mulige funksjon hvis du bruker denne stilen konsekvent. Aksepteringen av tildelingsparameteren gjør det forhåpentligvis ganske opplagt.

Jeg vet ikke. Jeg får motargumenter her som å implementere en enklest mulig arenaallokering mulig (bare bruk maksimal justering for alle forespørsler) og å takle det er for mye arbeid. Min stumme tanke er som, hva er vi, Python-programmerere? Kan like godt bruke Python i så fall. Bruk Python hvis disse detaljene ikke betyr noe. Jeg er seriøs. Jeg har hatt mange C-programmeringskollegaer som med stor sannsynlighet ville skrive ikke bare mer korrekt kode, men muligens enda mer effektiv med Python, siden de ignorerer ting som referanselokalitet mens de snubler over feil som de skaper til venstre og høyre. Jeg gjør ikke » t se hva det er som er så skummelt med noen enkle arenaallokatorer her hvis vi er C-programmerere som er opptatt av ting som datalokalitet og optimalt instruksjonsvalg, og dette er uten tvil mye mindre å tenke på enn i det minste de typer grensesnitt som krever innringere for å eksplisitt frigjøre hver enkelt ting som grensesnittet kan returnere. En skikkelig C-programmerer utfordrer loopy call til malloc slik jeg ser det, spesielt når det ser ut som et hotspot i profilen deres. Fra mitt ståsted må det være mer » oomph » til en begrunnelse for fortsatt å være C-programmerer i 2020, og vi kan » t viker unna ting som minnefordeler lenger.

Og dette har ikke kant tilfeller av å tildele en buffer i fast størrelse der vi tildeler, for eksempel, 256 byte og den resulterende strengen er større. Selv om funksjonene våre unngår bufferoverskridelser (som med sprintf_s), er det mer kode for å gjenopprette riktig fra slike feil som kreves, som vi kan utelate med tildelersaken, siden den ikke «t har disse kanttilfellene. Vi trenger ikke å håndtere slike saker i tildelingssaken, med mindre vi virkelig bruker det fysiske adresseringsområdet til maskinvaren vår (som koden ovenfor håndterer, men den trenger ikke å håndtere » ut av forhåndsallokert buffer » separat fra » uten minne «).

Svar

I tillegg til at det du foreslår å gjøre er dårlig kodelukt, alternativ 3 høres best ut for meg. Jeg tenker også som @ gnasher729 at du bruker feil språk.

Kommentarer

Svar

For å være ærlig vil du kanskje bytte til et annet språk der retur av en streng ikke er en kompleks, arbeidskrevende og feilutsatt handling.

Du kan vurdere C ++ eller Objective-C der du kan la 99% av koden være uendret.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *