2009-05-22 4 views
23

Sto eseguendo il porting di codice su Windows e il compilatore Microsoft (Visual C++ 8) mi sta dicendo che strerror() non è sicuro.Perché non posso usare strerror?

Mettendo da parte il fattore di disturbo in tutte le risorse di stringa sicure da Microsoft, posso effettivamente vedere che alcune delle funzioni deprecate sono pericolose. Ma non riesco a capire cosa potrebbe essere sbagliato con strerror(). Prende un codice (int) e restituisce la stringa corrispondente o la stringa vuota se tale codice non è noto.

Dov'è il pericolo?

Esiste una buona alternativa in C?

Esiste una buona alternativa in C++?

[modifica]

Avendo ottenuto alcune buone risposte, e ora comprendere che alcune implementazioni possono essere così pazzo da scrivere in realtà ad un buffer comune condiviso - pericoloso per rientranza all'interno di un singolo thread, figuriamoci tra i thread! - la mia domanda smette di essere "Perché non posso usarla e quali sono le alternative?" a "Esistono alternative decenti e succinte in C e/o C++?"

Grazie in anticipo

+28

Non è possibile utilizzarlo perché Microsoft dice "lo standard ISO C è dannato - non lo lasceremo usare, a meno che non si ignori l'avviso o l'errore con un pragma". Hanno anche bandito memcpy() - che è ridicolo perché gli dici quanti byte copiare e se non riesci a pensarci e sappi che c'è spazio sufficiente nello spazio di destinazione per il numero di byte prima di chiamare memcpy() , non appartieni a un team che scrive codice in C o C++. –

risposta

18

strerror è deprecato, perché non è thread-safe. strerror funziona su un buffer statico interno, che può essere sovrascritto da altri thread concorrenti. È necessario utilizzare una variante sicura denominata strerror_s.

La variante protetta richiede che la dimensione del buffer venga passata alla funzione per verificare che il buffer sia sufficientemente grande prima di scriverlo, aiutando a evitare sovraccarichi del buffer che potrebbero consentire l'esecuzione di codice dannoso.

+3

Er, la domanda era: _Perché_ è considerato pericoloso? Mi hai detto proprio niente. – JamieH

+10

Lui ti ha detto perché? Funziona su un buffer statico interno, cioè condiviso con i thread. Questo è pericoloso. – nsayer

+1

Ah, si. Mie scuse. Ora, la prossima domanda - per il mondo in generale - è: perché diamine qualcuno la implementerebbe in quel modo !? – JamieH

5

Non è possibile fare affidamento sulla stringa restituita da strerror() perché potrebbe cambiare con la chiamata successiva alla funzione. I valori precedentemente restituiti potrebbero diventare obsoleti. Soprattutto negli ambienti multi-thread , non è possibile garantire che la stringa sia valida quando ci si accede.

Immaginate questo:

Thread #1: 
char * error = strerror(1); 
            Thread #2 
            char * error = strerror(2); 
printf(error); 

seconda dell'implementazione di strerror(), questo codice stampa il codice di errore per il codice di errore 2, non per il codice di errore 1.

+6

Con quale meccanismo sarebbe cambiato? Presumibilmente le stringhe sono definite - staticamente - all'interno della implementazione della libreria di runtime C, e quindi non cambieranno mai? O è sbagliato, e possono davvero cambiare su base dinamica? – JamieH

+1

Perché dovresti assumere questo? Sì, potrebbe essere fatto in questo modo, ma potrebbe essere fatto completamente diverso - non lo sappiamo;) .. – beef2k

+3

Il problema principale è che Posix non richiede che strerror() sia thread-safe. Devi invece usare strerror_r(). Su Windows usa strerror_s(). –

1

Anche se io non conosco le ragioni di Microsoft , Noto che lo strerror restituisce un carattere non const *, il che significa che c'è il rischio che qualche Merry Prankster abbia chiamato strerror prima di averlo fatto e modificato il messaggio.

+2

Se è "const char *" lo stesso Merry Prankster potrebbe provare a trasmettere a (char *) e cambiare il codice dopo questo;) .. Dichiarare qualcosa come "const" non significa che non possa essere modificato. Fornisce solo un suggerimento per l'ottimizzazione al compilatore. – beef2k

+3

Concordo sul fatto che, da una prospettiva di pura costanza, questo è vero, ma sospetto fortemente che la mancanza di const sia solo storica, e gli utenti sono obbligati a non alterare il contenuto della stringa nello stesso modo in cui sono con molte di queste parti non costanti, ma dovrebbero essere parti della libreria standard. Se questo è il caso, non riesco ancora a vedere alcun motivo per la deprecazione di strerror(). – JamieH

