2013-06-12 1 views
8

Ho una funzione di stringa che accetta un puntatore a una stringa di origine e restituisce un puntatore a una stringa di destinazione. Questa funzione attualmente funziona, ma sono preoccupato di non seguire la procedura migliore per il ripristino di malloc, realloc e free.best practice per la restituzione di una stringa di lunghezza variabile in c

La cosa diversa dalla mia funzione è che la lunghezza della stringa di destinazione non è la stessa della stringa di origine, quindi è necessario richiamare realloc() all'interno della mia funzione. So per guardare la documentazione ...

http://www.cplusplus.com/reference/cstdlib/realloc/

che l'indirizzo di memoria potrebbe cambiare dopo la realloc. Questo significa che non posso "passare per riferimento" come un programmatore C potrebbe per altre funzioni, devo restituire il nuovo puntatore.

Quindi il prototipo per la mia funzione è:

//decode a uri encoded string 
char *net_uri_to_text(char *); 

non mi piace il modo in cui lo sto facendo perché devo liberare il puntatore dopo l'esecuzione della funzione:

char * chr_output = net_uri_to_text("testing123%5a%5b%5cabc"); 
printf("%s\n", chr_output); //testing123Z[\abc 
free(chr_output); 

Ciò significa che malloc() e realloc() sono chiamati all'interno della mia funzione e free() è chiamato al di fuori della mia funzione.

Ho un background in linguaggi di alto livello, (perl, plpgsql, bash) quindi il mio istinto è corretta l'incapsulamento di queste cose, ma che potrebbe non essere la migliore prassi in C.

La domanda: E 'la mia buone pratiche, o c'è un modo migliore che dovrei seguire?

piena esempio

compila e funziona con due avvertenze su argomenti argc e argv non utilizzati, è possibile ignorare quei due avvertimenti.

example.c:

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 

char *net_uri_to_text(char *); 

int main(int argc, char ** argv) { 
    char * chr_input = "testing123%5a%5b%5cabc"; 
    char * chr_output = net_uri_to_text(chr_input); 
    printf("%s\n", chr_output); 
    free(chr_output); 
    return 0; 
} 

//decodes uri-encoded string 
//send pointer to source string 
//return pointer to destination string 
//WARNING!! YOU MUST USE free(chr_result) AFTER YOU'RE DONE WITH IT OR YOU WILL GET A MEMORY LEAK! 
char *net_uri_to_text(char * chr_input) { 
    //define variables 
    int int_length = strlen(chr_input); 
    int int_new_length = int_length; 
    char * chr_output = malloc(int_length); 
    char * chr_output_working = chr_output; 
    char * chr_input_working = chr_input; 
    int int_output_working = 0; 
    unsigned int uint_hex_working; 
    //while not a null byte 
    while(*chr_input_working != '\0') { 
    //if % 
    if (*chr_input_working == *"%") { 
     //then put correct char in 
     sscanf(chr_input_working + 1, "%02x", &uint_hex_working); 
     *chr_output_working = (char)uint_hex_working; 
     //printf("special char:%c, %c, %d<\n", *chr_output_working, (char)uint_hex_working, uint_hex_working); 
     //realloc 
     chr_input_working++; 
     chr_input_working++; 
     int_new_length -= 2; 
     chr_output = realloc(chr_output, int_new_length); 
     //output working must be the new pointer plys how many chars we've done 
     chr_output_working = chr_output + int_output_working; 
    } else { 
     //put char in 
     *chr_output_working = *chr_input_working; 
    } 
    //increment pointers and number of chars in output working 
    chr_input_working++; 
    chr_output_working++; 
    int_output_working++; 
    } 
    //last null byte 
    *chr_output_working = '\0'; 
    return chr_output; 
} 
+8

Mi piace la parte '*"% "'. : D –

+0

Grazie, ho appena scoperto che ''%'' funziona. :) – Michael

+0

