snprintf และ Visual Studio 2010


102

ฉันโชคไม่ดีพอที่จะติดอยู่กับ VS 2010 สำหรับโปรเจ็กต์และสังเกตว่าโค้ดต่อไปนี้ยังไม่ได้สร้างโดยใช้คอมไพเลอร์ที่ไม่เป็นไปตามมาตรฐาน:

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

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

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

    return 0;
}

(คอมไพล์ล้มเหลวด้วยข้อผิดพลาด: C3861: 'snprintf': ไม่พบตัวระบุ)

ฉันจำได้ว่านี่เป็นกรณีย้อนกลับไปใน VS 2005 และรู้สึกตกใจที่เห็นว่ามันยังไม่ได้รับการแก้ไข

ไม่มีใครรู้ว่า Microsoft มีแผนจะย้ายไลบรารี C มาตรฐานไปสู่ปี 2010 หรือไม่?


1
... หรือคุณสามารถทำ "#define snprintf _snprintf"
Fernando Gonzalez Sanchez

4
... คุณทำได้ แต่น่าเสียดายที่ _snprintf () ไม่เหมือนกับ snprintf () เนื่องจากไม่รับประกันการสิ้นสุดเป็นโมฆะ
Andy Krouwel

ตกลงดังนั้นคุณจะต้องตั้งค่า mem ให้เป็นศูนย์ก่อนที่จะใช้ _snprintf () ฉันเห็นด้วยกับคุณ การพัฒนาภายใต้ MSVC นั้นแย่มาก ข้อผิดพลาดสับสนเหมือนนรกเช่นกัน
นกฮูก

คำตอบ:


88

เรื่องสั้น:ในที่สุด Microsoft ได้นำ snprintf มาใช้ใน Visual Studio 2015 ในเวอร์ชันก่อนหน้านี้คุณสามารถจำลองได้ดังต่อไปนี้


เวอร์ชันยาว:

นี่คือลักษณะการทำงานที่คาดไว้สำหรับ snprintf:

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

เขียนbuf_size - 1อักขระสูงสุดในบัฟเฟอร์ สตริงอักขระที่เป็นผลลัพธ์จะถูกยกเลิกด้วยอักขระ null เว้นแต่ buf_sizeจะเป็นศูนย์ ถ้าbuf_sizeเป็นศูนย์จะไม่มีอะไรเขียนและ bufferอาจเป็นตัวชี้โมฆะ ค่าส่งคืนคือจำนวนอักขระที่จะถูกเขียนขึ้นโดยสมมติว่าไม่ จำกัดbuf_sizeโดยไม่นับอักขระว่างที่สิ้นสุด

การเผยแพร่ก่อน Visual Studio 2015 ไม่มีการใช้งานที่สอดคล้องกัน มีส่วนขยายที่ไม่เป็นมาตรฐานแทนเช่น_snprintf()(ซึ่งไม่ได้เขียน null-terminator บน overflow) และ_snprintf_s()(ซึ่งสามารถบังคับใช้การสิ้นสุด null แต่จะส่งกลับ -1 เมื่อ overflow แทนจำนวนอักขระที่จะถูกเขียน)

ทางเลือกที่แนะนำสำหรับ VS 2005 ขึ้นไป:

#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

สิ่งนี้จะไม่ยุติสตริงด้วย 0 เสมอไปซึ่งจำเป็นสำหรับโอเวอร์โฟลว์ ตัวที่สอง if ใน c99_vsnprintf ต้องเป็น: if (count == -1) {if (size> 0) str [size-1] = 0; นับ = _vscprintf (รูปแบบ, ap); }
Lothar

1
@Lothar: บัฟเฟอร์จะสิ้นสุดด้วยค่าว่างเสมอ ตาม MSDN: "หากเปิดใช้งานการตัดสตริงโดยการส่ง _TRUNCATE ฟังก์ชันเหล่านี้จะคัดลอกเฉพาะสตริงให้มากที่สุดเท่าที่จะเหมาะสมปล่อยให้บัฟเฟอร์ปลายทางสิ้นสุดด้วยค่าว่างและส่งคืนสำเร็จ"
Valentin Milea

2
ตั้งแต่เดือนมิถุนายน 2014 ยังไม่มีการสนับสนุน C99 แบบ "เต็มรูปแบบ" ใน Visual Studio แม้ว่าจะมีการอัปเดต 2 ก็ตามบล็อกนี้จะให้ข้อมูลสรุปการสนับสนุน C99 สำหรับ MSVC 2013 เนื่องจากฟังก์ชันตระกูล snprintf () เป็นส่วนหนึ่งของมาตรฐาน C ++ 11 แล้ว MSVC ล้าหลังเสียงดังและ gcc ในการใช้งาน C ++ 11!
fnisi

2
ด้วย VS2014 จะมีการเพิ่มมาตรฐาน C99 พร้อม snprintf และ vsnprintf ดูblogs.msdn.com/b/vcblog/archive/2014/06/18/… .
vulcan raven

