snprintf et Visual Studio 2010


102

Je suis assez malheureux d'être bloqué à l'aide de VS 2010 pour un projet, et j'ai remarqué que le code suivant ne se construit toujours pas à l'aide du compilateur non conforme aux normes:

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

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

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

    return 0;
}

(échoue la compilation avec l'erreur: C3861: 'snprintf': identifiant introuvable)

Je me souviens que c'était le cas avec VS 2005 et je suis choqué de voir qu'il n'a toujours pas été corrigé.

Est-ce que quelqu'un sait si Microsoft envisage de déplacer ses bibliothèques C standard en 2010?


1
... ou vous pouvez simplement faire "#define snprintf _snprintf"
Fernando Gonzalez Sanchez

4
... vous pourriez, mais malheureusement _snprintf () n'est pas la même chose que snprintf () car elle ne garantit pas une terminaison nulle.
Andy Krouwel

Ok donc vous devrez le remettre à zéro avant d'utiliser _snprintf (). Aussi je suis d'accord avec vous. Développer sous MSVC est horrible. Les erreurs sont aussi déroutantes.
Owl

Réponses:


88

Petite histoire: Microsoft a finalement implémenté snprintf dans Visual Studio 2015. Sur les versions antérieures, vous pouvez le simuler comme ci-dessous.


Version longue:

Voici le comportement attendu pour snprintf:

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

Écrit au maximum les buf_size - 1caractères dans un tampon. La chaîne de caractères résultante se terminera par un caractère nul, sauf s'il buf_sizeest égal à zéro. Si buf_sizevaut zéro, rien n'est écrit et bufferpeut être un pointeur nul. La valeur de retour est le nombre de caractères qui auraient été écrits en supposant un nombre illimité buf_size, sans compter le caractère nul de fin.

Les versions antérieures à Visual Studio 2015 n'avaient pas d'implémentation conforme. Il existe à la place des extensions non standard telles que _snprintf()(qui n'écrit pas de terminateur nul en cas de débordement) et _snprintf_s()(qui peut appliquer une terminaison nulle, mais renvoie -1 en cas de débordement au lieu du nombre de caractères qui auraient été écrits).

Solution de secours suggérée pour VS 2005 et versions ultérieures:

#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

Cela ne terminera pas toujours la chaîne avec un 0 qui est requis en cas de débordement. Le second if dans c99_vsnprintf doit être: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (format, ap); }
Lothar

1
@Lothar: Le tampon est toujours terminé par un nul. Selon MSDN: "si la troncature de chaîne est activée en passant _TRUNCATE, ces fonctions ne copieront que la quantité de chaîne qui conviendra, laissant le tampon de destination terminé par un zéro et renvoyer avec succès".
Valentin Milea

2
Depuis juin 2014, il n'y a toujours pas de prise en charge «complète» de C99 dans Visual Studio, même avec la mise à jour 2. Ce blog présente le support C99 pour MSVC 2013. Comme les fonctions de la famille snprintf () font désormais partie de la norme C ++ 11 , MSVC est à la traîne par rapport à clang et gcc dans l'implémentation C ++ 11!
fnisi

2
Avec VS2014, les normes C99 avec snprintf et vsnprintf sont ajoutées. Voir blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
corbeau vulcan

1
Mikael Lepistö: Vraiment? Pour moi, _snprintf ne fonctionne que si j'active _CRT_SECURE_NO_WARNINGS. Cette solution de contournement fonctionne correctement sans cette étape.
FvD

33

snprintfne fait pas partie de C89. C'est standard uniquement dans C99. Microsoft n'a pas de plan prenant en charge C99 .

(Mais c'est aussi standard en C ++ 0x ...!)

Voir les autres réponses ci-dessous pour une solution de contournement.


5
Ce n'est pas une bonne solution de contournement, cependant ... car il existe des différences dans le comportement snprintf et _snprintf. _snprintf gère le terminateur nul avec retard en cas d'espace tampon insuffisant.
Andrew

7
@DeadMG - faux. cl.exe prend en charge l'option / Tc, qui demande au compilateur de compiler un fichier en tant que code C. De plus, MSVC est livré avec une version des bibliothèques C standard.
Andrew

3
@DeadMG - il prend cependant en charge le standard C90 ainsi que quelques bits de C99, ce qui en fait un compilateur C.
Andrew

15
Seulement si vous vivez entre 1990 et 1999.
Puppy

6
-1, Microsoft _snprintfest une fonction non sécurisée qui se comporte différemment snprintf(elle n'ajoute pas nécessairement un terminateur nul), donc le conseil donné dans cette réponse est trompeur et dangereux.
entre le

8

Si vous n'avez pas besoin de la valeur de retour, vous pouvez également définir simplement snprintf comme _snprintf_s

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)

3

Je pense que l'équivalent Windows est sprintf_s


7
sprintf_sse comporte différemment de snprintf.
entre le

Plus précisément, les documents sprintf_s disent: "Si le tampon est trop petit pour le texte imprimé, le tampon est défini sur une chaîne vide". En revanche, snprintf écrit une chaîne tronquée dans la sortie.
Andrew Bainbridge

2
@AndrewBainbridge - vous avez tronqué la documentation. La phrase complète est "Si le tampon est trop petit pour le texte en cours d'impression, le tampon est défini sur une chaîne vide et le gestionnaire de paramètres non valide est appelé." Le comportement par défaut du handle de paramètre non valide consiste à mettre fin à votre programme. Si vous souhaitez une troncature avec la famille _s, vous devez utiliser snprintf_s et l'indicateur _TRUNCATE. Oui, il est malheureux que les fonctions _s ne permettent pas de tronquer facilement. D'un autre côté, les fonctions _s utilisent la magie des modèles pour déduire les tailles des tampons, et c'est excellent.
Bruce Dawson


1

J'ai essayé le code de @Valentin Milea mais j'ai des erreurs de violation d'accès. La seule chose qui a fonctionné pour moi était l'implémentation d'Insane Coding: http://asprintf.insanecoding.org/

Plus précisément, je travaillais avec le code hérité de VC ++ 2008. De la mise en œuvre de Insane de codage (peut être téléchargé à partir du lien ci - dessus), j'ai utilisé trois fichiers: asprintf.c, asprintf.het vasprintf-msvc.c. Les autres fichiers concernaient d'autres versions de MSVC.

[EDIT] Par souci d'exhaustivité, leur contenu est le suivant:

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

Utilisation (partie de test.cfourni par 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);
  }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.