3

インタビュー中に、(とりわけ) 次の機能を実装するように要求されました。

int StrPrintF(char **psz, const char *szFmt, ...);

に似てsprintfいますが、既に割り当てられているストレージの代わりに、関数がそれ自体を割り当て、*psz変数に返す必要があることを除きます。さらに、*pszすでに割り当てられている (ヒープ上にある) 文字列を指している可能性があり、これはフォーマット中に使用される可能性があります。当然、この文字列は適切な手段で解放する必要があります。

戻り値は、新しく作成された文字列の長さ、またはエラーの場合は負になります。

これは私の実装です:

int StrPrintF(char **psz, const char *szFmt, ...)
{
    va_list args;
    int nLen;

    va_start(args, szFmt);

    if ((nLen = vsnprintf(NULL, 0, szFmt, args)) >= 0)
    {
        char *szRes = (char*) malloc(nLen + 1);
        if (szRes)
            if (vsnprintf(szRes, nLen + 1, szFmt, args) == nLen)
            {
                free(*psz);
                *psz = szRes;
            }
            else
            {
                free(szRes);
                nLen = -1;
            }
        else
            nLen = -1;
    }

    va_end(args);
    return nLen;
}

質問の作成者は、この実装にはバグがあると主張しています。特定の難解なシステムで失敗する可能性のある標準的な違反だけでなく、偶然にもほとんどのシステムで失敗する可能性がある「実際の」バグです。

またはintなどのメモリ機能に適した型の代わりに を使用することにも関係しません。たとえば、文字列は「妥当な」サイズです。size_tptrdiff_t

バグが何であるかは本当にわかりません。すべてのポインター演算は問題ありません。を 2 回呼び出しvsnprintfても同じ結果が得られるとは思いもしません。すべての可変引数処理も正しい IMHO です。va_copyは必要ありません ( を使用する呼び出し先の責任ですva_list)。また、x86 では意味がva_copyありません。va_end

誰かが (潜在的な) バグを発見できれば幸いです。

編集:

回答とコメントを確認した後、メモを追加したいと思います。

  • 当然のことながら、変数の状態を監視しながら、デバッガーでのステップバイステップなど、さまざまな入力を使用してコードをビルドして実行しました。最初に自分で試してみずに助けを求めることはありません。問題の兆候、スタック/ヒープの破損などは見られませんでした。また、デバッグ ヒープを有効にしてデバッグ ビルドで実行しました (これはヒープの破損に耐えられません)。
  • 関数は有効なパラメーターで呼び出されると仮定します。つまりpsz、有効なポインター ( と混同しないでください*psz) でszFmtあり、有効な書式指定子であり、すべての可変引数が評価され、書式文字列に対応します。
  • ポインターでの呼び出しfreeNULL、標準に従って問題ありません。
  • 呼び出しvsnprintfは、NULLポインターとサイズ = 0 で問題ありません。結果の文字列の長さを返す必要があります。MS バージョンは、完全に標準に準拠しているわけではありませんが、この特定のケースでは同じことを行います。
  • vsnprintf0 ターミネータを含め、指定されたバッファ サイズを超えません。手段 - それは常にそれを配置するとは限りません。
  • コーディング スタイルは脇に置いておいてください (気に入らない場合は、問題ありません)。
4

5 に答える 5

9

va_copy は必要ありません (va_list を使用する呼び出し先の責任です)

正しくありません。vsnprintfC11標準では、そのような要件は見つかりませんでした。脚注で次のように述べています。

関数 vfprintf、vfscanf、vprintf、vscanf、vsnprintf、vsprintf、および vsscanf が va_arg マクロを呼び出すため、戻り後の arg の値は不確定です。

を呼び出すとvsnprintf、 はva_list値または参照によって渡すことができます (これは、私たちが知っている限りでは不透明な型です)。したがって、最初のものvsnprintfは実際にva_list2番目のものを変更して台無しにすることができます. 推奨される方法は、 を使用してコピーを作成することva_copyです。

実際、この記事によると、x86 ではそうはなりませんが、x64 では起こります。

于 2012-04-09T08:14:09.610 に答える
1

vsnprintf の最初の引数は、次のように null であってはなりません。

http://msdn.microsoft.com/en-us/library/1kt27hek(v=vs.80).aspx

編集 1: null の場合は *psz を解放しないでください!

于 2012-04-09T06:25:35.717 に答える
0

さらに、*psz は、(ヒープ上に) 既に割り当てられている文字列を指している場合があり、これはフォーマット中に使用される可能性があります。

潜在的に再利用できるようにするには*psz、ガベージか有効なヒープ ポインターかを示す何らかの指示が必要です。それを示す関数引数がない場合、NULL センチネル値の唯一の正しい規則を想定できます。つまり、*pszが NULL でない場合、フォーマットするデータが同じスペースに収まる限り、それを再利用できます。関数には以前に割り当てられたメモリの量が表示されないため、次のいずれかを実行できます。 - realloc を使用し、バッファの不必要な移動を避けるためにそれを信頼strlen()するたとえば、長い文字列、短い文字列、元の長い文字列をバッファーに書き込むと、最後の操作でバッファーが不必要に置き換えられます。

明らかに、realloc の方が適しています。

int StrPrintF(char **psz, const char *szFmt, ...)
{
     va_list args;
     int nLen;
     va_start(args, szFmt);
     if ((nLen = vsnprintf(NULL, 0, szFmt, args)) >= 0)
     {
         char *szRes = (char*) realloc(psz, nLen + 1);
                             // ^ realloc does a fresh allocation is *psz == NULL
         if (szRes)
             vsnprintf(*psz = szRes, nLen + 1, szFmt, args); // can't fail
                       // ^ note the assignment....
         else
             nLen = -1;
     }
     va_end(args);
     return nLen;
} 

あまりにも注意してください-Linuxのマンページからprintf()-sprintf()有用な長さを返さない場合は、実装を取得/作成する必要があります....

snprintf() の戻り値に関して、SUSv2 と C99 は互いに矛盾しています。snprintf() が size=0 で呼び出された場合、SUSv2 は 1 未満の不特定の戻り値を規定しますが、C99 はこの場合 str が NULL であることを許可し、出力文字列が十分に大きい場合に書き込まれる文字数としての戻り値 (いつものように)。

于 2012-04-09T07:12:44.837 に答える
-1

完全に答えを出さずに、入力内容を確認してください。

于 2012-04-09T06:21:15.257 に答える