Se si avvolge il codice tra gli apici inversi ('), verranno formattati come codice –

risposta

8

E 'perfettamente ok per tornare malloc 'D buffer di funzioni in C, fino a quando si documentare il fatto che essi fanno. Molte librerie lo fanno, anche se non funziona nella libreria standard.

Se è possibile calcolare (un limite superiore non troppo pessimistico) il numero di caratteri che è necessario scrivere sul buffer a basso costo, è possibile offrire una funzione che lo faccia e consentire all'utente di chiamarlo.

È anche possibile, ma molto meno conveniente, accettare un buffer da compilare; Ho visto un paio di librerie bel che farlo in questo modo:

/* 
* Decodes uri-encoded string encoded into buf of length len (including NUL). 
* Returns the number of characters written. If that number is less than len, 
* nothing is written and you should try again with a larger buffer. 
*/ 
size_t net_uri_to_text(char const *encoded, char *buf, size_t len) 
{ 
    size_t space_needed = 0; 

    while (decoding_needs_to_be_done()) { 
     // decode characters, but only write them to buf 
     // if it wouldn't overflow; 
     // increment space_needed regardless 
    } 
    return space_needed; 
} 

Ora il chiamante è responsabile per l'assegnazione, e avrebbe fatto qualcosa di simile

size_t len = SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH; 
char *result = xmalloc(len); 

len = net_uri_to_text(input, result, len); 
if (len > SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH) { 
    // try again 
    result = xrealloc(input, result, len); 
} 

(Qui, xmalloc e xrealloc sono " sicuro "allocando le funzioni che ho elaborato per saltare i controlli NULL.)

+1

+1 per menzionare per documentare l'allocazione dinastica! -) – alk

+2

Due aspetti positivi riguardo l'aspettativa che il chiamante passi un buffer (+ dimensione di esso): 1. Il chiamante può conoscere la lunghezza massima in anticipo, quindi può decidere di usare un array allocato nello stack. 2.) La proprietà della memoria non viene trasferita, ovvero sia l'allocazione che la deallocazione avvengono sul sito del chiamante - questo è fondamentale su Windows nel caso in cui il chiamante si trovi in ​​una DLL diversa dal chiamato (è vietato allocare memoria in una DLL e rilasciarlo in un altro su Windows, dal momento che il gestore della memoria è per modulo, non per processo). –

+0

@FrerichRaabe: non lo sapevo. So che permette di utilizzare gestori di memoria personalizzati invece di 'malloc', che è utile anche su Unix. –

2

È perfettamente OK restituire recentemente i valori ed (e possibilmente internamente realloc) dalle funzioni, è sufficiente documentare che lo stanno facendo (come fai qui).

Altri elementi evidenti:

  • Invece di int int_length si potrebbe desiderare di utilizzare size_t. Questo è "un tipo senza segno" (di solito unsigned int o unsigned long) che è il tipo appropriato per lunghezze di stringhe e argomenti a malloc.
  • È necessario assegnare inizialmente n + 1 byte, dove n è la lunghezza della stringa, poiché strlen non include il byte di terminazione 0.
  • Si consiglia di verificare per malloc in mancanza (restituendo NULL). Se la funzione supera l'errore, documentarlo nel commento della descrizione della funzione.
  • sscanf è piuttosto pesante per la conversione dei due byte esadecimali. Non errato, ad eccezione del fatto che non stai controllando se la conversione ha esito positivo (e se l'input è malformato? Puoi ovviamente decidere che questo è il problema del chiamante, ma in generale potresti volerlo gestire). È possibile utilizzare isxdigit da <ctype.h> per verificare le cifre esadecimali e/o strtoul per eseguire la conversione.
  • Invece di eseguire uno realloc per ogni conversione %, è consigliabile eseguire un "ridimensionamento realloc" finale, se opportuno. Si noti che se si allocano (diciamo) 50 byte per una stringa e si trova che richiede solo 49 incluso il byte 0 finale, potrebbe non valere la pena fare un realloc dopo tutto.
2

Il fatto è che C è abbastanza basso da costringere il programmatore a ottenere la gestione della memoria giusta. In particolare, non c'è niente di sbagliato nel restituire una stringa malloc(). È un idioma comune restituire obietti mallocated e avere il chiamante free() loro.

E comunque, se non ti piace questo approccio, puoi sempre prendere un puntatore alla stringa e modificarlo dall'interno della funzione (dopo l'ultimo utilizzo, sarà comunque necessario essere free() d).

Una cosa, tuttavia, che non ritengo sia necessario è il restringimento esplicito della stringa. Se la nuova stringa è più corta di quella vecchia, c'è ovviamente spazio sufficiente nel frammento di memoria della vecchia stringa, quindi non è necessario lo realloc().

(A parte il fatto che si è dimenticato di assegnare un byte in più per il carattere di terminazione NUL, ovviamente ...)

E, come sempre, si può semplicemente restituire un puntatore diverso ogni volta che la funzione è chiamato, e non è nemmeno necessario chiamare lo realloc().

Se si accetta un ultimo consiglio: si consiglia di const -qualificare le stringhe di input, in modo che il chiamante possa assicurarsi di non modificarle. Utilizzando questo approccio, puoi tranquillamente chiamare la funzione su stringhe letterali, per esempio.

Tutto sommato, mi piacerebbe riscrivere la funzione in questo modo:

char *unescape(const char *s) 
{ 
    size_t l = strlen(s); 
    char *p = malloc(l + 1), *r = p; 

    while (*s) { 
     if (*s == '%') { 
      char buf[3] = { s[1], s[2], 0 }; 
      *p++ = strtol(buf, NULL, 16); // yes, I prefer this over scanf() 
      s += 3; 
     } else { 
      *p++ = *s++; 
     } 
    } 

    *p = 0; 
    return r; 
} 

e chiamare i seguenti:

int main() 
{ 
    const char *in = "testing123%5a%5b%5cabc"; 
    char *out = unescape(in); 
    printf("%s\n", out); 
    free(out); 

    return 0; 
} 
+0

Welp, non ho menzionato esplicitamente 'size_t' e' strtol(), e ho anche supposto che 'malloc()' non fallisca mai ... Attenzione! –

0

vorrei affrontare il problema in un modo leggermente diverso. Personalmente, vorrei dividere la tua funzione in due.La prima funzione per calcolare le dimensioni necessarie per malloc. Il secondo scriverebbe la stringa di output sul puntatore specificato (che è stato allocato al di fuori della funzione). Ciò consente di salvare diverse chiamate a realloc e manterrà la complessità lo stesso. Una possibile funzione per trovare la dimensione della nuova stringa è:

int getNewSize (char *string) { 
    char *i = string; 
    int size = 0, percent = 0; 
    for (i, size; *i != '\0'; i++, size++) { 
     if (*i == '%') 
      percent++; 
    } 
    return size - percent * 2; 
} 

Tuttavia, come detto in altre risposte non c'è nessun problema a tornare un buffer malloc'ed finchè si documento!

+0

Nota che se decidi di rifattorizzare in questo modo, puoi avere la tua "funzione per calcolare lo spazio" * anche * verificare che l'URL sia ben formato (non ha cose come% -! Nel mezzo).A volte le persone optano per una sorta di approccio ibrido: verificare, opzionalmente malloc, convertire facoltativamente nel buffer (fornito dall'utente o malloc-ed), restituire una serie di informazioni (tramite 'struct' o puntatori forniti dal chiamante), ecc. – torek

