Creazione di stringhe formattate in C (non stamparle)


101

Ho una funzione che accetta una stringa, ovvero:

void log_out(char *);

Nel chiamarlo, ho bisogno di creare una stringa formattata al volo come:

int i = 1;
log_out("some text %d", i);

Come si esegue questa operazione in ANSI C?


Solo, poiché sprintf()restituisce un int, questo significa che devo scrivere almeno 3 comandi, come:

char *s;
sprintf(s, "%d\t%d", ix, iy);
log_out(s);

Qualche modo per accorciarlo?


1
Confido che il prototipo della funzione sia realmente: extern void log_out (const char *, ...); perché se non lo è, la chiamata ad esso è errata (troppi argomenti). Dovrebbe prendere un puntatore const perché non c'è motivo per log_out () di modificare la stringa. Certo, potresti dire che vuoi passare una singola stringa alla funzione, ma non puoi. Un'opzione è quindi scrivere una versione varargs della funzione log_out ().
Jonathan Leffler

Risposte:


91

Usa sprintf .

int sprintf ( char * str, const char * format, ... );

Scrivi dati formattati in stringa Compone una stringa con lo stesso testo che verrebbe stampato se format fosse usato su printf, ma invece di essere stampato, il contenuto viene memorizzato come una stringa C nel buffer puntato da str.

La dimensione del buffer dovrebbe essere abbastanza grande da contenere l'intera stringa risultante (vedere snprintf per una versione più sicura).

Un carattere null di terminazione viene automaticamente aggiunto dopo il contenuto.

Dopo il parametro format, la funzione prevede almeno tanti argomenti aggiuntivi quanti sono necessari per il formato.

Parametri:

str

Puntatore a un buffer in cui è memorizzata la stringa C risultante. Il buffer dovrebbe essere sufficientemente grande da contenere la stringa risultante.

format

Stringa C che contiene una stringa di formato che segue le stesse specifiche di format in printf (vedere printf per i dettagli).

... (additional arguments)

A seconda della stringa di formato, la funzione può aspettarsi una sequenza di argomenti aggiuntivi, ciascuno contenente un valore da utilizzare per sostituire un identificatore di formato nella stringa di formato (o un puntatore a una posizione di archiviazione, per n). Dovrebbero esserci almeno tanti di questi argomenti quanti sono i valori specificati negli identificatori di formato. Gli argomenti aggiuntivi vengono ignorati dalla funzione.

Esempio:

// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");

35
Yikes! Se possibile, utilizzare le funzioni di variazione "n". Cioè snprintf. Ti faranno contare le dimensioni del buffer e quindi assicurarti contro i sovraccarichi.
dmckee --- gattino ex moderatore

7
Sì, ma ha chiesto una funzione ANSI C e non sono così sicuro che snprintf sia ansi o addirittura posix.
akappa

7
Ah. Ho perso questo. Ma sono salvato dal nuovo standard: le varianti "n" sono ufficiali in C99. FWIW, YMMV, ecc.
dmckee --- gattino ex moderatore,

1
snprintf non è il modo più sicuro di procedere. Dovresti andare con snprintf_s. See msdn.microsoft.com/en-us/library/f30dzcf6(VS.80).aspx
joce

2
@Joce - la famiglia di funzioni C99 snprintf () è abbastanza sicura; tuttavia, la famiglia snprintf_s () ha un comportamento molto diverso (soprattutto per quanto riguarda il modo in cui viene gestito il trunction). MA - la funzione _snprintf () di Microsoft non è sicura, poiché potrebbe potenzialmente lasciare il buffer risultante non terminato (C99 snprintf () termina sempre).
Michael Burr

16

Se si dispone di un sistema conforme a POSIX-2008 (qualsiasi Linux moderno), è possibile utilizzare la funzione sicura e conveniente asprintf(): avrà malloc()abbastanza memoria per te, non devi preoccuparti della dimensione massima della stringa. Usalo in questo modo:

char* string;
if(0 > asprintf(&string, "Formatting a number: %d\n", 42)) return error;
log_out(string);
free(string);

Questo è lo sforzo minimo che puoi ottenere per costruire la corda in modo sicuro. Il sprintf()codice che hai fornito nella domanda è profondamente difettoso:

  • Non c'è memoria allocata dietro il puntatore. Stai scrivendo la stringa in una posizione casuale nella memoria!

  • Anche se tu avessi scritto

    char s[42];

    saresti in grossi guai, perché non puoi sapere quale numero mettere tra parentesi.

  • Anche se avessi usato la variante "sicura" snprintf(), correresti comunque il pericolo che le tue stringhe vengano troncate. Quando si scrive in un file di registro, questa è una preoccupazione relativamente minore, ma ha il potenziale per tagliare con precisione le informazioni che sarebbero state utili. Inoltre, taglierà il carattere di fine riga, incollando la riga di registro successiva alla fine della riga scritta senza successo.

  • Se provi a utilizzare una combinazione di malloc()e snprintf()per produrre un comportamento corretto in tutti i casi, ti ritroverai con circa il doppio di codice rispetto a quello che ho fornito asprintf()e sostanzialmente riprogrammerai la funzionalità di asprintf().


Se stai cercando di fornire un wrapper log_out()che può prendere un printf()elenco di parametri di stile stesso, puoi usare la variante vasprintf()che accetta va_listcome argomento a. Ecco un'implementazione perfettamente sicura di un tale wrapper:

