Cách chuyển số đối số biến cho printf / sprintf


83

Tôi có một lớp chứa một hàm "lỗi" sẽ định dạng một số văn bản. Tôi muốn chấp nhận một số đối số có thể thay đổi và sau đó định dạng chúng bằng printf.

Thí dụ:

class MyClass
{
public:
    void Error(const char* format, ...);
};

Phương thức Lỗi sẽ nhận các tham số, gọi printf / sprintf để định dạng nó và sau đó thực hiện điều gì đó với nó. Tôi không muốn tự mình viết tất cả các định dạng nên việc thử và tìm ra cách sử dụng định dạng hiện có là rất hợp lý.

Câu trả lời:


151
void Error(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    vfprintf(stderr, format, argptr);
    va_end(argptr);
}

Nếu bạn muốn thao tác chuỗi trước khi hiển thị và thực sự cần nó được lưu trữ trong bộ đệm trước tiên, hãy sử dụng vsnprintfthay thế vsprintf. vsnprintfsẽ ngăn lỗi vô tình tràn bộ đệm.


36

hãy xem vsnprintf vì điều này sẽ làm những gì bạn muốn http://www.cplusplus.com/reference/clibrary/cstdio/vsprintf/

bạn sẽ phải init mảng va_list arg trước, sau đó gọi nó.

Ví dụ từ liên kết đó: / * ví dụ vsprintf * /

#include <stdio.h>
#include <stdarg.h>

void Error (char * format, ...)
{
  char buffer[256];
  va_list args;
  va_start (args, format);
  vsnprintf (buffer, 255, format, args);


  //do something with the error

  va_end (args);
}

6
Đối số thứ hai của vsnprintf phải là độ dài bộ đệm, bao gồm byte null kết thúc ('\ 0'). Vì vậy, bạn có thể sử dụng 256 trong cuộc gọi chức năng thay vì 255.
aviggiano

và vượt qua các con số ma thuật là BAD ... sử dụng sizeof(buffer)thay vì 256.
Anonymouse

4

Tôi nên đọc thêm về các câu hỏi hiện có trong phần tràn ngăn xếp.

C ++ Truyền số đối số biến là một câu hỏi tương tự. Mike F có lời giải thích sau:

Không có cách nào để gọi (ví dụ) printf mà không biết bạn đang chuyển bao nhiêu đối số cho nó, trừ khi bạn muốn tham gia vào các thủ thuật nghịch ngợm và không di động.

Giải pháp thường được sử dụng là luôn cung cấp một dạng thay thế của các hàm vararg, vì vậy printf có vprintf lấy một va_list thay cho .... Các phiên bản ... chỉ là các trình bao bọc xung quanh các phiên bản va_list.

Điều này thật đúng với gì mà tôi đã tìm kiếm. Tôi đã thực hiện triển khai thử nghiệm như sau:

void Error(const char* format, ...)
{
    char dest[1024 * 16];
    va_list argptr;
    va_start(argptr, format);
    vsprintf(dest, format, argptr);
    va_end(argptr);
    printf(dest);
}

Cuối cùng 'printf (dest);' được định dạng sai - nó cũng cần ít nhất một chuỗi định dạng.
Jonathan Leffler

Nó không phải là chuỗi là chuỗi định dạng tức là printf ("một chuỗi"); vẫn ổn
Lodle

4
Bạn có thể sử dụng printf (dest) lên cho đến khi hàm cuối chứa "% s" hoặc "% d", sau đó BOOM . Vui lòng sử dụng printf ("% s", dest).
John Kugelman

Chỉ muốn chỉ ra rằng một kết xuất lõi là trường hợp tốt nhất, hãy làm điều đó trong mã máy chủ và tin tặc sẽ có CPU của bạn cho bữa sáng.
MickLH

Hãy thử sử dụng "% .16383s" và điều đó sẽ bảo vệ đích của mảng khỏi bị tràn. (cho phép dấu chấm hết '\ 0')
eddyq

3

Bạn đang tìm kiếm các hàm đa dạng . printf () và sprintf () là các hàm khác nhau - chúng có thể chấp nhận một số đối số thay đổi.

Về cơ bản, điều này bao gồm các bước sau:

  1. Tham số đầu tiên phải cung cấp một số chỉ báo về số lượng tham số theo sau. Vì vậy, trong printf (), tham số "format" đưa ra dấu hiệu này - nếu bạn có 5 định dạng định dạng, thì nó sẽ tìm kiếm thêm 5 đối số (với tổng số 6 đối số.) Đối số đầu tiên có thể là một số nguyên (ví dụ: "my function (3, a, b, c) "trong đó" 3 "biểu thị" 3 đối số)

  2. Sau đó lặp qua và truy xuất từng đối số kế tiếp, sử dụng các hàm va_start (), v.v.

Có rất nhiều hướng dẫn về cách làm điều này - chúc bạn may mắn!


3

Sử dụng các hàm với dấu chấm lửng không an toàn lắm. Nếu hiệu suất không quan trọng đối với hàm nhật ký, hãy xem xét sử dụng nạp chồng toán tử như ở định dạng boost ::. Bạn có thể viết một cái gì đó như thế này:

#include <sstream>
#include <boost/format.hpp>
#include <iostream>
using namespace std;

class formatted_log_t {
public:
    formatted_log_t(const char* msg ) : fmt(msg) {}
    ~formatted_log_t() { cout << fmt << endl; }

    template <typename T>
    formatted_log_t& operator %(T value) {
        fmt % value;
        return *this;
    }

protected:
    boost::format                fmt;
};

formatted_log_t log(const char* msg) { return formatted_log_t( msg ); }

// use
int main ()
{
    log("hello %s in %d-th time") % "world" % 10000000;
    return 0;
}

Mẫu sau minh họa các lỗi có thể xảy ra với dấu ba chấm:

int x = SOME_VALUE;
double y = SOME_MORE_VALUE;
printf( "some var = %f, other one %f", y, x ); // no errors at compile time, but error at runtime. compiler do not know types you wanted
log( "some var = %f, other one %f" ) % y % x; // no errors. %f only for compatibility. you could write %1% instead.

5
Đây là cách biến một điều dễ trở thành khó.
eddyq

2
"Sử dụng các hàm với dấu chấm lửng không an toàn lắm." nếu giải pháp thay thế an toàn duy nhất của bạn liên quan đến c ++ và boost, bạn nên giải thích ý bạn là "không an toàn lắm" và đề cập rằng các hàm printf hoàn toàn an toàn nếu bạn sử dụng các chỉ định định dạng chính xác.
osvein

2

Ví dụ đơn giản dưới đây. Lưu ý rằng bạn nên chuyển vào một bộ đệm lớn hơn và kiểm tra xem bộ đệm có đủ lớn hay không

void Log(LPCWSTR pFormat, ...) 
{
    va_list pArg;
    va_start(pArg, pFormat);
    char buf[1000];
    int len = _vsntprintf(buf, 1000, pFormat, pArg);
    va_end(pArg);
    //do something with buf
}

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.