2006/06/23(金)va_start

64bit FreeBSDgcc*1において,なぜかvsnprintfが動作しなかったのですが,ようやく原因がなんとなく判明しました.

どうやら,64bit FreeBSDgccでは,vsnprintfの引数va_listが指しているポインタをいじっている模様.va_listを使う必要がある場合は,毎回va_startva_endを実行した方が無難そうです.

ダメな例

char *test(const char *format, ...)
{
    int size;
    int len;
    char *buf;

    va_list ap;
    va_start(ap, format); // ここで ap を初期化

    while(1){
        buf = (char *)malloc(size);

        // vsnprintfが複数回呼ばれるが,
        // apは最初に初期化されたきり
        len = vsnprintf(buf, size, format, ap);

        if(0 <= len && len < size) break;

        if(0 <= len) m_BufferSize  = len;
        else         m_BufferSize += BUFFER_SIZE;
        free(buf);
    }

    va_end(ap);
    return buf;
}

安全な例

char *test(const char *format, ...)
{
    int size;
    int len;
    char *buf;

    va_list ap;

    while(1){
        buf = (char *)malloc(size);

        va_start(ap, format); // ここで ap を初期化
        // vsnprintfが複数回呼ばれても,
        // 毎回きちんとapが初期化されている
        len = vsnprintf(buf, size, format, ap);
        va_end(ap);

        if(0 <= len && len < size) break;

        if(0 <= len) m_BufferSize  = len;
        else         m_BufferSize += BUFFER_SIZE;
        free(buf);
    }

    return buf;
}

ちなみに,32bitLinux*2FreeBSD*3ではダメな例でも問題ありませんでした.おそらく,va_listに関しての実装が変わっているのでしょう.

*1 : gcc (GCC) 3.4.4

*2 : gcc (GCC) 3.3.1

*3 : バージョン不明