14

私はC++で作業しています。

sprintf (具体的には _snprintf_s のような安全なカウント バージョンですが、考え方は同じです) を使用して、潜在的に非常に長い形式の文字列を書きたいと考えています。おおよその長さはコンパイル時に不明であるため、大きな静的バッファーに依存するのではなく、動的に割り当てられたメモリを使用する必要があります。特定の sprintf 呼び出しに必要な文字数を決定する方法はありますか?

私のフォールバックは、フォーマット文字列の長さを2倍にして試してみることです。機能する場合は素晴らしいです。機能しない場合は、バッファーのサイズを 2 倍にして、もう一度やり直します。合うまで繰り返す。正確には最も賢い解決策ではありません。

C99 は、長さを取得するために snprintf に NULL を渡すことをサポートしているようです。他に何もなければ、その機能をラップするモジュールを作成できると思いますが、私はそのアイデアに夢中ではありません。

「/dev/null」/「nul」への fprintf が代わりに機能する可能性がありますか? 他のアイデアはありますか?

編集:または、sprintf を「チャンク」して、書き込み中にピックアップする方法はありますか? それが可能であれば、バッファを埋めて処理し、中断したところから再充填を開始できます。

4

7 に答える 7

24

のマニュアルページにsnprintfは次のように書かれています。

   戻り値
       正常に戻ると、これらの関数は次の数を返します。
       印刷された文字(末尾に使用される「\0」は含まれません)
       文字列への出力)。関数snprintfおよびvsnprintfはしません
       サイズバイト以上(末尾の「\ 0」を含む)を書き込みます。もしも
       この制限のために出力が切り捨てられ、その後戻り値
       文字数です(末尾の「\ 0」は含まれません)
       十分であれば、これは最終的な文字列に書き込まれます
       スペースが利用可能でした。したがって、サイズ以上の戻り値
       出力が切り捨てられたことを意味します。(以下も参照してください
       注。)出力エラーが発生した場合、負の値は
       戻ってきた。

これが意味するのは、サイズ0で呼び出すことができるということです。snprintf何も書き込まれず、戻り値は、文字列に割り当てる必要のあるスペースの量を示します。

int how_much_space = snprintf(NULL, 0, fmt_string, param0, param1, ...);
于 2009-02-05T07:31:28.263 に答える
5

他の人が述べsnprintf()たように、出力が切り捨てられるのを防ぐためにバッファに必要な文字数を返します。必要なサイズを取得してから、適切なサイズのバッファを使用するには、バッファ長パラメータを 0 にして呼び出すだけです。

snprintf()効率をわずかに改善するために、通常の場合に十分な大きさのバッファーを使用して呼び出し、出力が切り捨てられた場合にのみ を 2 回呼び出すことができます。その場合にバッファーが適切に解放されるようにするためにauto_buffer<>、動的メモリを処理するオブジェクトを使用することがよくあります (通常の場合、ヒープ割り当てを回避するために、スタックに既定のバッファーがあります)。 .

Microsoft コンパイラを使用している場合、MS には非標準_snprintf()があり、バッファが常に null で終了するとは限らず、バッファの大きさを示さないという重大な制限があります。

Microsoft の非サポートを回避するために、Holger Weissのほぼパブリック ドメインsnprintf()を使用しています。

もちろん、MS 以外の C または C++ コンパイラが見つからないsnprintf()場合は、上記のリンクのコードも同様に機能するはずです。

于 2009-02-05T08:16:10.390 に答える
3

私は2段階のアプローチを使用します。一般に、出力文字列の大部分は特定のしきい値を下回り、それより大きくなるのはごくわずかです。

ステージ1、4Kなどの適切なサイズの静的バッファーを使用します。書き込まれる文字数を制限できるためsnprintf()、バッファオーバーフローは発生しません。返されるのは、バッファ十分に大きい場合に書き込まれた文字数snprintf()です。

の呼び出しsnprintf()が4K未満を返す場合は、バッファーを使用して終了します。述べたように、呼び出しの大部分はそれを行う必要があります。

ステージ2に入ると、そうでないものもあります。への呼び出しがsnprintf()4Kバッファーに収まらない場合は、少なくとも、必要なバッファーの大きさがわかります。

malloc()を使用して、それを保持するのに十分な大きさのバッファを割り当ててsnprintf()から、その新しいバッファに再度割り当てます。バッファを使い終わったら、それを解放します。

