snprintf ve Visual Studio 2010


102

Bir proje için VS 2010 kullanarak takılıp kalacağım kadar talihsizim ve aşağıdaki kodun hala standartlara uymayan uyumlu derleyici kullanılarak oluşturulmadığını fark ettim:

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

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

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

    return 0;
}

(C3861: 'snprintf' hatasıyla derleme başarısız: tanımlayıcı bulunamadı)

Bunun VS 2005 ile geri döndüğünü hatırlıyorum ve hala düzeltilmediğini görünce şok oldum.

Microsoft'un standart C kitaplıklarını 2010 yılına taşımayı planlayıp planlamadığını bilen var mı?


1
... veya sadece "#define snprintf _snprintf" yapabilirsiniz
Fernando Gonzalez Sanchez

4
... yapabilirsiniz, ancak maalesef _snprintf (), boş sonlandırmayı garanti etmediği için snprintf () ile aynı değildir.
Andy Krouwel

Tamam, _snprintf () kullanmadan önce onu sıfıra ayarlamanız gerekecek. Ben de sana katılıyorum. MSVC altında geliştirme yapmak korkunç. Hatalar da cehennem gibi kafa karıştırıcı.
Baykuş

Yanıtlar:


88

Kısa hikaye: Microsoft, nihayet Visual Studio 2015'te snprintf'i uygulamaya koydu. Önceki sürümlerde aşağıdaki gibi simüle edebilirsiniz.


Uzun versiyon:

Snprintf için beklenen davranış şu şekildedir:

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

buf_size - 1Bir arabelleğe en çok karakter yazar . Ortaya çıkan karakter dizisi buf_size, sıfır olmadığı sürece bir boş karakterle sonlandırılacaktır . Eğer buf_sizeis sıfır, hiçbir şey yazılır ve bufferbir boş gösterici olabilir. Dönüş değeri, buf_sizebiten boş karakterleri hesaba katmadan , sınırsız olduğu varsayılarak yazılacak karakter sayısıdır .

Visual Studio 2015'ten önceki sürümlerde uyumlu bir uygulama yoktu. Bunun yerine _snprintf()(taşma üzerine boş sonlandırıcı yazmayan) ve _snprintf_s()(boş sonlandırmayı zorlayabilen, ancak taşmada yazılacak karakter sayısı yerine -1 döndüren ) gibi standart olmayan uzantılar vardır .

VS 2005 ve üstü için önerilen yedek:

#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

Bu, bir taşmada gerekli olan dizeyi her zaman 0 ile sonlandırmaz. İkincisi, c99_vsnprintf içinde şöyle olmalıdır: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (biçim, ap); }
Lothar

1
@Lothar: Arabellek her zaman boş olarak sonlandırılır. MSDN'ye göre: "_TRUNCATE iletilerek dize kesilmesi etkinleştirilirse, bu işlevler dizenin yalnızca sığacağı kadarını kopyalayacak, hedef arabelleği sonlandırılmış olarak bırakacak ve başarıyla geri dönecektir".
Valentin Milea

2
Haziran 2014 itibariyle, Visual Studio'da Güncelleştirme 2'de bile "tam" C99 desteği bulunmamaktadır. Bu blog , MSVC 2013 için C99 destek özetini vermektedir. Snprintf () aile işlevleri artık C ++ 11 standardının bir parçası olduğu için , MSVC, C ++ 11 uygulamasında clang ve gcc'nin gerisinde kalıyor!
fnisi

2
VS2014 ile snprintf ve vsnprintf ile C99 standartları eklenir. Blogs.msdn.com/b/vcblog/archive/2014/06/18/… bakın .
vulcan kuzgun

1
Mikael Lepistö: Gerçekten mi? Benim için _snprintf yalnızca _CRT_SECURE_NO_WARNINGS'i etkinleştirirsem çalışır. Bu geçici çözüm, bu adım olmadan iyi çalışır.
FvD

33

snprintfC89'un bir parçası değildir. Yalnızca C99'da standarttır. Microsoft'un C99'u destekleyen bir planı yoktur .

(Ancak C ++ 0x'de de standarttır ...!)

Bir geçici çözüm için aşağıdaki diğer yanıtlara bakın.


5
Snprintf ve _snprintf davranışında farklılıklar olduğu için bu iyi bir çözüm değildir. _snprintf, yetersiz arabellek alanıyla uğraşırken boş sonlandırıcıyı geciktirerek işler.
Andrew

7
@DeadMG - yanlış. cl.exe, derleyiciye bir dosyayı C kodu olarak derlemesi talimatını veren / Tc seçeneğini destekler. Ayrıca MSVC, standart C kitaplıklarının bir sürümüyle birlikte gelir.
Andrew

3
@DeadMG - Bununla birlikte, C90 standardını ve birkaç bit C99'u destekleyerek onu bir C derleyicisi yapar.
Andrew

15
Yalnızca 1990 ile 1999 arasında yaşıyorsanız.
Puppy

6
-1, Microsoft'unkinden _snprintffarklı davranan güvenli olmayan bir işlevdir snprintf(boş bir sonlandırıcı eklemesi gerekmez), bu nedenle bu yanıtta verilen tavsiyeler yanıltıcı ve tehlikelidir.
interjay

8

Dönüş değerine ihtiyacınız yoksa snprintf'i _snprintf_s olarak da tanımlayabilirsiniz.

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

3

Windows eşdeğeri olduğuna inanıyorum sprintf_s


7
sprintf_sfarklı davranır snprintf.
interjay

Sprintf_s docs özellikle, "Arabellek yazdırılan metin için çok küçükse, arabellek boş bir dizeye ayarlanır" der. Buna karşılık, snprintf çıktıya kesilmiş bir dizge yazar.
Andrew Bainbridge

2
@AndrewBainbridge - belgeleri kestiniz. Tam cümle, "Arabellek yazdırılan metin için çok küçükse, arabellek boş bir dizeye ayarlanır ve geçersiz parametre işleyici çağrılır." Geçersiz parametre tutamacının varsayılan davranışı, programınızı sonlandırmaktır. _S ailesi ile kısaltmak istiyorsanız, snprintf_s ve _TRUNCATE bayrağını kullanmanız gerekir. Evet, talihsiz bir durumdur ki _s işlevleri, kısaltma için uygun bir yol sağlamaz. Öte yandan, _s işlevleri arabellek boyutlarını çıkarmak için şablon sihrini kullanır ve bu mükemmeldir.
Bruce Dawson


1

@Valentin Milea'nın kodunu denedim ama erişim ihlali hataları aldım. Benim için işe yarayan tek şey, Insane Coding'in uygulamasıydı: http://asprintf.insanecoding.org/

Özellikle, VC ++ 2008 eski koduyla çalışıyordum. Deli Kodlama uygulamasına itibaren (yukarıdaki bağlantıdan indirilebilir), ben üç dosya kullandı: asprintf.c, asprintf.hve vasprintf-msvc.c. Diğer dosyalar MSVC'nin diğer sürümleri içindi.

[DÜZENLE] Eksiksiz olması için içerikleri aşağıdaki gibidir:

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

Kullanım ( test.cInsane Coding tarafından sağlanan parçası ):

#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);
  }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.