Funzioni che restituiscono stringhe, buon stile?


11

Nei miei programmi in C ho spesso bisogno di un modo per creare una rappresentazione in stringa dei miei ADT. Anche se non ho bisogno di stampare la stringa sullo schermo in alcun modo, è corretto avere tale metodo per il debug. Quindi questo tipo di funzione si presenta spesso.

char * mytype_to_string( const mytype_t *t );

In realtà mi rendo conto di avere (almeno) tre opzioni qui per gestire la memoria per la restituzione della stringa.

Alternativa 1: memorizzazione della stringa di ritorno in un array di caratteri statici nella funzione. Non ho bisogno di molte riflessioni, tranne per il fatto che la stringa viene sovrascritta ad ogni chiamata. Quale potrebbe essere un problema in alcune occasioni.

Alternativa 2: alloca la stringa sull'heap con malloc all'interno della funzione. Davvero pulito da quando non avrò più bisogno di pensare alle dimensioni di un buffer o alla sovrascrittura. Tuttavia, devo ricordare di liberare () la stringa una volta terminato, e quindi devo anche assegnare a una variabile temporanea tale da poter liberare. e quindi l'allocazione dell'heap è molto più lenta dell'allocazione dello stack, quindi sii un collo di bottiglia se questo viene ripetuto in un ciclo.

Alternativa 3: passare il puntatore a un buffer e consentire al chiamante di allocare quel buffer. Piace:

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

Questo porta più impegno al chiamante. Noto anche che questa alternativa mi offre un'altra opzione sull'ordine degli argomenti. Quale argomento dovrei avere il primo e l'ultimo? (in realtà sei possibilità)

Quindi, quale dovrei preferire? Qualche perchè? Esiste una sorta di standard non scritto tra gli sviluppatori C?


3
Solo una nota osservativa, la maggior parte dei sistemi operativi utilizza l'opzione 3: il chiamante alloca comunque il buffer; indica il puntatore e la capacità del buffer; callee riempie il buffer e restituisce anche la lunghezza effettiva della stringa se il buffer è insufficiente. Esempio: sysctlbynamein OS X e iOS
rwong

Risposte:


11

I metodi che ho visto di più sono 2 e 3.

Il buffer fornito dall'utente è in realtà abbastanza semplice da usare:

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

Sebbene la maggior parte delle implementazioni restituisca la quantità di buffer utilizzata.

L'opzione 2 sarà più lenta ed è pericolosa quando si utilizzano librerie collegate dinamicamente in cui possono utilizzare runtime diversi (e heap diversi). Quindi non puoi liberare ciò che è stato malloced in un'altra libreria. Ciò richiede quindi una free_string(char*)funzione per gestirlo.


Grazie! Penso che mi piaccia anche l'alternativa 3. Tuttavia, voglio essere in grado di fare cose del genere: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));e quindi non mi piacerebbe restituire la lunghezza utilizzata, ma piuttosto il puntatore alla stringa. Il commento dinamico della biblioteca è davvero importante.
Øystein Schønning-Johansen

Non dovrebbe essere sizeof(buffer) - 1per soddisfare il \0terminatore?
Michael-O,

1
@ Michael-O no il termine null è incluso nella dimensione del buffer, il che significa che la stringa massima che può essere inserita è 1 in meno della dimensione passata. Questo è lo schema che la stringa di sicurezza funziona come una libreria standard come l' snprintfuso.
maniaco del cricchetto,

@ratchetfreak Grazie per il chiarimento. Sarebbe bello estendere la risposta con quella saggezza.
Michael-O,

0

Idea di design aggiuntiva per il n. 3

Se possibile, fornire anche la dimensione massima necessaria per mytypelo stesso file .h di mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256

Ora l'utente può programmare di conseguenza.

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

Ordine

La dimensione delle matrici, quando è la prima, consente i tipi VLA.

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

Non così importante con una singola dimensione, ma utile con 2 o più.

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

Ricordo che leggere per primo la dimensione è un linguaggio preferito nel prossimo C. Necessario trovare quel riferimento ....


0

Come aggiunta all'eccellente risposta di @ ratchetfreak, vorrei sottolineare che l'alternativa n. 3 segue un paradigma / modello simile alle funzioni della libreria C standard.

Ad esempio strncpy,.

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

Seguire lo stesso paradigma aiuterebbe a ridurre il carico cognitivo per i nuovi sviluppatori (o anche per il tuo sé futuro) quando devono usare la tua funzione.

L'unica differenza con ciò che hai nel tuo post sarebbe che l' destinationargomento nelle librerie C tende ad essere elencato per primo nell'elenco degli argomenti. Così:

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

-2

Oltre al fatto che quello che stai proponendo di fare è un cattivo odore di codice, l'alternativa 3 suona meglio per me. Penso anche come @ gnasher729 che stai usando la lingua sbagliata.


Cosa consideri esattamente un odore di codice? Per favore, elabora.
Hulk,

-3

Ad essere onesti, potresti voler passare a una lingua diversa in cui la restituzione di una stringa non è un'operazione complessa, impegnativa e soggetta a errori.

Potresti considerare C ++ o Objective-C dove potresti lasciare invariato il 99% del tuo codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.