2010-10-20 5 views
9

Sto scrivendo un programma C che dovrebbe essere compilato con tutti i principali compilatori. Attualmente sto sviluppando su GCC su una macchina Linux e compilerò su MSVC prima di eseguire il codice. Per semplificare la compilazione incrociata, compilo con le bandiere -ansi e -pedantic. Questo ha funzionato bene fino a quando ho iniziato a utilizzare snprintf che non è disponibile nello standard C89. GCC può compilare questo senza lo switch ma MSVC fallirà sempre in quanto non ha il supporto C99.Utilizzo di snprintf in un'applicazione multipiattaforma

Così ho fatto qualcosa di simile,

#ifdef WIN32 
#define snprintf sprintf_s 
#endif 

Questo funziona bene perché snprintf e sprintf_s ha le stesse firme. Mi chiedo se questo è l'approccio corretto?

+1

non è lo standard 'snprintf' per tutti i C in qualsiasi piattaforma? –

+2

no. 'snprintf' fa parte dello standard C99. MSVC non ha un'implementazione C99. –

+3

'sprintf_s' non è equivalente. 'snprintf' restituisce il numero di caratteri che sarebbero stati scritti, mentre' sprintf_s' restituisce -1 sul troncamento. Vedi [questa discussione] (http://social.msdn.microsoft.com/forums/en-US/vcgeneral/thread/2b339bdf-7ab1-4a08-bf7e-e9293801455b/). –

risposta

-5

No. Il tuo approccio è destinato a fallire.

sqrt e cos hanno lo stesso prototipo. Pensi di poterli scambiare in un programma e ottenere lo stesso comportamento prima/dopo la modifica?


Probabilmente dovrebbe scrivere il proprio snprintf, o scaricare un'implementazione da internet (google is your friend) e l'uso che sia in Linux e Windows.

+0

grazie. Proverò un'implementazione personalizzata. –

+7

Questa risposta non è utile. La domanda si pone in un modo diverso: poiché snprintf e sprintf_s hanno la stessa firma, hanno anche la stessa funzionalità? La tua risposta in fondo dice: non lo so. –

14

Ho trovato this sull'utilizzo di _snprintf() in alternativa e i trucchi coinvolti se la protezione da sovraccarico del buffer si attiva effettivamente. Da quello che ho potuto vedere a una rapida occhiata, simili avvertimenti si applicano a sprintf_s.

Riesci a vedere il problema? Nella versione Linux, l'output è sempre terminato da null. In MSVC, non lo è.

Ancora più sottile è la differenza tra il parametro size in Linux e il parametro count in MSVC. Il primo è la dimensione del buffer di output incluso il null che termina e il secondo è il numero massimo di caratteri da memorizzare, il che esclude il null terminante.

Oh, e non dimenticare di inviare una mail a Microsoft chiedendo di supportare gli attuali standard linguistici. (So ​​che hanno già annunciato che non hanno intenzione di supportare C99, ma lo infastidiscono comunque. Lo meritano.)

In conclusione, se vuoi giocare davvero sicuro, dovrai fornire il tuo snprintf() (un wrapper attorno a _snprintf() o sprintf_s() che recupera il loro comportamento non standard) per MSVC.

+0

Suggerisco un "involucro del contratto" attorno alle implementazioni esistenti (come menzionato nella risposta) invece di trascinare qualsiasi codice di terze parti nel progetto (come suggerito da pmg). Un'implementazione completa di '* printf()' è abbastanza grande. – DevSolar

+0

In realtà un'implementazione completa di 'printf' può essere molto piccola. Normalmente sono d'accordo con il tuo principio di avvolgere le implementazioni spezzate piuttosto che reimplementarle, ma dal momento che Windows '* printf' ha anche alcune cose rotte che non puoi facilmente racchiudere (come l'interpretazione all'indietro di '% s' e'% ls' nelle ampie varianti), mi chiedo se solo la sostituzione potrebbe essere l'approccio migliore. –

+0

In realtà un'altra cosa che puoi correggere allo stesso tempo è la stampa inesatta in virgola mobile di MS. –

0

la risposta più completa (si può migliorare, se lo si desidera), che mettere in un adesivo

#if __PLATFORM_WIN_ZERO_STANDARD__ 

    static inline 
    int LIBSYS_SNPRINTF(char * str, size_t size, const char * format, ...) 
    { 
     int retval; 
     va_list ap; 
     va_start(ap, format); 
     retval = _vsnprintf(str, size, format, ap); 
     va_end(ap); 
     return retval; 
    } 

    static inline 
    int LIBSYS_VASPRINTF(char **ret, char * format, va_list ap) 
    { 
     int wanted = vsnprintf(*ret = NULL, 0, format, ap); 
     if((wanted > 0) && ((*ret = LIBSYS_MALLOC(1 + wanted)) != NULL)) { 
      return vsprintf(*ret, format, ap); 
     } 
     return wanted; 
    } 

    static inline 
    int LIBSYS_ASPRINTF(char **ret, char * format, ...) 
    { 
     int retval; 
     va_list ap; 
     va_start(ap, format); 
     retval = LIBSYS_VASPRINTF(ret, format, ap); 
     va_end(ap); 
     return retval; 
    } 

#else 
    #define LIBSYS_SNPRINTF snprintf 
    #define LIBSYS_VASPRINTF vasprintf 
    #define LIBSYS_ASPRINTF asprintf 
#endif 
+0

Abbastanza sicuro che le funzioni 'inline' e variadic sono C99. – Thomas

+3

@Thomas 'printf()' è una funzione variadica. È in giro da un po '... Quindi no, non è un'aggiunta al C99. – unwind

9

La proposta può funzionare se si sta prestando attenzione.Il problema è che sia la funzione si comportano un po 'diverso, se questo non è un problema per voi, vi sono buone per andare, altrimenti pensare a una funzione wrapper:

Differenze tra MSVCs _snprintf e ufficiale C99 (gcc, clang) snprintf:

valore

di ritorno:

  • MSVC: ritorno -1 se la dimensione del buffer non è sufficiente per scrivere tutto
  • GCC (non compreso di terminazione nullo!): restituisce il numero di caratteri che sarebbe stato scritto se il buffer di grandi dimensioni abbastanza

byte scritte:

  • MSVC: scrivere il più possibile, non scrivere NULL alla fine se c'è spazio lasciato
  • GCC: scrivere il più possibile, sempre scrivere di terminazione NULL (eccezione : buffer_size = 0)

Interessante %n sottigliezza: Se si utilizza %n nel codice, MSVC lascerà non inizializzate! se smette di parsing perché la dimensione del buffer è troppo piccola, GCC scriverà sempre il numero di byte che sarebbero stati scritti se il buffer fosse stato sufficientemente grande.

Quindi la mia proposta sarebbe quello di scrivere la propria funzione involucro mysnprintf utilizzando vsnprintf/_vsnprintf che dà i valori stessi di ritorno e scrive le stesse byte su entrambe le piattaforme (attenzione: %n è più difficile da risolvere).

+0

È un po 'più facile avvolgere al livello successivo. Ho dovuto farlo un po 'di tempo fa, e l'approccio che ho usato aveva una funzione interna che avrebbe alloca() un buffer di una dimensione specificata e chiama vsnprintf; se il risultato si adattava, veniva eliminato in un'altra memoria e la funzione restituiva OK. In caso contrario, la funzione restituirebbe un codice 'try again with specified buffer size larger'. Per gcc la dimensione 'try-again' era conosciuta dal ritorno di vsnprintf, e per MSVC avrebbe continuato ad aumentare in base ad alcune euristiche. – greggo

1

È possibile aprire il file speciale NUL per MSVC e scrivere su quello. Ti dirà sempre quanti byte sono necessari e non scriverà nulla. Mi piace così:

int main (int argc, char* argv[]) { 
    FILE* outfile = fopen("nul", "wb"); 
    int written; 

    if(outfile == NULL) { 
    fputs ("could not open 'nul'", stderr); 
    } 
    else { 
    written = fprintf(outfile, "redirect to /dev/null"); 
    fclose(outfile); 
    fprintf(stdout, "didn't write %d characters", written); 
    } 

    return 0; 
} 

Si dovrebbe quindi sapere quanti byte allocare per utilizzare sprintf con successo.