Le funzioni di una libreria C dovrebbero sempre prevedere una lunghezza di stringa?

14

Attualmente sto lavorando su una libreria scritta in C. Molte funzioni di questa libreria prevedono una stringa come char* o const char* nei loro argomenti. Ho iniziato con quelle funzioni che aspettavano sempre la lunghezza della stringa come size_t in modo che non fosse richiesta la terminazione nulla. Tuttavia, durante la scrittura di test, questo ha comportato un uso frequente di strlen() , in questo modo:

const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));

Affidarsi che l'utente passi stringhe correttamente terminate porterebbe a codice meno sicuro, ma più conciso e (a mio parere) leggibile:

libFunction("I hope there's a null-terminator there!");

Quindi, qual è la pratica ragionevole qui? Rendi l'API più complicata da utilizzare, ma costringi l'utente a pensare al proprio input o a documentare il requisito per una stringa con terminazione null e fidarsi del chiamante?

    
posta Benjamin Kloster 12.06.2012 - 20:42
fonte

7 risposte

4

Sicuramente e in tutta la sua lunghezza . La libreria C standard è infamemente rotta in questo modo, il che non ha causato alcuna sofferenza nel gestire i buffer overflow. Questo approccio è al centro di tanto odio e angoscia che i compilatori moderni in realtà avvertiranno, lamenteranno e si lamenteranno quando usano queste funzioni di libreria standard di questo tipo.

È così brutto, che se ti capita di imbatterti in un'intervista - e il tuo intervistatore tecnico sembra che abbia qualche anno di esperienza - il puro zelotro può finire il lavoro - puoi davvero andare molto più avanti se puoi citare il precedente di shooting qualcuno che implementa le API alla ricerca del terminatore di stringa C.

Lasciando da parte l'emozione di tutto questo, c'è molto che può andare storto con quello NULL alla fine della tua stringa, sia nella lettura che nella manipolazione - in più è in diretta violazione dei concetti di design moderni come la difesa- approfondito (non necessariamente applicato alla sicurezza, ma alla progettazione dell'API). Esempi di API C che portano la lunghezza abbondano - es. l'API di Windows.

In effetti, questo problema è stato risolto negli anni '90, il consenso emergente di oggi è che non dovresti nemmeno toccare le tue stringhe .

Modifica successiva : questo è piuttosto un dibattito dal vivo, quindi aggiungerò che fidarsi di tutti sotto e sopra di te per essere gentili e utilizzare la libreria str * funzioni è OK, finché non vedi cose classiche come output = malloc(strlen(input)); strcpy(output, input); o while(*src) { *dest=transform(*src); dest++; src++; } . Posso quasi sentire il Lacrimosa di Mozart sullo sfondo.

    
risposta data 12.06.2012 - 21:06
fonte
17

In C, l'idioma è che le stringhe di caratteri sono NUL-terminate, quindi ha senso attenersi alla pratica comune - in realtà è relativamente improbabile che gli utenti della libreria abbiano stringhe non NUL-terminate (poiché queste hanno bisogno di extra lavorare per stampare usando printf e utilizzare in altri contesti). L'uso di qualsiasi altro tipo di corda è innaturale e probabilmente relativamente raro.

Inoltre, date le circostanze, il tuo test sembra un po 'strano per me, dal momento che per funzionare correttamente (usando strlen), stai assumendo una stringa terminata NUL in primo luogo. Dovresti provare il caso di stringhe non terminate da NUL se vuoi che la tua libreria lavori con loro.

    
risposta data 12.06.2012 - 20:53
fonte
10

La tua argomentazione sulla "sicurezza" in realtà non vale. Se non ti fidi che l'utente ti dia una stringa con terminazione null quando è quello che hai documentato (e qual è la "norma" per la C semplice), non puoi davvero fidarti della lunghezza che ti danno (cosa che faranno probabilmente ottieni usando strlen proprio come stai facendo se non ce l'hanno a portata di mano, e che fallirà se la "stringa" non fosse una stringa in primo luogo).

Ci sono validi motivi per richiedere una lunghezza: se vuoi che le tue funzioni lavorino su sottostringhe, è probabilmente molto più facile (ed efficiente) passare una lunghezza piuttosto che far eseguire alla copia una magia di copiatura avanti e indietro per ottenere il byte nullo nel posto giusto (e rischio gli errori "off-by-one" lungo il cammino).
Essere in grado di gestire le codifiche in cui i byte null non sono terminazioni o essere in grado di gestire stringhe che hanno valori null incorporati (di proposito) può essere utile in alcune circostanze (dipende da cosa fanno esattamente le funzioni).
Essere anche in grado di gestire dati non terminati da null (array a lunghezza fissa) è utile.
In breve: dipende da cosa stai facendo nella tua libreria e dal tipo di dati che ti aspetti che i tuoi utenti possano gestire.

C'è anche un aspetto prestazionale in questo. Se la tua funzione ha bisogno di conoscere in anticipo la lunghezza della stringa, e tu ritieni che i tuoi utenti conoscano almeno di solito tali informazioni, passandole sopra (piuttosto che calcolandole) potresti radere alcuni cicli.