1
Mikael Lepistö: จริงเหรอ? สำหรับฉัน _snprintf ใช้งานได้ก็ต่อเมื่อฉันเปิดใช้งาน _CRT_SECURE_NO_WARNINGS วิธีแก้ปัญหานี้ใช้งานได้ดีโดยไม่มีขั้นตอนนั้น
FvD

33

snprintfไม่ได้เป็นส่วนหนึ่งของ C89 เป็นมาตรฐานเฉพาะใน C99 ไมโครซอฟท์มีแผนสนับสนุน C99

(แต่ก็เป็นมาตรฐานใน C ++ 0x ... !)

ดูคำตอบอื่น ๆ ด้านล่างสำหรับวิธีแก้ปัญหาชั่วคราว


5
ไม่ใช่วิธีแก้ปัญหาที่ดีอย่างไรก็ตาม ... เนื่องจากมีความแตกต่างในพฤติกรรม snprintf และ _snprintf _snprintf จัดการเทอร์มิเนเตอร์ว่างเปล่าเมื่อจัดการกับพื้นที่บัฟเฟอร์ไม่เพียงพอ
Andrew

7
@DeadMG - ผิด cl.exe สนับสนุนตัวเลือก / Tc ซึ่งสั่งให้คอมไพเลอร์รวบรวมไฟล์เป็นรหัส C นอกจากนี้ MSVC ยังมาพร้อมกับไลบรารี C มาตรฐานอีกด้วย
แอนดรู

3
@DeadMG - รองรับมาตรฐาน C90 เช่นเดียวกับ C99 ไม่กี่บิตทำให้เป็นคอมไพเลอร์ C
แอนดรูว์

15
เฉพาะในกรณีที่คุณมีชีวิตอยู่ระหว่างปี 2533 ถึง 2542
ลูกสุนัข

6
-1, Microsoft _snprintfเป็นฟังก์ชันที่ไม่ปลอดภัยซึ่งทำงานแตกต่างจากsnprintf(ไม่จำเป็นต้องเพิ่มเทอร์มิเนเตอร์ว่าง) ดังนั้นคำแนะนำที่ให้ไว้ในคำตอบนี้จึงทำให้เข้าใจผิดและเป็นอันตราย
interjay

8

หากคุณไม่ต้องการค่าส่งคืนคุณสามารถกำหนด snprintf เป็น _snprintf_s ได้

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

3

ฉันเชื่อว่าเทียบเท่ากับ Windows sprintf_s


7
sprintf_ssnprintfมีลักษณะการทำงานที่แตกต่างจาก
interjay

โดยเฉพาะอย่างยิ่งเอกสาร sprintf_s กล่าวว่า "หากบัฟเฟอร์เล็กเกินไปสำหรับข้อความที่พิมพ์บัฟเฟอร์จะถูกตั้งค่าเป็นสตริงว่าง" ในทางตรงกันข้าม snprintf จะเขียนสตริงที่ถูกตัดทอนไปยังเอาต์พุต
Andrew Bainbridge

2
@AndrewBainbridge - คุณตัดทอนเอกสาร ประโยคเต็มคือ "ถ้าบัฟเฟอร์เล็กเกินไปสำหรับข้อความที่พิมพ์บัฟเฟอร์จะถูกตั้งค่าเป็นสตริงว่างและเรียกใช้ตัวจัดการพารามิเตอร์ที่ไม่ถูกต้อง" ลักษณะการทำงานเริ่มต้นสำหรับหมายเลขอ้างอิงพารามิเตอร์ที่ไม่ถูกต้องคือการยุติโปรแกรมของคุณ หากคุณต้องการตัดทอนด้วยตระกูล _s คุณต้องใช้ snprintf_s และแฟล็ก _TRUNCATE ใช่เป็นเรื่องน่าเสียดายที่ฟังก์ชัน _s ไม่ได้ให้วิธีการตัดทอนที่สะดวก ในทางกลับกันฟังก์ชัน _s ใช้เทมเพลตเมจิกเพื่ออนุมานขนาดบัฟเฟอร์และนั่นก็ยอดเยี่ยม
Bruce Dawson


1

ฉันลองใช้รหัสของ @Valentin Milea แล้ว แต่พบข้อผิดพลาดในการละเมิดการเข้าถึง สิ่งเดียวที่ใช้ได้ผลสำหรับฉันคือการใช้งาน Insane Coding: http://asprintf.insanecoding.org/

โดยเฉพาะฉันกำลังทำงานกับรหัสดั้งเดิมของ VC ++ 2008 จากบ้าการเข้ารหัสของการดำเนินงาน (สามารถดาวน์โหลดได้จากลิงค์ด้านบน) ผมใช้สามไฟล์: asprintf.c, และasprintf.h vasprintf-msvc.cไฟล์อื่น ๆ สำหรับ MSVC เวอร์ชันอื่น

[แก้ไข] เพื่อความสมบูรณ์เนื้อหามีดังนี้:

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

การใช้งาน (เป็นส่วนหนึ่งของtest.cInsane 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);
  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.