//Tell gcc that we are defining a printf-style function so that it can do type checking.
//Obviously, this should go into a header.
void log_out_wrapper(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

void log_out_wrapper(const char *format, ...) {
    char* string;
    va_list args;

    va_start(args, format);
    if(0 > vasprintf(&string, format, args)) string = NULL;    //this is for logging, so failed allocation is not fatal
    va_end(args);

    if(string) {
        log_out(string);
        free(string);
    } else {
        log_out("Error while logging a message: Memory allocation failed.\n");
    }
}

2
Si noti che asprintf()non fa parte dello standard C 2011 né di POSIX, nemmeno POSIX 2008 o 2013. Fa parte di TR 27431-2: vedere Utilizzate le funzioni "sicure" di TR 24731?
Jonathan Leffler

funziona come un fascino! Stavo affrontando il problema, quando il valore "char *" non veniva stampato correttamente nei log, cioè la stringa formattata non era appropriata in qualche modo. codice stava usando, "asprintf ()".
parasrish

11

Mi sembra che tu voglia essere in grado di passare facilmente una stringa creata usando la formattazione in stile printf alla funzione che hai già che accetta una stringa semplice. È possibile creare una funzione wrapper utilizzando le stdarg.hstrutture e vsnprintf()(che potrebbero non essere prontamente disponibili, a seconda del compilatore / piattaforma):

#include <stdarg.h>
#include <stdio.h>

// a function that accepts a string:

void foo( char* s);

// You'd like to call a function that takes a format string 
//  and then calls foo():

void foofmt( char* fmt, ...)
{
    char buf[100];     // this should really be sized appropriately
                       // possibly in response to a call to vsnprintf()
    va_list vl;
    va_start(vl, fmt);

    vsnprintf( buf, sizeof( buf), fmt, vl);

    va_end( vl);

    foo( buf);
}



int main()
{
    int val = 42;

    foofmt( "Some value: %d\n", val);
    return 0;
}

Per le piattaforme che non forniscono una buona implementazione (o alcuna implementazione) della snprintf()famiglia di routine, ho utilizzato con successo un dominio quasi pubblico di snprintf()Holger Weiss .


Attualmente, si potrebbe considerare l'utilizzo di vsnprintf_s.
amalgamare il

3

Se hai il codice per log_out(), riscrivilo. Molto probabilmente puoi fare:

static FILE *logfp = ...;

void log_out(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(logfp, fmt, args);
    va_end(args);
}

Se sono necessarie ulteriori informazioni di registrazione, è possibile stamparle prima o dopo il messaggio visualizzato. Ciò consente di risparmiare allocazione di memoria e dimensioni di buffer dubbie e così via. Probabilmente è necessario inizializzare logfpa zero (puntatore nullo) e verificare se è nullo e aprire il file di registro come appropriato, ma il codice esistente log_out()dovrebbe occuparsene comunque.

Il vantaggio di questa soluzione è che puoi chiamarla semplicemente come se fosse una variante di printf(); anzi, è una variante minore di printf().

Se non hai il codice log_out(), valuta se puoi sostituirlo con una variante come quella descritta sopra. La possibilità di utilizzare lo stesso nome dipenderà dal framework dell'applicazione e dall'origine definitiva della log_out()funzione corrente . Se è nello stesso file oggetto di un'altra funzione indispensabile, dovresti usare un nuovo nome. Se non riesci a capire come replicarlo esattamente, dovrai usare qualche variante come quelle fornite in altre risposte che alloca una quantità appropriata di memoria.

void log_out_wrapper(const char *fmt, ...)
{
    va_list args;
    size_t  len;
    char   *space;

    va_start(args, fmt);
    len = vsnprintf(0, 0, fmt, args);
    va_end(args);
    if ((space = malloc(len + 1)) != 0)
    {
         va_start(args, fmt);
         vsnprintf(space, len+1, fmt, args);
         va_end(args);
         log_out(space);
         free(space);
    }
    /* else - what to do if memory allocation fails? */
}

Ovviamente, ora chiami log_out_wrapper()invece di log_out()- ma l'allocazione della memoria e così via viene eseguita una volta. Mi riservo il diritto di sovra-allocare lo spazio di un byte non necessario - non ho ricontrollato se la lunghezza restituita da vsnprintf()include il null finale o meno.


3

Non usare sprintf.
Overflow del tuo String-Buffer e crash del tuo programma.
Usa sempre snprintf


0

Non l'ho fatto, quindi indicherò solo la risposta giusta.

C ha disposizioni per funzioni che accettano numeri non specificati di operandi, utilizzando l' <stdarg.h>intestazione. Puoi definire la tua funzione come void log_out(const char *fmt, ...);e ottenere l' va_listinterno della funzione. Quindi puoi allocare memoria e chiamare vsprintf()con la memoria allocata, il formato e va_list.

In alternativa, potresti usarlo per scrivere una funzione analoga a sprintf()quella che allocherebbe memoria e restituirebbe la stringa formattata, generandola più o meno come sopra. Sarebbe una perdita di memoria, ma se ti stai disconnettendo potrebbe non avere importanza.


-2

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html fornisce il seguente esempio per stampare su stderr. Puoi modificarlo per utilizzare invece la tua funzione di registro:

 #include <stdio.h>
 #include <stdarg.h>

 void
 eprintf (const char *template, ...)
 {
   va_list ap;
   extern char *program_invocation_short_name;

   fprintf (stderr, "%s: ", program_invocation_short_name);
   va_start (ap, template);
   vfprintf (stderr, template, ap);
   va_end (ap);
 }

Invece di vfprintf dovrai usare vsprintf dove devi fornire un buffer adeguato per stampare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.