snprintf e Visual Studio 2010

102

Tenho a infelicidade de estar preso ao uso do VS 2010 para um projeto e percebi que o código a seguir ainda não compila usando o compilador não compatível com os padrões:

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    char buffer[512];

    snprintf(buffer, sizeof(buffer), "SomeString");

    return 0;
}

(falha na compilação com o erro: C3861: 'snprintf': identificador não encontrado)

Lembro-me de ser esse o caso com o VS 2005 e estou chocado ao ver que ainda não foi corrigido.

Alguém sabe se a Microsoft tem planos de mover suas bibliotecas C padrão para o ano de 2010?

Andrew
fonte
1
... ou você pode simplesmente fazer "#define snprintf _snprintf"
Fernando Gonzalez Sanchez
4
... você poderia, mas infelizmente _snprintf () não é o mesmo que snprintf (), pois não garante terminação nula.
Andy Krouwel,
Ok, então você precisará defini-lo como zero antes de usar _snprintf (). Também concordo com você. Desenvolver no MSVC é horrível. Os erros também são confusos.
Coruja de

Respostas:

88

Breve história: A Microsoft finalmente implementou o snprintf no Visual Studio 2015. Em versões anteriores, você pode simular como a seguir.


Versão longa:

Aqui está o comportamento esperado para snprintf:

int snprintf( char* buffer, std::size_t buf_size, const char* format, ... );

Grava no máximo buf_size - 1caracteres em um buffer. A sequência de caracteres resultante será encerrada com um caractere nulo, a menos que buf_sizeseja zero. Se buf_sizefor zero, nada é escrito e bufferpode ser um ponteiro nulo. O valor de retorno é o número de caracteres que teriam sido escritos assumindo ilimitado buf_size, sem contar o caractere nulo final.

Versões anteriores ao Visual Studio 2015 não tinham uma implementação compatível. Em vez disso, existem extensões não padrão como _snprintf()(que não grava terminador nulo em overflow) e _snprintf_s()(que pode impor terminação nula, mas retorna -1 em overflow em vez do número de caracteres que teriam sido gravados).

Alternativa sugerida para VS 2005 e superior:

#if defined(_MSC_VER) && _MSC_VER < 1900

#define snprintf c99_snprintf
#define vsnprintf c99_vsnprintf

__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
{
    int count = -1;

    if (size != 0)
        count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
    if (count == -1)
        count = _vscprintf(format, ap);

    return count;
}

__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
{
    int count;
    va_list ap;

    va_start(ap, format);
    count = c99_vsnprintf(outBuf, size, format, ap);
    va_end(ap);

    return count;
}

#endif
Valentin Milea
fonte
Isso nem sempre terminará a string com um 0, que é necessário em um estouro. O segundo if em c99_vsnprintf deve ser: if (count == -1) {if (size> 0) str [size-1] = 0; contagem = _vscprintf (formato, ap); }
Lothar
1
@Lothar: O buffer é sempre terminado em nulo. De acordo com o MSDN: "se o truncamento de string for habilitado passando _TRUNCATE, essas funções copiarão apenas a quantidade de string que couber, deixando o buffer de destino com terminação nula e retornando com sucesso".
Valentin Milea
2
Em junho de 2014, ainda não havia suporte C99 "completo" no Visual Studio, mesmo com a Atualização 2. Este blog fornece o resumo de suporte C99 para MSVC 2013. Como as funções da família snprintf () agora fazem parte do padrão C ++ 11 , MSVC fica atrás do clang e do gcc na implementação do C ++ 11!
fnisi
2
Com o VS2014, os padrões C99 com snprintf e vsnprintf são adicionados. Consulte blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
vulcan raven
1
Mikael Lepistö: Sério? Para mim, _snprintf só funciona se eu habilitar _CRT_SECURE_NO_WARNINGS. Esta solução alternativa funciona bem sem essa etapa.
FvD
33

snprintfnão faz parte do C89. É padrão apenas no C99. A Microsoft não tem nenhum plano de suporte ao C99 .

(Mas também é padrão em C ++ 0x ...!)

Veja outras respostas abaixo para uma solução alternativa.