Ma se la libreria si aspetta ordinarie stringhe di testo ASCII semplice, e non si dispone di straziante vincoli di prestazioni e una buona comprensione di come gli utenti interagiranno con la libreria, l'aggiunta di un parametro di lunghezza non suona come una buona idea . Se la stringa non è terminata correttamente, è probabile che il parametro length sia altrettanto fasullo. Non penso che ne guadagnerai molto.

    
risposta data 12.06.2012 - 21:06
fonte
2

No. Le stringhe sono sempre terminate da null per definizione, la lunghezza della stringa è ridondante.

I dati dei caratteri non terminati da null non dovrebbero mai essere chiamati "string". Elaborandolo (e lanciando lunghezze) dovrebbe di solito essere incapsulato all'interno di una libreria e non parte dell'API. Richiedere la lunghezza come parametro solo per evitare le chiamate a strlen () è probabile Ottimizzazione prematura.

Affidarsi al chiamante di una funzione API non è non sicuro ; comportamento non definito è perfettamente a posto se non sono soddisfatte le condizioni preliminari documentate.

Naturalmente, un'API ben progettata non dovrebbe contenere problemi e dovrebbe renderla facile da usare correttamente. E questo significa che dovrebbe essere il più semplice e diretto possibile, evitando ridondanze e seguendo le convenzioni della lingua.

    
risposta data 09.05.2015 - 00:55
fonte
1

Dovresti sempre mantenere la tua lunghezza. Per uno, i tuoi utenti potrebbero desiderare di contenere NULL in loro. E in secondo luogo, non dimenticare che strlen è O (N) e richiede di toccare l'intera stringa ciao ciao. In terzo luogo, semplifica il passaggio dei sottoinsiemi, ad esempio, potrebbero fornire meno della lunghezza effettiva.

    
risposta data 12.06.2012 - 21:11
fonte
1

Devi distinguere tra il passaggio di una stringa e il passaggio di un buffer .

In C, le stringhe sono tradizionalmente NUL-terminate. È del tutto ragionevole aspettarsi questo. Pertanto di solito non c'è bisogno di passare la lunghezza della corda; può essere calcolato con strlen se necessario.

Quando si passa attorno a un buffer , specialmente uno su cui è scritto, allora si deve assolutamente passare lungo la dimensione del buffer. Per un buffer di destinazione, ciò consente al chiamato di assicurarsi che non superi il buffer. Per un buffer di input, consente al destinatario di evitare di leggere oltre la fine, soprattutto se il buffer di input contiene dati arbitrari provenienti da una fonte non attendibile.

C'è forse un po 'di confusione perché sia le stringhe che i buffer potrebbero essere char* e poiché molte funzioni stringa generano nuove stringhe scrivendo sui buffer di destinazione. Alcune persone concludono che le funzioni di stringa dovrebbero prendere lunghezze di stringa. Tuttavia, questa è una conclusione imprecisa. La pratica di includere una dimensione con un buffer (se quel buffer può essere usato per stringhe, matrici di interi, strutture, qualunque cosa) è un mantra più utile e più generale.

(Nel caso di leggere una stringa da una fonte non sicura (ad esempio un socket di rete), è importante fornire una lunghezza poiché l'input potrebbe non essere terminato NUL. Tuttavia , dovresti non considera l'input come una stringa. Dovresti trattarlo come un buffer di dati arbitrario che potrebbe contenere una stringa (ma non lo sai fino a quando non lo convalidi effettivamente) , quindi questo segue ancora il principio che i buffer dovrebbero avere dimensioni associate e che le stringhe non ne hanno bisogno.)

    
risposta data 22.03.2017 - 11:40
fonte
0

Se le funzioni sono principalmente utilizzate con stringhe letterali, il dolore di gestire lunghezze esplicite può essere ridotto al minimo definendo alcune macro. Ad esempio, data una funzione API:

void use_string(char *string, int length);

si potrebbe definire una macro:

#define use_strlit(x) use_string(x, sizeof ("" x "")-1)

e quindi richiamarlo come mostrato in:

void test(void)
{
  use_strlit("Hello");
}

Anche se potrebbe essere possibile inventare cose "creative" per superarlo macro che compilerà ma non funzionerà, l'uso di "" su entrambi lato della stringa all'interno della valutazione di "sizeof" dovrebbe catturare tentativi accidentali di utilizzare puntatori di caratteri diversi dai letterali stringa decomposti [in assenza di quei "" , un tentativo di passare un puntatore di carattere darebbe erroneamente la lunghezza come dimensione di un puntatore, meno uno.

Un approccio alternativo in C99 sarebbe definire un tipo di struttura "puntatore e lunghezza" e definire una macro che converta un letterale di stringa in un letterale composto di quel tipo di struttura. Ad esempio:

struct lstring { char const *ptr; int length; };
#define as_lstring(x) \
  (( struct lstring const) {x, sizeof("" x "")-1})

Si noti che se si utilizza un tale approccio, si dovrebbero passare tali strutture in base al valore piuttosto che passare attorno ai propri indirizzi. Altrimenti qualcosa come:

struct lstring *p;
if (foo)
{
  p = &as_lstring("Hello");
}
else
{
  p = &as_lstring("Goodbye!");
}
use_lstring(p);

può fallire poiché la vita dei letterali composti termina alle estremità delle loro dichiarazioni allegate.

    
risposta data 21.03.2017 - 22:50
fonte

Leggi altre domande sui tag