9

va_listを使用して、レンダリングされる文字列を作成しています。

void Text2D::SetText(const char *szText, ...)

これはすべて問題ありませんが、ユーザーはアプリケーションの実行中に言語を変更できるようになりました。すべてのテキスト文字列を再生成し、初期化後にテキストビットマップを再キャッシュする必要があります。va_listを保存して、テキストを生成する必要があるときはいつでも使用したいと思います。

もう少し背景を説明するために、これは、翻訳しているキー文字列に動的なデータが含まれている場合に発生する必要があります。

"Player Score:%d"

それが私が翻訳する必要のあるキーストリングです。初期化後に再変換する必要がある場合に備えて、後で使用するために(テキストを初期化する関数の範囲外で)va_listで提供された番号を保持したいと思います。できれば、vsnprintfで使用するためにva_listのコピーを保持したいと思います。

私はこれを行うためにいくつかの調査を行い、いくつかの方法を見つけました。そのうちのいくつかは、それが適切な方法であるかどうかを疑問視しています(安定性と移植性の観点から)。

4

5 に答える 5

10

この質問は本当に私の興味をそそりました。また、私自身の仕事でも同様の問題に直面するので、ここで考案した解決策も役立つかもしれません。

要するに、後で使用するために変数引数をキャッシュする概念実証コードを作成しました。これは以下にあります。

以下のコードをWindowsとIntelベースのLinuxの両方で正しく動作させることができました。Linuxではgcc、WindowsではMSVCでコンパイルしました。gccからva_start()を悪用することについて、2回繰り返される警告があります。この警告は、makefileで無効にすることができます。

このコードがMacコンパイラで動作するかどうか知りたいです。コンパイルするには少し調整が必要な場合があります。

このコードは次のとおりです。

  • ANSI C標準で定義されているva_start()の乱用が極端です。
  • 昔ながらのバイト指向のC。
  • va_list変数をポインターとして使用する場合、理論的には移植性がありません。

va_listマクロはC標準のものであり、C ++機能ではないため、malloc()とfree()の使用は非常に慎重でした。あなたの質問のタイトルがC++に言及していることは承知していますが、C ++スタイルのコメントを使用する以外に、完全にC互換のソリューションを作成しようとしました。

このコードには、フォーマット文字列処理にいくつかのバグや移植性がないことも間違いありません。これは、プロが使用できる完成したコードサンプルではなく、2時間で一緒にハッキングした概念実証として提供します。

その免責事項は言った、私がしたのと同じくらい楽しい結果を見つけてくれることを願っています!これはハックするのに素敵な質問でした。結果の病気でねじれた性質は私に深い腹笑いを与えます。;)

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

#define VERBOSE 0

#ifdef WINDOWS
#define strdup _strdup
#endif

/ *
 * struct cached_printf_args
 *
 *これは動的に割り当てられたポインタタイプとして使用されます
 *変数引数のコピーを保持するメモリ。構造体
 *は、printf()のコピーを受け取るconstchar*で始まります。
 *フォーマット文字列。
 *
 *長さがゼロの配列で構造体を終了する目的は
 *配列名を次のデータのシンボルにすることができます
 *その構造体。この場合、追加のメモリは常に
 *実際に変数argsを含むように割り当てられ、cached_printf_args-> args
 *は、その追加のバッファスペースの開始アドレスに名前を付けます。
 *
 * /
