2016-06-13 5 views
4

私は非常に古い(> 10y)Cコードを現代のLinuxに移植しています。vsnprintf()はどのようにして安全に呼び出しますか?

char* strVPrintf(const String fmt, va_list ap) 
{ 
    /* Guess we need no more than 50 bytes. */ 
    int n, size = 50; 
    char* p = (char*)memMalloc(size), q; 

    while (1) { 
    /* Try to print in the allocated space. */ 
    n = vsnprintf(p, size, fmt, ap); 
    /* If that worked, return the string. */ 
    if (n > -1 && n < size) { 
     break; 
    } 
    /* Else try again with more space. */ 
    if (n > -1)    /* glibc 2.1 */ 
     size = n + 1;   /* precisely what is needed */ 
    else     /* glibc 2.0 */ 
     size *= 2;    /* twice the old size */ 
    p = memRealloc(p, size); 
    } 

    q = strRegister(p); 
    memFree(p); 
    return q; 
} 

著者は標準vsnprintf()関数が返すことを想定しているようだ:私はカスタム書かれたvsnprintf()のラッパー内セグメンテーションフォールトを取得しています(どうやらそのタスクが重複して出力文字列やインターンそれらを検出することです)書き込まれた文字数を返し、すべてのargを書式設定するのに十分なスペースがない場合は、単にセンチネル値を返します。つまり、バッファサイズを推測し、必要に応じて増やすことができます。

私のシステム(Ubuntu 14.04、glibc 2.19)では、vnprintfは指定されたスペースに対して引数が多すぎると呼び出されたときにセグメント化エラーを起こします。その間にsnprintf()ファミリのセマンティクスが大幅に変更されましたか?バッファスペースを十分に確保する現代的な方法は何ですか?

+0

'男vsnprintf':実行私はこれを取得すると

#include <stdio.h> #include <stdlib.h> #include <stdarg.h> void foo(const char *fmt, va_list ap) { #ifdef BAD vprintf(fmt, ap); #else va_list apc; va_copy(apc, ap); vprintf(fmt, apc); va_end(apc); #endif vprintf(fmt, ap); } void bar(const char *fmt, ...) { va_list ap; va_start(ap, fmt); foo(fmt, ap); va_end(ap); } int main(int argc, char **argv) { bar("foo %s\n", "bar"); return 0; } 

ここで何が起こっているかを確認する簡単なテストがあります*関数のsnprintf()とvsnprintf()がありませんsizeバイト以上(終端のヌルバイト( '\ 0')を含む)を書き込みます。この制限のために出力が切り捨てられた場合、戻り値は、十分なスペースが使用可能であった場合に最終文字列に書き込まれる文字数(終了NULLバイトを除く)です。したがって、サイズ以上の戻り値は、出力が切り捨てられたことを意味します。*プログラムのその部分は正しいようです。 – EOF

+2

メモリ割り当ての失敗をチェックしていません。 – user694733

+0

'String'とは何ですか?私はそれが 'char * 'だと思っていますが、その情報も提供するべきです。 –

答えて

7

これは、SunOSの4を除くすべてのオペレーティングシステム上でsnprintfvsnprintfを使用する正しい方法です(通常はvsnprintfはSTDARG.H互換性があります) 20年後に廃止されました)、あなたの問題はどこか他の場所にあります。

純粋な推測を行い、va_list apをそれを消費するvsnprintfに渡していることがほとんど確実であると言い、次の呼び出しでリセットされることを期待しています。これは間違っており、何年か前にgccで動作しなくなりました(これは特定のアーキテクチャでしか機能しなかったためです)。

変更:

va_list apc; 
va_copy(apc, ap); 
n = vsnprintf(p, size, fmt, apc); 
va_end(apc); 

そして、それは場合に役立ちます参照してください。

n = vsnprintf(p, size, fmt, ap); 

へ。

$ cc -o foo foo.c && ./foo 
foo bar 
foo bar 
$ cc -DBAD -o foo foo.c && ./foo 
foo bar 
foo ���� 
+1

そうです、これが問題でした。いつものように短い文字列が問題なく印刷されていたのに長い文字列がクラッシュしたので、私はバッファオーバーランの問題を想定していました。私は、未定義の振る舞いが決して信頼されないことを知っていて、あなたがそれをチャンスとして与えるならば、違う種類の問題に偽ります! –

2

私はコードを理解しているので、その目的は、出力文字列をバッファに完全に書き込むためにsprintfが必要とするサイズを検出することです。あなたのためにこれを行う機能があります:asprintf(またはここではvasprintf)。

プロトタイプ:

int vasprintf(char **strp, const char *fmt, va_list ap); 

ただ、次のようにそれを使用する:

String strVPrintf(const String fmt, va_list ap) 
{ 
    char *ans; 
    int n; 
    n = vasprintf(&ans, fmt, ap); 
    // do the checks 
    return ans; 
} 

この機能を使用すると、私が思うに、これ以上このラッパーは必要ありません。

+0

'asprintf()'と 'vasprintf()'は非標準関数です。 –

+1

@AndrewHenle OPはglibcを使用します。 – Boiethios

+0

OPは、移植性のないコードを書きたいという願いを表明していません。 –

2

あなたにわからないが、私のmanページ可変長引数のリストは言う:
これらのマクロは、彼らが交換する歴史的なマクロとの互換性はありません

互換性を。 の下位互換バージョンは、インクルードファイル<varargs.h>にあります。

あなたはそれが非常に古いコードで言ったように、多分このルーチンで受信va_listのはvsnprintfによって期待va_listのではありません。あなたはまず、他は必ず1つのヘッダを持つすべてのパラメータを抽出しようとする必要があります持っている(

関連する問題