Kennytm
fonte
5
Não é uma boa solução alternativa, entretanto ... pois há diferenças no comportamento de snprintf e _snprintf. _snprintf lida com o terminador nulo de forma retardada ao lidar com espaço de buffer insuficiente.
Andrew
7
@DeadMG - errado. cl.exe oferece suporte à opção / Tc, que instrui o compilador a compilar um arquivo como código C. Além disso, o MSVC é fornecido com uma versão das bibliotecas C padrão.
Andrew
3
@DeadMG - ele suporta, no entanto, o padrão C90, bem como alguns bits do C99, tornando-o um compilador C.
Andrew
15
Somente se você mora entre 1990 e 1999.
Filhote de cachorro de
6
-1, a função da Microsoft _snprintfé insegura e se comporta de maneira diferente snprintf(não adiciona necessariamente um terminador nulo), portanto, o conselho dado nesta resposta é enganoso e perigoso.
intervalo de
8

Se você não precisa do valor de retorno, também pode definir snprintf como _snprintf_s

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
Stefan Steiger
fonte
3

Eu acredito que o equivalente do Windows é sprintf_s

Il-Bhima
fonte
7
sprintf_sse comporta de maneira diferente de snprintf.
intervalo de
Especificamente, sprintf_s docs dizem: "Se o buffer for muito pequeno para o texto que está sendo impresso, então o buffer é definido como uma string vazia". Em contraste, o snprintf grava uma string truncada na saída.
Andrew Bainbridge
2
@AndrewBainbridge - você truncou a documentação. A frase completa é "Se o buffer for muito pequeno para o texto que está sendo impresso, o buffer é definido como uma string vazia e o manipulador de parâmetro inválido é chamado." O comportamento padrão para o identificador de parâmetro inválido é encerrar seu programa. Se você deseja truncamento com a família _s, você precisa usar snprintf_s e o sinalizador _TRUNCATE. Sim, é uma pena que as funções _s não forneçam uma maneira conveniente de truncar. Por outro lado, as funções _s usam magia de template para inferir tamanhos de buffer, e isso é excelente.
Bruce Dawson
2

Outro substituto seguro snprintf()e vsnprintf()fornecido pelo ffmpeg. Você pode verificar a fonte aqui (sugerido).

Marco Pracucci
fonte
1

Tentei o código de @Valentin Milea, mas encontrei erros de violação de acesso. A única coisa que funcionou para mim foi a implementação do Insane Coding: http://asprintf.insanecoding.org/

Especificamente, estava trabalhando com código legado VC ++ 2008. Desde a implementação da Insane Coding (pode ser baixado a partir do link acima), eu usei três arquivos: asprintf.c, asprintf.he vasprintf-msvc.c. Outros arquivos eram para outras versões do MSVC.

[EDITAR] Para ser completo, seus conteúdos são os seguintes:

asprintf.h:

#ifndef INSANE_ASPRINTF_H
#define INSANE_ASPRINTF_H

#ifndef __cplusplus
#include <stdarg.h>
#else
#include <cstdarg>
extern "C"
{
#endif

#define insane_free(ptr) { free(ptr); ptr = 0; }

int vasprintf(char **strp, const char *fmt, va_list ap);
int asprintf(char **strp, const char *fmt, ...);

#ifdef __cplusplus
}
#endif

#endif

asprintf.c:

#include "asprintf.h"

int asprintf(char **strp, const char *fmt, ...)
{
  int r;
  va_list ap;
  va_start(ap, fmt);
  r = vasprintf(strp, fmt, ap);
  va_end(ap);
  return(r);
}

vasprintf-msvc.c:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "asprintf.h"

int vasprintf(char **strp, const char *fmt, va_list ap)
{
  int r = -1, size = _vscprintf(fmt, ap);

  if ((size >= 0) && (size < INT_MAX))
  {
    *strp = (char *)malloc(size+1); //+1 for null
    if (*strp)
    {
      r = vsnprintf(*strp, size+1, fmt, ap);  //+1 for null
      if ((r < 0) || (r > size))
      {
        insane_free(*strp);
        r = -1;
      }
    }
  }
  else { *strp = 0; }

  return(r);
}

Uso (parte test.cfornecida pela Insane Coding):

#include <stdio.h>
#include <stdlib.h>
#include "asprintf.h"

int main()
{
  char *s;
  if (asprintf(&s, "Hello, %d in hex padded to 8 digits is: %08x\n", 15, 15) != -1)
  {
    puts(s);
    insane_free(s);
  }
}
andertavares
fonte