Criação de strings formatadas em C (não imprimi-las)

101

Tenho uma função que aceita uma string, que é:

void log_out(char *);

Ao chamá-lo, preciso criar uma string formatada em tempo real, como:

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

Como faço isso em ANSI C?


Apenas, uma vez que sprintf()retorna um int, isso significa que tenho que escrever pelo menos 3 comandos, como:

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

Alguma maneira de encurtar isso?

pistacchio
fonte
1
Acredito que o protótipo da função seja realmente: extern void log_out (const char *, ...); porque se não for, a chamada para ele é errônea (muitos argumentos). Deve ser usado um ponteiro const porque não há razão para log_out () modificar a string. Claro, você pode estar dizendo que deseja passar uma única string para a função - mas não pode. Uma opção é escrever uma versão varargs da função log_out ().
Jonathan Leffler

Respostas:

91

Use sprintf .

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

Gravar dados formatados na string Compõe uma string com o mesmo texto que seria impresso se format fosse usado em printf, mas em vez de ser impresso, o conteúdo é armazenado como string C no buffer apontado por str.

O tamanho do buffer deve ser grande o suficiente para conter toda a string resultante (consulte snprintf para uma versão mais segura).

Um caractere nulo de terminação é anexado automaticamente após o conteúdo.

Após o parâmetro de formato, a função espera pelo menos tantos argumentos adicionais quantos forem necessários para o formato.

Parâmetros:

str

Ponteiro para um buffer onde a string C resultante é armazenada. O buffer deve ser grande o suficiente para conter a string resultante.

format

String C que contém uma string de formato que segue as mesmas especificações do formato em printf (consulte printf para obter detalhes).

... (additional arguments)

Dependendo da string de formato, a função pode esperar uma sequência de argumentos adicionais, cada um contendo um valor a ser usado para substituir um especificador de formato na string de formato (ou um ponteiro para um local de armazenamento, para n). Deve haver pelo menos tantos desses argumentos quanto o número de valores especificados nos especificadores de formato. Argumentos adicionais são ignorados pela função.

Exemplo:

// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");
Akappa
fonte
35
Caramba! Se possível, use as funções de variação 'n'. Ou seja, snprintf. Eles farão você contar o tamanho do buffer e, assim, garantirão contra estouros.
dmckee --- ex-moderador gatinho
7
Sim, mas ele pediu uma função ANSI C e não tenho certeza se snprintf é ansi ou mesmo posix.
Akappa
7
Ah. Eu senti falta disso. Mas sou resgatado pelo novo padrão: as variantes 'n' são oficiais em C99. FWIW, YMMV, etc.
dmckee --- ex-gatinho moderador
1
o snprintf não é a maneira mais segura de fazer isso. Você deve ir com snprintf_s. Consulte msdn.microsoft.com/en-us/library/f30dzcf6(VS.80).aspx
joce
2
@Joce - a família de funções C99 snprintf () é bastante segura; no entanto, a família snprintf_s () tem alguns comportamentos diferentes (especialmente em relação a como o truncamento é tratado). MAS - a função _snprintf () da Microsoft não é segura - pois pode deixar o buffer resultante sem terminação (C99 snprintf () sempre termina).
Michael Burr
16

Se você tem um sistema compatível com POSIX-2008 (qualquer Linux moderno), você pode usar a asprintf()função segura e conveniente : Ele terá malloc()memória suficiente para você, você não precisa se preocupar com o tamanho máximo da string. Use-o assim:

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

Este é o esforço mínimo que você pode fazer para construir a corda de maneira segura. O sprintf()código que você forneceu na pergunta é profundamente falho:

  • Não há memória alocada por trás do ponteiro. Você está escrevendo a string em um local aleatório na memória!

  • Mesmo se você tivesse escrito

    char s[42];

    você estaria em apuros, porque não sabe qual número colocar entre colchetes.

  • Mesmo se você tivesse usado a variante "segura" snprintf(), ainda correria o perigo de suas strings ficarem truncadas. Ao gravar em um arquivo de log, essa é uma preocupação relativamente menor, mas tem o potencial de cortar precisamente as informações que seriam úteis. Além disso, ele cortará o caractere final da linha final, colando a próxima linha de log ao final da linha escrita sem sucesso.

  • Se você tentar usar uma combinação de malloc()e snprintf()para produzir um comportamento correto em todos os casos, acabará com quase o dobro do código que eu forneci asprintf()e basicamente reprogramará a funcionalidade de asprintf().