以前はシステムに取り組んでいましたsnprintf()が、ファイルハンドルを接続し/dev/nullて使用fprintf()することで、同じ結果を達成しました。/ dev / nullは常に、指定された量のデータを取得することが保証されているため、実際にそのサイズからサイズを取得し、必要に応じてバッファーを割り当てます。

すべてのシステムが備えているわけではないのでsnprintf()(たとえば、Microsoft Cにあることを理解しています)、同じ仕事をする機能を見つけるか、ソリューション_snprintf()に戻る必要があるかもしれません。fprintf /dev/null

snprintf()また、サイズチェックとバッファへの実際のデータの間でデータを変更できるかどうかにも注意snprintf()してください(つまり、スレッドを削除します)。サイズが大きくなると、バッファオーバーフローが破損します。

関数に渡されたデータは、返されるまでその関数に排他的に属するという規則に従う場合、これは問題にはなりません。

于 2009-02-05T07:18:39.600 に答える
1

価値があるのasprintfは、この機能を管理する GNU 拡張機能です。出力引数としてポインタをフォーマット文字列と可変数の引数とともに受け取り、結果を含む適切に割り当てられたバッファのアドレスをポインタに書き戻します。

次のように使用できます。

#define _GNU_SOURCE
#include <stdio.h>

int main(int argc, char const *argv[])
{
    char *hi = "hello"; // these could be really long
    char *everyone = "world";
    char *message;
    asprintf(&message, "%s %s", hi, everyone);
    puts(message);
    free(message);
    return 0;
}

これが誰かを助けることを願っています!

于 2014-09-03T23:29:35.757 に答える
0

C ++を使用しているので、実際にはどのバージョンのsprintfも使用する必要はありません。最も簡単な方法は、std::ostringstreamを使用することです。

std::ostringstream oss;
oss << a << " " << b << std::endl;

oss.str()ossに書き込んだ内容を含むstd::stringを返します。oss.str().c_str()を取得するために使用しますconst char *。長期的には処理がはるかに簡単になり、メモリリークやバッファオーバーランがなくなります。一般に、C ++でのようなメモリの問題を心配している場合は、言語を最大限に活用していないため、設計を再考する必要があります。

于 2009-02-05T19:51:17.940 に答える
0

CodeProject:CString-clone Using StandardC++をご覧ください。バッファサイズを拡大して提案したソリューションを使用します。

// -------------------------------------------------------------------------
    // FUNCTION:  FormatV
    //      void FormatV(PCSTR szFormat, va_list, argList);
    //
// DESCRIPTION: // This function formats the string with sprintf style format-specs. // It makes a general guess at required buffer size and then tries // successively larger buffers until it finds one big enough or a // threshold (MAX_FMT_TRIES) is exceeded. // // PARAMETERS: // szFormat - a PCSTR holding the format of the output // argList - a Microsoft specific va_list for variable argument lists // // RETURN VALUE: // -------------------------------------------------------------------------

void FormatV(const CT* szFormat, va_list argList)
{
#ifdef SS_ANSI

    int nLen    = sslen(szFormat) + STD_BUF_SIZE;
    ssvsprintf(GetBuffer(nLen), nLen-1, szFormat, argList);
    ReleaseBuffer();
#else
    CT* pBuf            = NULL;
    int nChars          = 1;
    int nUsed           = 0;
    size_type nActual   = 0;
    int nTry            = 0;

    do  
    {
        // Grow more than linearly (e.g. 512, 1536, 3072, etc)

        nChars          += ((nTry+1) * FMT_BLOCK_SIZE);
        pBuf            = reinterpret_cast<CT*>(_alloca(sizeof(CT)*nChars));
        nUsed           = ssnprintf(pBuf, nChars-1, szFormat, argList);

        // Ensure proper NULL termination.
        nActual         = nUsed == -1 ? nChars-1 : SSMIN(nUsed, nChars-1);
        pBuf[nActual+1]= '\0';


    } while ( nUsed < 0 && nTry++ < MAX_FMT_TRIES );

    // assign whatever we managed to format

    this->assign(pBuf, nActual);
#endif
}

于 2009-02-05T07:19:25.417 に答える
0

私はあなたが話しているのと同じ機能を探しましたが、私の知る限り、C++ は現在 C99 で追加された機能 (snprintf など) を組み込んでいないため、C99 メソッドのような単純なものは C++ では利用できません。 .

あなたの最善の策は、おそらく stringstream オブジェクトを使用することです。明確に書かれた sprintf 呼び出しよりも少し面倒ですが、うまくいきます。

于 2009-02-05T07:17:41.677 に答える