C形式の文字列の作成(印刷しない)


100

文字列を受け入れる関数があります。

void log_out(char *);

それを呼び出すには、次のようにその場でフォーマットされた文字列を作成する必要があります。

int i = 1;
log_out("some text %d", i);

ANSI Cでこれを行うにはどうすればよいですか?


ただ、sprintf()intを返すので、次のような少なくとも3つのコマンドを書く必要があります。

char *s;
sprintf(s, "%d\t%d", ix, iy);
log_out(s);

これを短くする方法はありますか?


1
私は関数のプロトタイプが本当にあると信じています:extern void log_out(const char *、...); そうでない場合は、呼び出しが誤っている(引数が多すぎる)ためです。log_out()が文字列を変更する理由はないため、constポインタを使用する必要があります。もちろん、関数に単一の文字列を渡したいと言っているかもしれませんが、できません。1つのオプションは、log_out()関数の可変バージョンを書き込むことです。
ジョナサンレフラー、

回答:


90

sprintfを使用します。

int sprintf ( char * str, const char * format, ... );

フォーマットされたデータを文字列に書き込むフォーマットがprintfで使用された場合に印刷されるのと同じテキストで文字列を構成しますが、コンテンツは印刷される代わりに、strが指すバッファーにC文字列として格納されます。

バッファのサイズは、結果の文字列全体を格納するのに十分な大きさにする必要があります(より安全なバージョンについては、snprintfを参照してください)。

終了ヌル文字は、コンテンツの後に自動的に追加されます。

関数は、formatパラメーターの後、少なくともformatに必要な数の追加の引数を予期します。

パラメーター:

str

結果のC文字列が格納されるバッファーへのポインター。バッファは、結果の文字列を格納するのに十分な大きさである必要があります。

format

printfのフォーマットと同じ仕様に従うフォーマット文字列を含むC文字列(詳細については、printfを参照)。

... (additional arguments)

書式文字列に応じて、関数は追加の引数のシーケンスを期待できます。各引数には、書式文字列の書式指定子(またはnの場合は格納場所へのポインター)を置き換えるために使用される値が含まれます。これらの引数は、少なくともフォーマット指定子で指定された値の数と同じ数でなければなりません。追加の引数は関数によって無視されます。

例:

// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");

35
うわぁ!可能であれば、「n」バリエーション関数を使用します。すなわちsnprintf。それらはあなたにあなたのバッファサイズを数えるようにし、それによってオーバーランを防ぎます。
dmckee ---元モデレーターの子猫

7
はい、しかし彼はANSI C関数を要求しました、そして、私はsnprintfがansiであるか、さらにはposixでさえあるかどうかはっきりしません。
アカッパ2009

7
ああ。私は逃しました。しかし、私は新しい標準に助けられました。「n」バリアントはC99で公式です。FWIW、YMMVなど
dmckee ---元モデレーターの子猫

1
snprintfは、最も安全な方法ではありません。snprintf_sを使用する必要があります。msdn.microsoft.com/en-us/library/f30dzcf6(VS.80).aspxを
2009

2
@Joce-C99 snprintf()ファミリーの関数はかなり安全です。ただし、snprintf_s()ファミリには、(特に切り捨ての処理方法に関して)さまざまな動作があります。ただし、Microsoftの_snprintf()関数は安全ではありません。結果のバッファーが終了しない可能性があるためです(C99 snprintf()は常に終了します)。
マイケルバー

15

POSIX-2008準拠のシステム(最新のLinux)を使用している場合は、安全で便利なasprintf()機能を使用できます。malloc()十分なメモリがあるため、文字列の最大サイズを気にする必要はありません。次のように使用します。

char* string;
if(0 > asprintf(&string, "Formatting a number: %d\n", 42)) return error;
log_out(string);
free(string);

これは、安全な方法で文字列を構築するために取得できる最小限の労力です。sprintf()あなたが質問に与えたコードが深く欠陥があります:

  • ポインタの背後に割り当てられたメモリはありません。文字列をメモリ内のランダムな場所に書き込んでいます!

  • 書いても

    char s[42];

    かっこに何を入れればよいかわからないので、大変なことになります。

  • 「安全な」バリアントを使用した場合でもsnprintf()、文字列が切り捨てられる危険性があります。ログファイルに書き込む場合、それは比較的軽微な問題ですが、有用であった情報を正確に切り捨てる可能性があります。また、最後の行の文字を切り取り、次のログ行を、失敗した行の最後に接着します。

  • あなたがの組み合わせを使用しようとするmalloc()と、snprintf()すべての場合に正しい動作を生成するために、あなたは私が与えられているよりも約2倍多くのコードなどで終わるasprintf()、と基本的の機能を再プログラムasprintf()


それ自体がスタイルパラメータリストをlog_out()受け取ることができるラッパーを提供することを検討している場合は、a を引数として取るprintf()バリアントvasprintf()を使用できva_listます。以下は、そのようなラッパーの完全に安全な実装です。