struct cached_printf_args
{{
    const char * fmt;
    char args [0];
};


/ *
 * copy_va_args-printf()形式の文字列とva_listを受け入れます
 *引数。
 *
 *のva_listポインタを*p_arg_srcの
 *フォーマット文字列の仕様に準拠します。
 *
 *提供されたarg_destがNULLでない場合、各引数
 *は*p_arg_srcからarg_destにコピーされます
 *フォーマット文字列に。
 *
 * /
int copy_va_args(const char * fmt、va_list * p_arg_src、va_list arg_dest)
{{
    const char * pch = fmt;

    intprocessing_format = 0;

    while(* pch)
    {{
        if(processing_format)
        {{
            スイッチ(* pch)
            {{
            // case'!':FormatMessage()などの一部の実装では有効である可能性があります
            ケース「0」:
            ケース「1」:
            ケース「2」:
            ケース「3」:
            ケース「4」:
            ケース「5」:
            ケース「6」:
            ケース「7」:
            ケース「8」:
            ケース「9」:
            場合 '。':
            場合 '-':

                //上記のすべての文字は、%とtype-specifierの間で有効です。
                //引数をキャッシュする効果がないため、ここでは単に引数をキャッシュします
                //無視されます。
                壊す;

            ケース'l':
            ケース「私」:
            ケース'h':
                printf("サイズプレフィックスはまだサポートされていません。\n");
                exit(1);

            ケース'c':
            ケース「C」:
                //'...'を通過すると、charはintにプロモートされました
            ケース'x':
            ケース'X':
            ケース'd':
            ケース'i':
            ケース'o':
            ケース'u':
                if(arg_dest)
                {{
                     *((int *)arg_dest)= va_arg(* p_arg_src、int);
                     va_arg(arg_dest、int);
                }
                そうしないと
                    va_arg(* p_arg_src、int);
#if VERBOSE
                printf( "va_arg(int)、ap =%08X、&fmt =%08X \ n"、* p_arg_src、&fmt);
#endif
                processing_format = 0;
                壊す;

            ケース's':
            ケース「S」:
            ケース'n':
            ケース'p':
                if(arg_dest)
                {{
                    *((char **)arg_dest)= va_arg(* p_arg_src、char *);
                    va_arg(arg_dest、char *);
                }
                そうしないと
                    va_arg(* p_arg_src、char *);
#if VERBOSE
                printf( "va_arg(char *)、ap =%08X、&fmt =%08X \ n"、* p_arg_src、&fmt);
#endif
                processing_format = 0;
                壊す;

            ケース'e':
            ケース'E':
            ケース'f':
            ケース'F':
            ケース'g':
            ケース「G」:
            ケース'a':
            ケース「A」:
                if(arg_dest)
                {{
                    *((double *)arg_dest)= va_arg(* p_arg_src、double);
                    va_arg(arg_dest、double);
                }
                そうしないと
                    va_arg(* p_arg_src、double);
#if VERBOSE
                printf( "va_arg(double)、ap =%08X、&fmt =%08X \ n"、* p_arg_src、&fmt);
#endif
                processing_format = 0;
                壊す;
            }
        }
        else if('%' == * pch)
        {{
            if(*(pch + 1)=='%')
                pch ++;
            そうしないと
                processing_format = 1;
        }
        pch ++;
    }

    0を返します。
}

/ *
 * printf_later-printf()形式の文字列と変数を受け入れます
 *引数。
 *
 *NULLまたは構造体へのポインタを返します。
 *後でva_XXX()マクロとともに使用して取得する
 *キャッシュされた引数。
 *
 *呼び出し元は、返された構造体と同様にfree()する必要があります
 *その中のfmtメンバー。
 *
 * /
struct cached_printf_args * printf_later(const char * fmt、...)
{{
    structcached_printf_args*キャッシュ;
    va_list ap;
    va_list ap_dest;
    char * buf_begin、* buf_end;
    int buf_len;

    va_start(ap、fmt);
#if VERBOSE
    printf( "va_start、ap =%08X、&fmt =%08X \ n"、ap、&fmt);
#endif

    buf_begin =(char *)ap;

    //宛先がNULLの「コピー」呼び出しを行います。これは進歩します
    //ソースポイントであり、必要なものを計算できます
    //キャッシュバッファサイズ。
    copy_va_args(fmt、&ap、NULL);

    buf_end =(char *)ap;

    va_end(ap);

    //引数に必要なバイトを計算します:
    buf_len = buf_end --buf_begin;

    if(buf_len)
    {{
        //偽造に使用される「ヘッダー」バイトを追加します
        //最後の非可変引数を上げます。へのポインタ
        //とにかく、フォーマット文字列のコピーが必要です。
        //後で引数を解凍するには、覚えておく必要があります
        //それらがどのタイプか。
        buf_len + = sizeof(struct cached_printf_args);

        キャッシュ=malloc(buf_len);
        if(キャッシュ)
        {{
            memset(cache、0、buf_len);
            va_start(ap、fmt);
            va_start(ap_dest、cache-> fmt);

            //実際に引数をスタックからバッファにコピーします
            copy_va_args(fmt、&ap、ap_dest);

            va_end(ap);
            va_end(ap_dest);

            //フォーマット文字列のコピーを割り当てます
            cache-> fmt = strdup(fmt);

            //文字列の割り当てに失敗した場合は、割り当てを逆にして、
            //ポインタ
            if(!cache-> fmt)
            {{
                free(キャッシュ);
                キャッシュ=NULL;
            }
        }
    }

    キャッシュを返します。
}

/ *
 *free_printf_cache-キャッシュと動的メンバーを解放します
 *
 * /
void free_printf_cache(struct cached_printf_args * cache)
{{
    if(キャッシュ)
        free((char *)cache-> fmt);
    free(キャッシュ);
}

/ *
 * print_from_cache-引数が格納されているvprintf()を呼び出します
 *割り当てられた引数キャッシュ
 *
 *
 * gccでコンパイルするには、この関数を次のように宣言する必要があります。
 *可変引数を受け入れます。それ以外の場合は、va_start()を使用します
 *マクロは許可されていません。追加の引数がに渡される場合
 *この関数、それらは読み取られません。
 * /
int print_from_cache(struct cached_printf_args * cache、...)
{{
    va_list arg;

    va_start(arg、cache-> fmt);
    vprintf(cache-> fmt、arg);
    va_end(arg);
}

int main(int argc、char * argv)
{{
    structcached_printf_args*キャッシュ;

    //変数引数のキャッシュとフォーマット文字列のコピーを割り当てます。
    cache = printf_later( "これらの引数のすべての%dは、後で%s fo%cで使用され、おそらく%g秒で使用されます。"、10、 "stored"、'r'、2.2);

    //出力へのコメント付きでタイムラインを示します。
    printf( "このステートメントは、キャッシュの作成とディスプレイへの移動の間に介在します。\ n"

    //これは、キャッシュされたprintfからの出力を実際に表示する呼び出しです。
    print_from_cache(cache);

    //動的メモリを空きストアに戻すことを忘れないでください
    free_printf_cache(cache);

    0を返します。

}
于 2009-10-14T09:19:06.750 に答える
8

それ自体を保存するva_listことは素晴らしい考えではありません。標準では、va_list引数が、、およびで機能va_start()するva_arg()ことだけが要求されますva_end()。私の知る限り、va_listはコピー構築可能であるとは限りません。

ただし、を保存する必要はありませんva_list。提供された引数を(おそらくvoid *の)ベクトルなどの別のデータ構造にコピーし、後で通常の方法で取得します。タイプに注意する必要がありますが、C++のprintfスタイルの関数の場合は常にそうです。

于 2009-10-13T21:32:48.313 に答える
3

を使用できますva_copy()。例を次に示します。

va_list ap;
va_list tmp;
va_copy(tmp, ap);
//do something with tmp
va_end(tmp);
于 2012-06-23T11:43:14.393 に答える
2

「va_listで提供された番号を保持する」についてあなたが説明することは、これにアプローチする方法です。

は、スタック上の一時メモリへのva_listポインタを維持します(C標準ではいわゆる「自動ストレージ」)。変数argsを持つ関数が返されると、この自動ストレージはなくなり、コンテンツは使用できなくなります。このため、それ自体のコピーを単純に保持することはできませんva_list。参照するメモリには、予測できないコンテンツが含まれます。

与えた例では、そのメッセージを再作成するときに再利用される2つの整数を格納する必要があります。処理する必要のある異なるフォーマット文字列の数に応じて、アプローチは異なる場合があります。

完全に一般的なタイプのアプローチでは、次のことを行う必要があります。

  • cache_arguments()変数引数で見つかった値の動的メモリバッファを作成する" "関数を記述します。
  • これは、、、マクロとともに-styleフォーマット文字列cache_arguments()を使用します。型指定子に従って型を取得する必要があります。printf()va_startva_argva_endprintf()sizeof(double) != sizeof(int)
  • プラットフォームで期待されるのと同じ配置とパディングを使用して、引数をメモリキャッシュに格納しva_arg()ます。varargs.h(ファイルを読んでください。)
  • vsnprintf()によって作成されたポインタの代わりに、このキャッシュされたメモリバッファを操作するための呼び出しを取得しますva_start()

上記の項目はすべて、LinuxやWindowsを含むほとんどのプラットフォームで可能です。

翻訳について検討したい項目は、語順の問題です。英語で書かれているもの:

プレーヤーサムは20ポイントを獲得しました。

一部の(人間の)言語では、次の語順に類似した語順でのみ流暢に書くことができます。

プレイヤーサムは20ポイントを獲得しました。

このため、Win32 FormatMessage()APIは-のprintf()ようなフォーマット文字列を使用しますが、次のようにパラメータに番号が付けられるという機能上の違いがあります。

プレイヤー%1は%2!d!を獲得しました ポイント。
%2!d!ポイントはプレイヤー%1によって獲得されました。

(文字列型は引数ごとに想定されるため、%1と同等です%1!s!

もちろん、Win32 APIを使用していない場合もありますが、フォーマットされた引数の語順を変更する機能は、私が概念として紹介しようとしているものです。これをソフトウェアに実装することもできます。

于 2009-10-13T21:56:19.633 に答える
0

Cでそれを行う方法は、代わりに引数の構造体を関数に送信することです。構造体を参照で渡し、後で再利用できるように構造体を共通の場所にコピー(memcpy)する必要があります。送信したのと同じ方法で、宛先で構造体を解きます。'設定と取得'のための構造体のテンプレートを保持します。

于 2017-11-19T23:21:10.883 に答える