Se você deseja fornecer um wrapper de log_out()que pode receber uma printf()lista de parâmetros de estilo, você pode usar a variante vasprintf()que recebe a va_listcomo argumento. Aqui está uma implementação perfeitamente segura de tal 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");
    }
}
cmaster - reintegrar monica
fonte
2
Observe que asprintf()não faz parte do padrão C 2011 nem do POSIX, nem mesmo do POSIX 2008 ou 2013. Faz parte do TR 27431-2: consulte Você usa as funções 'seguras' do TR 24731?
Jonathan Leffler
funciona como charme! Eu estava enfrentando o problema, quando o valor "char *" não estava sendo impresso corretamente nos logs, ou seja, a string formatada não era apropriada de alguma forma. o código estava usando, "asprintf ()".
parasrish de
11

Parece-me que você deseja passar facilmente uma string criada usando a formatação no estilo printf para a função que você já possui que usa uma string simples. Você pode criar uma função de wrapper usando stdarg.hrecursos e vsnprintf()(que podem não estar prontamente disponíveis, dependendo do seu compilador / plataforma):

#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;
}

Para plataformas que não fornecem uma boa implementação (ou qualquer implementação) da snprintf()família de rotinas, usei com sucesso um domínio quase público snprintf()de Holger Weiss .

Michael Burr
fonte
Atualmente, pode-se considerar o uso de vsnprintf_s.
amalgamar
3

Se você tiver o código log_out(), reescreva-o. Provavelmente, você pode fazer:

static FILE *logfp = ...;

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

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

Se houver necessidade de informações de registro extras, elas podem ser impressas antes ou depois da mensagem exibida. Isso economiza alocação de memória e tamanhos de buffer duvidosos e assim por diante. Você provavelmente precisa inicializarlogfp para zero (ponteiro nulo) e verificar se ele é nulo e abrir o arquivo de log conforme apropriado - mas o código existente log_out()deve lidar com isso de qualquer maneira.

A vantagem dessa solução é que você pode simplesmente chamá-la como se fosse uma variante de printf(); na verdade, é uma variante menor emprintf() .

Se você não tem o código log_out(), considere se você pode substituí-lo por uma variante como a descrita acima. Se você pode usar o mesmo nome, dependerá de sua estrutura de aplicativo e da fonte final da log_out()função atual . Se estiver no mesmo arquivo de objeto que outra função indispensável, você terá que usar um novo nome. Se você não conseguir descobrir como replicá-lo exatamente, terá que usar alguma variante, como as fornecidas em outras respostas, que aloca uma quantidade adequada de memória.

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? */
}

Obviamente, você agora chama o em log_out_wrapper()vez de log_out()- mas a alocação de memória e assim por diante é feita uma vez. Eu me reservo o direito de superalocar espaço em um byte desnecessário - não verifiquei duas vezes se o comprimento retornado por vsnprintf()inclui o nulo de terminação ou não.

Jonathan Leffler
fonte
3

Não use sprintf.
Isso irá estourar seu String-Buffer e travar seu Programa.
Sempre use snprintf

Tom
fonte
0

Eu não fiz isso, então vou apenas apontar para a resposta certa.

C tem disposições para funções que usam números não especificados de operandos, usando o <stdarg.h>cabeçalho. Você pode definir sua função como void log_out(const char *fmt, ...);e obter o va_listinterior da função. Em seguida, você pode alocar memória e chamar vsprintf()com a memória alocada, formato e va_list.

Como alternativa, você poderia usar isso para escrever uma função análoga a sprintf()que alocaria memória e retornaria a string formatada, gerando-a mais ou menos como acima. Seria um vazamento de memória, mas se você estiver apenas desconectando, pode não importar.

David Thornley
fonte
-2

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html fornece o seguinte exemplo para imprimir em stderr. Você pode modificá-lo para usar sua função de 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);
 }

Em vez de vfprintf, você precisará usar o vsprintf onde precisa fornecer um buffer adequado para imprimir.

lothar
fonte