snprintf dan Visual Studio 2010

102

Saya cukup malang untuk terjebak menggunakan VS 2010 untuk sebuah proyek, dan memperhatikan kode berikut masih tidak dibangun menggunakan kompiler yang tidak memenuhi standar yang sesuai:

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

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

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

    return 0;
}

(gagal kompilasi dengan kesalahan: C3861: 'snprintf': pengenal tidak ditemukan)

Saya ingat kasus ini terjadi pada VS 2005 dan saya terkejut melihat itu masih belum diperbaiki.

Apakah ada yang tahu jika Microsoft memiliki rencana untuk memindahkan perpustakaan C standar mereka ke tahun 2010?

Andrew
sumber
1
... atau Anda dapat melakukan "#define snprintf _snprintf"
Fernando Gonzalez Sanchez
4
... Anda bisa, tapi sayangnya _snprintf () tidak sama dengan snprintf () karena tidak menjamin penghentian null.
Andy Krouwel
Oke, jadi Anda perlu memsetnya ke nol sebelum menggunakan _snprintf (). Saya juga setuju dengan Anda. Mengembangkan di bawah MSVC itu buruk. Kesalahannya juga membingungkan.
Burung Hantu

Jawaban:

88

Singkat cerita: Microsoft akhirnya mengimplementasikan snprintf di Visual Studio 2015. Pada versi sebelumnya Anda dapat mensimulasikannya seperti di bawah ini.


Versi panjang:

Berikut adalah perilaku yang diharapkan untuk snprintf:

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

Menulis paling banyak buf_size - 1karakter ke buffer. String karakter yang dihasilkan akan diakhiri dengan karakter null, kecuali buf_sizenol. Jika buf_sizenol, tidak ada yang ditulis dan buffermungkin berupa penunjuk nol. Nilai yang dikembalikan adalah jumlah karakter yang akan ditulis dengan asumsi tidak terbatas buf_size, tidak termasuk karakter null yang diakhiri.

Rilis sebelum Visual Studio 2015 tidak memiliki implementasi yang sesuai. Ada ekstensi non-standar seperti _snprintf()(yang tidak menulis null-terminator pada overflow) dan _snprintf_s()(yang dapat memberlakukan penghentian null, tetapi mengembalikan -1 pada overflow, bukan jumlah karakter yang akan ditulis).

Pengganti yang disarankan untuk VS 2005 dan yang lebih baru:

#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
sumber
Ini tidak akan selalu menghentikan string dengan 0 yang diperlukan untuk overflow. Yang kedua jika di c99_vsnprintf harus: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (format, ap); }
Lothar
1
@Lothar: Buffer selalu diakhiri dengan null. Menurut MSDN: "jika pemotongan string diaktifkan dengan meneruskan _TRUNCATE, fungsi ini hanya akan menyalin string sebanyak yang sesuai, membiarkan buffer tujuan dihentikan, dan kembali berhasil".
Valentin Milea
2
Hingga Juni 2014, masih belum ada dukungan C99 "penuh" di Visual Studio, bahkan dengan Pembaruan 2. Blog ini memberikan penjelasan singkat dukungan C99 untuk MSVC 2013. Karena fungsi keluarga snprintf () sekarang menjadi bagian dari standar C ++ 11 , MSVC tertinggal dari clang dan gcc dalam implementasi C ++ 11!
fnisi
2
Dengan VS2014, standar C99 dengan snprintf dan vsnprintf ditambahkan. Lihat blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
gagak vulcan
1
Mikael Lepistö: Benarkah? Bagi saya _snprintf hanya berfungsi jika saya mengaktifkan _CRT_SECURE_NO_WARNINGS. Solusi ini berfungsi dengan baik tanpa langkah itu.
FvD
33

snprintfbukan bagian dari C89. Ini standar hanya di C99. Microsoft tidak memiliki rencana yang mendukung C99 .

(Tapi itu juga standar di C ++ 0x ...!)

Lihat jawaban lain di bawah untuk mengatasinya.

kennytm
sumber
5
Namun, ini bukan solusi yang baik ... karena ada perbedaan dalam perilaku snprintf dan _snprintf. _snprintf menangani terminator null dengan lambat saat menangani ruang buffer yang tidak mencukupi.
Andrew
7
@DeadMG - salah. cl.exe mendukung opsi / Tc, yang menginstruksikan kompiler untuk mengkompilasi file sebagai kode C. Selanjutnya, MSVC dikirimkan dengan versi pustaka C standar.
Andrew
3
@DeadMG - namun, ini mendukung standar C90 serta beberapa bit C99, menjadikannya kompiler C.
Andrew
15
Hanya jika Anda hidup antara tahun 1990 dan 1999.
Anak Anjing
6
-1, Microsoft _snprintfadalah fungsi tidak aman yang berperilaku berbeda dari snprintf(tidak perlu menambahkan terminator nol), jadi saran yang diberikan dalam jawaban ini menyesatkan dan berbahaya.
interjay
8

Jika Anda tidak membutuhkan nilai kembalian, Anda juga bisa mendefinisikan snprintf sebagai _snprintf_s

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

Saya percaya setara dengan Windows sprintf_s

Il-Bhima
sumber
7
sprintf_sberperilaku berbeda dari snprintf.
interjay
Secara khusus sprintf_s docs mengatakan, "Jika buffer terlalu kecil untuk teks yang sedang dicetak maka buffer diset ke string kosong". Sebaliknya snprintf menulis string yang terpotong ke keluaran.
Andrew Bainbridge
2
@AndrewBainbridge - Anda memotong dokumentasi. Kalimat lengkapnya adalah "Jika buffer terlalu kecil untuk teks yang sedang dicetak, maka buffer disetel ke string kosong dan penangan parameter yang tidak valid dipanggil." Perilaku default untuk pegangan parameter yang tidak valid adalah menghentikan program Anda. Jika Anda ingin pemotongan dengan keluarga _s maka Anda perlu menggunakan snprintf_s dan tanda _TRUNCATE. Ya, sangat disayangkan bahwa fungsi _s tidak memberikan cara pemotongan yang nyaman. Di sisi lain, fungsi _s memang menggunakan keajaiban template untuk menyimpulkan ukuran buffer, dan itu sangat bagus.
Bruce Dawson
2

Pengganti aman lainnya snprintf()dan vsnprintf()disediakan oleh ffmpeg. Anda dapat melihat sumbernya di sini (disarankan).

Marco Pracucci
sumber
1

Saya mencoba kode @Valentin Milea tetapi saya mendapat kesalahan pelanggaran akses. Satu-satunya hal yang berhasil bagi saya adalah penerapan Insane Coding: http://asprintf.insanecoding.org/

Secara khusus, saya bekerja dengan kode warisan VC ++ 2008. Dari pelaksanaan Insane Coding ini (dapat didownload dari link di atas), saya menggunakan tiga file: asprintf.c, asprintf.hdan vasprintf-msvc.c. File lain adalah untuk versi MSVC lainnya.

[EDIT] Lengkapnya isinya sbb:

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

Penggunaan (bagian dari test.cdisediakan oleh 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
sumber