2010-10-06 14 views
21

Le funzioni di libreria standard del C strtof e strtod avere i seguenti identificativi:Perché il parametro endptr è in grado di strtof e strtod un puntatore a un puntatore char non const?

float strtof(const char *str, char **endptr); 
double strtod(const char *str, char **endptr); 

Ognuno decompongono la stringa di input, str, in tre parti:

  1. Un primo, possibilmente vuoto, sequenza di spazio vuoto
  2. Una "sequenza soggetto" di caratteri che rappresentano un valore in virgola mobile
  3. Una "sequenza finale" di caratteri che sono unrec individuato (e che non influisce sulla conversione).

Se endptr non è NULL, quindi *endptr è impostato a un puntatore al carattere immediatamente successiva all'ultimo carattere, che faceva parte della conversione (in altre parole, l'inizio della sequenza finale).

Mi chiedo: perché è endptr, quindi, un puntatore a un puntatore non constchar? Non è *endptr un puntatore in una stringa constchar (la stringa di input str)?

+0

È fondamentalmente lo stesso problema di 'strchr' e amici, tranne che qui abbiamo un puntatore out-param piuttosto che un valore di ritorno. –

+0

@Steve: sì, ma è più problematico di 'strchr' perché non è possibile passare un indirizzo del puntatore qualificato' const' a queste funzioni senza un cast esplicito. –

+3

Interessante domanda. Fondamentalmente questo significa che puoi nascondere un cast da 'char const *' a 'char *' dietro le funzioni 'strtoX'. Strano. –

risposta

10

Il motivo è semplicemente l'usabilità. char * può convertire automaticamente in const char *, ma non è possibile convertire automaticamente char ** in const char ** e il tipo effettivo del puntatore (il cui indirizzo viene passato) utilizzato dalla funzione di chiamata è molto più probabile che sia char * rispetto a const char *. Il motivo per cui questa conversione automatica non è possibile è che non esiste un modo ovvio per rimuovere la qualifica const attraverso diversi passaggi, in cui ogni passaggio sembra perfettamente valido e corretto in sé e per sé. Steve Jessop ha fornito un esempio nei commenti:

se si potesse convertire automaticamente char**-const char**, allora si potrebbe fare

char *p; 
char **pp = &p; 
const char** cp = pp; 
*cp = (const char*) "hello"; 
*p = 'j';. 

Per const-sicurezza, una di queste linee deve essere illegale, e poiché le altre sono tutte operazioni perfettamente normali, deve essere cp = pp;

Un approccio migliore sarebbe stata quella di definire queste funzioni di prendere void * al posto di char **. Entrambi char ** e const char ** possono convertire automaticamente in void *. (Il testo colpito era in realtà una pessima idea, non solo impedisce qualsiasi tipo di controllo, ma C in realtà proibisce oggetti di tipo char * e const char * in alias.) In alternativa, queste funzioni avrebbero potuto prendere un argomento ptrdiff_t * o size_t * in cui archiviare lo offset della fine, piuttosto che un puntatore ad esso. Questo è spesso più utile in ogni caso.

Se ti piace il secondo approccio, sentiti libero di scrivere un tale wrapper attorno alle funzioni della libreria standard e chiama il tuo wrapper, in modo da mantenere il resto del codice const -clean e cast-free.

+2

"Sia' char ** 'che' const char ** 'può convertire automaticamente in vuoto *." Vedo quello che stai dicendo, e l'uso di 'void *' consentirebbe all'utente di passare in un 'const char **' senza cast. Ma permetterebbe anche a loro di passare in un intero universo di cose sbagliate senza un cast. Considerati i limiti di C, penso che sia meglio preservare la sicurezza di base dei tipi, anche il costo di perdere la cost-safety. –

+0

Non è semplicemente usabilità, queste funzioni memorizzano il risultato in quel puntatore e non è possibile scrivere in una memoria costante. –

+0

@Vlad: stai confondendo 'const char **' con 'char * const *'. –

4

Usabilità. L'argomento str è contrassegnato come const poiché l'argomento di input non verrà modificato. Se endptr erano const, allora questo avrebbe richiesto al chiamante di non modificare i dati di riferimento da endptr in uscita, ma spesso il chiamante vuole farlo. Ad esempio, mi può essere utile a null-terminare una stringa dopo aver ottenuto il galleggiante fuori di esso:

float StrToFAndTerminate(char *Text) { 
    float Num; 

    Num = strtof(Text, &Text); 
    *Text = '\0'; 
    return Num; 
} 

cosa perfettamente ragionevole vogliono fare, in alcune circostanze. Non funziona se endptr è di tipo const char **.

Idealmente, endptr dovrebbe essere di costante corrispondenza con la costante di ingresso effettiva di str, ma C non fornisce alcun modo per indicarlo tramite la sua sintassi. (è stato lasciato fuori da C#)

+0

Lo stesso effetto potrebbe essere facilmente ottenuto con 'Text [FloatEnd-Text] = '\ 0'' quindi per me questa non è una buona scusa. Il metodo che stai indicando produrrà UB se 'Text' verrebbe dichiarato con' const' e non potresti leggerlo facilmente dalla posizione in cui esegui il compito. –

+0

@Jens: ho aggiornato il codice per riflettere meglio la logica. E sono d'accordo sul problema del comportamento indefinito. La domanda è se la cura è peggiore della malattia ... –

+0

"potrebbe essere facilmente raggiunto" - ma che cosa è l'ottimizzazione prematura per noi (preferendo il puntatore direttamente, invece di fare affidamento sul compilatore per risolvere quell'espressione, e/o l'hardware per essere così veloce non ci interessa), non era l'ottimizzazione prematura per il comitato degli standard C89 quando hanno specificato 'strtod'. Inoltre, questo è un vergognoso idioma da imparare, quindi ci limitiamo a chiederci se è più o meno vergognoso di const-incorrectness ;-) –

Problemi correlati