//Tell gcc that we are defining a printf-style function so that it can do type checking.
//Obviously, this should go into a header.
void log_out_wrapper(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

void log_out_wrapper(const char *format, ...) {
    char* string;
    va_list args;

    va_start(args, format);
    if(0 > vasprintf(&string, format, args)) string = NULL;    //this is for logging, so failed allocation is not fatal
    va_end(args);

    if(string) {
        log_out(string);
        free(string);
    } else {
        log_out("Error while logging a message: Memory allocation failed.\n");
    }
}

2
これasprintf()は、標準のC 2011の一部でも、POSIXの一部でもなく、POSIX 2008または2013の一部でもないことに注意してください。これは、TR 27431-2の一部です。TR24731の「安全な」機能を使用していますか?を
Jonathan Leffler、2014年

魅力のように動作します!「char *」の値がログに正しく出力されない、つまり、フォーマットされた文字列がどういうわけか適切ではないという問題に直面していました。コードは「asprintf()」を使用していました。
Parasrish

11

printfスタイルのフォーマットを使用して作成された文字列を、単純な文字列を取得する既存の関数に簡単に渡すことができるように思えます。あなたは使用してラッパー関数を作成することができるstdarg.h設備やvsnprintf()(あなたのコンパイラ/プラットフォームに応じて、容易に入手できない場合があります):

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

// a function that accepts a string:

void foo( char* s);

// You'd like to call a function that takes a format string 
//  and then calls foo():

void foofmt( char* fmt, ...)
{
    char buf[100];     // this should really be sized appropriately
                       // possibly in response to a call to vsnprintf()
    va_list vl;
    va_start(vl, fmt);

    vsnprintf( buf, sizeof( buf), fmt, vl);

    va_end( vl);

    foo( buf);
}



int main()
{
    int val = 42;

    foofmt( "Some value: %d\n", val);
    return 0;
}

snprintf()ルーチンファミリの適切な実装(または任意の実装)を提供しないプラットフォームの場合、私はHolger Weissのほぼパブリックドメインをsnprintf()正常に使用ました。


現在、vsnprintf_sの使用を検討している可能性があります。
合併

3

するコードがある場合は、コードをlog_out()書き直してください。ほとんどの場合、次のことができます。

static FILE *logfp = ...;

void log_out(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(logfp, fmt, args);
    va_end(args);
}

追加のログ情報が必要な場合は、表示されるメッセージの前または後に出力できます。これにより、メモリ割り当てや不審なバッファサイズなどが節約されます。おそらくlogfp、ゼロ(nullポインター)に初期化し、それがnullかどうかを確認して、必要に応じてログファイルを開く必要があります。ただし、既存のコードはlog_out()とにかくそれを処理する必要があります。

このソリューションの利点は、それをのバリアントであるかのように簡単に呼び出せることprintf()です。実際、これはのマイナーバリアントprintf()です。

へのコードがない場合は、log_out()上記のようなバリアントに置き換えることができるかどうか検討してください。同じ名前を使用できるかどうかは、アプリケーションフレームワークと現在のlog_out()関数の最終的なソースによって異なります。別の必須機能と同じオブジェクトファイルにある場合は、新しい名前を使用する必要があります。それを正確に複製する方法を理解できない場合は、適切な量のメモリを割り当てる他の回答で提供されているようなバリアントを使用する必要があります。

void log_out_wrapper(const char *fmt, ...)
{
    va_list args;
    size_t  len;
    char   *space;

    va_start(args, fmt);
    len = vsnprintf(0, 0, fmt, args);
    va_end(args);
    if ((space = malloc(len + 1)) != 0)
    {
         va_start(args, fmt);
         vsnprintf(space, len+1, fmt, args);
         va_end(args);
         log_out(space);
         free(space);
    }
    /* else - what to do if memory allocation fails? */
}

もちろん、log_out_wrapper()代わりにを呼び出しますlog_out()が、メモリの割り当てなどは一度だけ行われます。私は不要な1バイトだけスペースを過剰に割り当てる権利を留保します-によって返される長さにvsnprintf()終端のnullが含まれるかどうかをダブルチェックしていません。


3

sprintfは使用しないでください。
文字列バッファをオーバーフローさせ、プログラムをクラッシュさせます。
常にsnprintfを使用する


0

私はこれを行っていないので、正しい答えを指摘します。

Cには、<stdarg.h>ヘッダーを使用して、不特定の数のオペランドを取る関数が用意されています。関数をとして定義し、関数void log_out(const char *fmt, ...);va_list内部を取得できます。次にvsprintf()、メモリを割り当て、割り当てられたメモリ、フォーマット、およびを使用して呼び出すことができますva_list

あるいは、これを使用して、sprintf()メモリを割り当て、フォーマットされた文字列を返し、上記のように多かれ少なかれ生成するのと同様の関数を書くことができます。メモリリークになる可能性がありますが、ログアウトしているだけの場合は問題になりません。


-2

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.htmlは、stderrに出力する次の例を示しています。代わりにログ関数を使用するように変更できます。

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

 void
 eprintf (const char *template, ...)
 {
   va_list ap;
   extern char *program_invocation_short_name;

   fprintf (stderr, "%s: ", program_invocation_short_name);
   va_start (ap, template);
   vfprintf (stderr, template, ap);
   va_end (ap);
 }

vfprintfの代わりに、出力先として適切なバッファーを提供する必要がある場合は、vsprintfを使用する必要があります。

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