snprintf và Visual Studio 2010


102

Tôi thật không may khi bị mắc kẹt khi sử dụng VS 2010 cho một dự án và nhận thấy mã sau vẫn không được tạo bằng trình biên dịch tuân thủ không theo tiêu chuẩn:

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

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

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

    return 0;
}

(không biên dịch được với lỗi: C3861: 'snprintf': không tìm thấy mã định danh)

Tôi nhớ đây là trường hợp trở lại với VS 2005 và bị sốc khi thấy nó vẫn chưa được sửa.

Có ai biết liệu Microsoft có kế hoạch chuyển các thư viện C tiêu chuẩn của họ sang năm 2010 không?


1
... hoặc bạn chỉ có thể làm "#define snprintf _snprintf"
Fernando Gonzalez Sanchez

4
... bạn có thể, nhưng rất tiếc _snprintf () không giống với snprintf () vì nó không đảm bảo kết thúc bằng rỗng.
Andy Krouwel

Ok, vì vậy bạn sẽ cần phải ghi nhớ nó bằng 0 trước khi sử dụng _snprintf (). Tôi cũng đồng ý với bạn. Phát triển theo MSVC thật là khủng khiếp. Các lỗi cũng khó hiểu.

Câu trả lời:


88

Câu chuyện ngắn: Microsoft cuối cùng đã triển khai snprintf trong Visual Studio 2015. Trên các phiên bản trước đó, bạn có thể mô phỏng nó như bên dưới.


Phiên bản dài:

Đây là hành vi mong đợi cho snprintf:

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

Ghi nhiều nhất các buf_size - 1ký tự vào bộ đệm. Chuỗi ký tự kết quả sẽ được kết thúc bằng ký tự rỗng, trừ khi buf_sizebằng không. Nếu buf_sizelà 0, không có gì được viết và buffercó thể là một con trỏ null. Giá trị trả về là số ký tự sẽ được viết với giả sử là không giới hạn buf_size, không tính ký tự rỗng kết thúc.

Các bản phát hành trước Visual Studio 2015 không có triển khai tuân thủ. Thay vào đó, có các phần mở rộng không chuẩn như _snprintf()(không viết null-terminator khi tràn) và_snprintf_s() (có thể thực thi null-end, nhưng trả về -1 khi tràn thay vì số ký tự đã được viết).

Dự phòng được đề xuất cho VS 2005 trở lên:

#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

Điều này sẽ không phải lúc nào cũng kết thúc chuỗi bằng một số 0 được yêu cầu trong một lần tràn. Nếu thứ hai trong c99_vsnprintf phải là: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (format, ap); }
Lothar

1
@Lothar: Bộ đệm luôn được kết thúc bằng null. Theo MSDN: "nếu việc cắt ngắn chuỗi được kích hoạt bằng cách truyền _TRUNCATE, các hàm này sẽ chỉ sao chép nhiều chuỗi nhất có thể, để lại bộ đệm đích không kết thúc và trả về thành công".
Valentin Milea

2
Kể từ tháng 6 năm 2014, vẫn không có hỗ trợ C99 "đầy đủ" trong Visual Studio, ngay cả với Bản cập nhật 2. Blog này cung cấp tóm tắt hỗ trợ C99 cho MSVC 2013. Vì các hàm họ snprintf () hiện là một phần của tiêu chuẩn C ++ 11 , MSVC thua xa clang và gcc trong việc triển khai C ++ 11!
fnisi

2
Với VS2014, các tiêu chuẩn C99 với snprintf và vsnprintf được thêm vào. Xem blog.msdn.com/b/vcblog/archive/2014/06/18/… .
quạ vulcan

1
Mikael Lepistö: Thật không? Đối với tôi _snprintf chỉ hoạt động nếu tôi bật _CRT_SECURE_NO_WARNINGS. Công việc xung quanh này hoạt động tốt mà không cần bước đó.
FvD

33

snprintfkhông thuộc C89. Nó chỉ tiêu chuẩn trong C99. Microsoft không có kế hoạch hỗ trợ C99 .

(Nhưng nó cũng tiêu chuẩn trong C ++ 0x ...!)

Xem các câu trả lời khác bên dưới để biết cách giải quyết.


5
Tuy nhiên, đó không phải là một giải pháp tốt ... vì có sự khác biệt trong hành vi snprintf và _snprintf. _snprintf xử lý dấu chấm hết null một cách chậm chạp khi xử lý không đủ bộ đệm.
Andrew

7
@DeadMG - sai. cl.exe hỗ trợ tùy chọn / Tc, hướng dẫn trình biên dịch biên dịch tệp dưới dạng mã C. Hơn nữa, MSVC cung cấp một phiên bản của thư viện C tiêu chuẩn.
Andrew

3
@DeadMG - tuy nhiên, nó hỗ trợ tiêu chuẩn C90 cũng như một vài bit của C99, biến nó thành trình biên dịch C.
Andrew

15
Chỉ khi bạn sống trong khoảng thời gian từ 1990 đến 1999.
Puppy

6
-1, Microsoft's _snprintflà một chức năng không an toàn, hoạt động khác với snprintf(nó không nhất thiết phải thêm dấu chấm dứt rỗng), vì vậy lời khuyên đưa ra trong câu trả lời này là gây hiểu lầm và nguy hiểm.
tục

8

Nếu bạn không cần giá trị trả về, bạn cũng có thể định nghĩa snprintf là _snprintf_s

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

3

Tôi tin rằng Windows tương đương là sprintf_s


7
sprintf_scư xử khác với snprintf.
giữa

Cụ thể sprintf_s docs nói, "Nếu bộ đệm quá nhỏ cho văn bản được in thì bộ đệm được đặt thành một chuỗi trống". Ngược lại snprintf ghi một chuỗi bị cắt ngắn vào đầu ra.
Andrew Bainbridge

2
@AndrewBainbridge - bạn đã cắt bớt tài liệu. Câu đầy đủ là "Nếu bộ đệm quá nhỏ đối với văn bản được in thì bộ đệm được đặt thành một chuỗi trống và trình xử lý tham số không hợp lệ được gọi." Hành vi mặc định cho xử lý tham số không hợp lệ là chấm dứt chương trình của bạn. Nếu bạn muốn cắt ngắn với họ _s thì bạn cần sử dụng snprintf_s và cờ _TRUNCATE. Vâng, thật không may là các hàm _s không cung cấp một cách cắt bớt thuận tiện. Mặt khác, các hàm _s sử dụng phép mẫu để suy ra kích thước bộ đệm, và điều đó thật tuyệt vời.
Bruce Dawson


1

Tôi đã thử mã của @Valentin Milea nhưng tôi gặp lỗi vi phạm quyền truy cập. Điều duy nhất hiệu quả với tôi là triển khai Insane Coding: http://asprintf.insanecoding.org/

Cụ thể, tôi đã làm việc với mã kế thừa VC ++ 2008. Từ Insane Mã hóa của thực hiện (có thể được tải về từ liên kết ở trên), tôi sử dụng ba tập tin: asprintf.c, asprintf.hvasprintf-msvc.c. Các tệp khác dành cho các phiên bản khác của MSVC.

[EDIT] Để hoàn chỉnh, nội dung của chúng như sau:

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

Cách sử dụng (một phần test.cđược cung cấp bởi 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);
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.