可変数の引数をprintf / sprintfに渡す方法


83

一部のテキストをフォーマットする「エラー」関数を保持するクラスがあります。可変数の引数を受け入れてから、printfを使用してそれらをフォーマットしたいと思います。

例:

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

Errorメソッドはパラメーターを受け取り、printf / sprintfを呼び出してフォーマットしてから、何かを実行する必要があります。すべてのフォーマットを自分で書きたくないので、既存のフォーマットの使用方法を試してみるのは理にかなっています。

回答:


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

文字列を表示する前に操作したいが、実際に最初にバッファに格納する必要がある場合は、のvsnprintf代わりにを使用してくださいvsprintfvsnprintf偶発的なバッファオーバーフローエラーを防ぎます。


36

vsnprintfを見てください。これにより、http: //www.cplusplus.com/reference/clibrary/cstdio/vsprintf/が必要になります。

最初にva_listarg配列を初期化してから、それを呼び出す必要があります。

そのリンクの例:/ * 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
vsnprintfの2番目の引数は、終了ヌルバイト( '\ 0')を含むバッファー長である必要があります。あなたの代わりに255の関数呼び出しで256を使用できるように
aviggiano

マジックナンバーを渡すのは悪いです... sizeof(buffer)256の代わりに使用
匿名

4

スタックオーバーフローの既存の質問についてもっと読むべきだった。

可変数の引数を渡すC ++も同様の質問です。マイクFには次の説明があります。

いたずらで移植性のないトリックに取り掛かる場合を除いて、渡す引数の数を知らずにprintfを呼び出す方法はありません。

一般的に使用される解決策は、常にvararg関数の代替形式を提供することです。したがって、printfには、...の代わりにva_listを使用するvprintfがあります。...バージョンは、va_listバージョンの単なるラッパーです。

これはまさに私が探していたものです。私は次のようなテスト実装を実行しました:

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

最後の「printf(dest);」不正な形式です-少なくともフォーマット文字列も必要です。
ジョナサンレフラー

文字列はフォーマット文字列、つまりprintf( "a string");であるため、そうではありません。大丈夫です
Lodle 2009年

4
destに "%s"または "%d"が含まれるまで、printf(dest)を使用して、BOOMを実行できます。printf( "%s"、dest)を使用してください。
John Kugelman

コアダンプが最良のシナリオであることを指摘したいだけです。サーバーコードでそれを行うと、ハッカーは朝食用にCPUを使用します。
MickLH 2014

「%.16383s」を使用してみてください。これにより、配列destがオーバーフローから保護されます。(「\ 0」ターミネーターを許可)
eddyq 2016年

3

可変個引数関数を探しています。printf()とsprintf()は可変個引数関数であり、可変数の引数を受け入れることができます。

これには、基本的に次の手順が含まれます。

  1. 最初のパラメーターは、後続のパラメーターの数を示す必要があります。したがって、printf()では、「format」パラメーターがこの指示を示します。5つのフォーマット指定子がある場合、さらに5つの引数(合計6つの引数)が検索されます。最初の引数は整数(例:「myfunction」)にすることができます。 (3、a、b、c) "ここで、" 3 "は" 3つの引数を意味します)

  2. 次に、va_start()などの関数を使用して、連続する各引数をループして取得します。

これを行う方法についてのチュートリアルはたくさんあります-頑張ってください!


3

楕円で関数を使用することは、あまり安全ではありません。ログ関数のパフォーマンスが重要でない場合は、boost :: formatのように演算子のオーバーロードを使用することを検討してください。あなたはこのようなものを書くことができます:

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

次のサンプルは、省略記号で発生する可能性のあるエラーを示しています。

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
これが簡単なことを難しくする方法です。
eddyq 2016年

2
「省略記号を使用して関数を使用することは、あまり安全ではありません。」唯一の安全な代替手段がc ++とboostを含む場合は、「あまり安全ではない」とはどういう意味かを説明し、正しいフォーマット指定子を使用すればprintf関数は完全に安全であることを説明する必要があります。
osvein 2017

2

以下の簡単な例。より大きなバッファーを渡して、バッファーが十分に大きいかどうかをテストする必要があることに注意してください

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
}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.