0

In aggiunta a quanto già menzionato negli altri post, è necessario documentare anche il fatto che la stringa viene riallocata. Se il tuo codice viene chiamato con una stringa statica o una stringa allocata con alloca, potresti non riallocarlo.

0

Penso che tu abbia ragione a preoccuparti di dividere mallocs e libera. Di norma, qualunque cosa lo renda, lo possiede e dovrebbe liberarlo.

In questo caso, dove le stringhe sono relativamente piccole, una buona procedura consiste nel rendere il buffer di stringa più grande di qualsiasi stringa possibile che potrebbe contenere. Ad esempio, gli URL hanno un limite di fatto di circa 2000 caratteri, quindi se malloc 10000 caratteri è possibile memorizzare qualsiasi URL possibile.

Un altro trucco consiste nel memorizzare la lunghezza e la capacità della stringa nella parte anteriore, in modo che (int)*mystring == length of string e (int)*(mystring + 4) == capacity di stringa. Pertanto, la stringa stessa inizia solo nell'ottava posizione *(mystring+8). In questo modo è possibile passare un singolo puntatore a una stringa e sapere sempre quanto è lungo e quanta capacità di memoria ha la stringa. È possibile creare macro che generano automaticamente questi offset e creano "codice grazioso".

Il valore dell'utilizzo di buffer in questo modo è che non è necessario effettuare una riallocazione. Il nuovo valore sovrascrive il vecchio valore e si aggiorna la lunghezza all'inizio della stringa.

+1

Penso che la tecnica 'encode size of string in the first fewtes' sia molto sgradevole, dato che è piuttosto rara (non l'ho mai vista in pratica) e il compilatore non può aiutare se ti dimentichi di questo fatto. Quindi se hai una stringa "Ciao", la stampa potrebbe solo ottenere un singolo carattere stampato poiché la maggior parte dei byte dell'int iniziale sono zero. Non è una buona cosa fare il debug. : -/ –

+0

Quando si usa il contenuto della stringa ci si riferisce ad esso come '* (mystring + 8)'. Ad esempio: printf ("% s \ n", * (mystring + 8)); una macro può essere usata se lo si desidera. L'alternativa è invece definire una struttura, ma poi devi gestire i puntatori nidificati. Nella mia esperienza, è più facile usare il metodo che descrivo che usare i puntatori annidati quando si manipolano le stringhe corte. –