+1

rendendolo const char * e dicendo che la cosa non deve essere cambiata sarebbe sufficiente a renderla thread-safe * se * ci si assicura di aver sincronizzato le chiamate in modo che gli usi non si alternino. In ogni caso, non è possibile proibire al programmatore di bloccare il programma. Poteva ancora creare un array e scrivere fuori dai limiti dell'array. e potrebbe uccidere gli altri thread usando Terminate o qualcosa :) Rendere il ritorno un const char * è una protezione efficace contro le modifiche accidentali. –

16

strerror da solo non è pericoloso. Nei tempi passati prima di infilare semplicemente non era un problema. Con i thread, due o più thread possono chiamare strerror lasciando il buffer restituito in uno stato indefinito. Per i programmi a thread singolo non dovrebbe fare male usare strerror a meno che non stiano giocando alcuni giochi bizzarri in libc, come la memoria comune per tutte le app in una DLL.

per affrontare questo c'è una nuova interfaccia per la stessa funzionalità:

int strerror_r(int errnum, char *buf, size_t buflen); 

Nota che il chiamante fornisce lo spazio buffer e la dimensione del buffer. Questo risolve il problema. Anche per le applicazioni a thread singolo si potrebbe anche usarlo. Non farà male un po ', e potresti anche abituarti a farlo in modo più sicuro.

NOTA: il prototipo sopra riportato è la specifica XSI. Può variare a seconda della piattaforma o con opzioni del compilatore o simboli #define. GNU, per esempio, fa si che o la loro propria versione disponibile a seconda di una #define

+1

E c'è anche TR24731 che definisce strerror_s(). –

+0

"Nei tempi passati prima del threading semplicemente non era un problema". Non è neanche rientrante, ma non ha mai preteso di essere richiamabile dai gestori del segnale. –

+0

Nessun problema, solo 'longjmp' ad esso dal gestore del segnale;) –

2

Per un wrapper succinta, è possibile utilizzare STLSoft s' stlsoft::error_desc, come in:

std::string errstr = stlsoft::error_desc(errno); 

Guardando il codice, sembra che è implementato in termini di strerror(), il che significa che sarà sicuro per la rientranza all'interno di un thread (cioè se utilizzato più volte all'interno di una determinata istruzione), ma non risolve il problema del multithreading.

Sembrano operare cicli di rilascio piuttosto rapidi per difetti, quindi potresti provare a richiedere una mod?

+0

Grazie. Potrei provarlo. – JamieH

12

Avendo ottenuto alcune buone risposte, e ora comprendere che alcune implementazioni possono essere così pazzo da scrivere in realtà ad un buffer comune condiviso - pericoloso per rientranza all'interno di un singolo thread, figuriamoci tra i thread! - la mia domanda smette di essere "Perché non posso usarla e quali sono le alternative?" a "Esistono alternative decenti e succinte in C e/o C++?"

Posix specifica strerror_r(), e su Windows è possibile utilizzare strerror_s(), che è un po 'diverso, ma ha lo stesso obiettivo. Faccio questo:

#define BAS_PERROR(msg, err_code)\ 
    bas_perror(msg, err_code, __FILE__, __LINE__) 

void bas_perror (const char* msg, int err_code, const char* filename, 
       unsigned long line_number); 


void 
bas_perror (const char* usr_msg, int err_code, const char* filename, 
      unsigned long line_number) 
{ 
    char sys_msg[64]; 

#ifdef _WIN32 
    if (strerror_s(sys_msg, sizeof sys_msg, err_code) != 0) 
    { 
    strncpy(sys_msg, "Unknown error", taille); 
    sys_msg[sizeof sys_msg - 1] = '\0'; 
    } 
#else 
    if (strerror_r(err_code, sys_msg, sizeof sys_msg) != 0) 
    { 
    strncpy(sys_msg, "Unknown error", sizeof sys_msg); 
    sys_msg[sizeof sys_msg - 1] = '\0'; 
    } 
#endif 

    fprintf(stderr, "%s: %s (debug information: file %s, at line %lu)\n", 
      usr_msg, sys_msg, filename, line_number); 
} 

Ho scritto questa funzione perché thread POSIX funzioni non modificano errno, ritornano un codice di errore invece. Quindi questa funzione è fondamentalmente uguale a perror(), tranne per il fatto che consente di fornire un codice di errore diverso da errno e visualizza anche alcune informazioni di debug. Puoi adattarlo alle tue necessità.