3

バッファに必要なメモリを割り当てることを使命とする可変個引数C関数を作成し、そのバッファでこの関数に与えられた引数をsprintfします。しかし、私はそれで奇妙な振る舞いを見ています。一度だけ動作します。この関数を2回呼び出すと、segfaultsになります。

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

char *xsprintf(char * fmt, ...)
{
    va_list ap;
    char *part;
    char *buf;
    size_t len = strlen(fmt)+1;

    va_start(ap, fmt);
    while (part = va_arg(ap, char *))
        len += strlen(part);
    va_end(ap);

    buf = (char*) malloc(sizeof(char)*len);

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);

    return buf;
}

int main(int argc, const char *argv[])
{
    char *b;
    b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
    printf("size de buf is %d\n", strlen(b)); //this works. After it, it segfaults.
    /*
    free(b);
    b = NULL;
    */
    b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
    printf("size de buf is %d\n", strlen(b));
    printf("%s", b);
    return 0;
}

このプログラムの出力は次のとおりです。

size de buf is 46
[1]    4305 segmentation fault  ./xsprintftest

私は何か間違ったことをしていますか?va_start1つの関数で複数回使用するべきではありませんか?代替案はありますか?どうもありがとう!:)

4

5 に答える 5

5

を使用する必要がありますvsnprintf。2回使用してください。NULL割り当てが必要なバッファの長さを確認するために宛先/ゼロサイズを使用してから、2回目はバッファを埋めます。そうすれば、すべての引数が文字列でなくても、関数は機能します。

書かれているように、文字列以外の引数(、、、など)があると失敗し%dます。また、文字数を数えることは、引数の数を取得するための有効な方法ではありません。結果は多すぎる(としてエンコードされたリテラル文字がある場合)か、少なすぎる( 、、などの幅/精度指定子にも引数が必要な場合)可能性があります。%x%f%%%%%*s%.*d

于 2010-08-03T15:39:41.937 に答える
3

NULL最後の引数としてxsprintf()に渡します。

b = xsprintf("my favorite fruits are: %s, %s, and %s",
             "coffee", "C", "oranges", (void*)0);

次に、while()ループはNULLを認識し、適切に終了します。

R ..が以下のコメントと別の回答で言及しているように、他のフォーマット引数がある場合、xsprintf関数は失敗します。他の回答で説明されているように、vsprintfを使用することをお勧めします。

ここでの私の意図は、va_argでセンチネルを使用する方法を示すことでした。

于 2010-08-03T14:44:46.307 に答える
2

まず、を使用してみてくださいvsnprintf。それは良い考えです。

しかし、それはあなたの問題ではありません。va_argあなたの問題は、引数があるよりも多くの回数を呼び出すことができないということです。引数の数は返されません。存在する必要がある数を示すパラメーターを渡すか、フォーマット文字列内の特別なトークンの数を抽出して、暗黙的に存在する必要がある数を把握する必要があります。

printfこれが、渡す引数が少なすぎるとプログラムを窒息させる可能性がある理由です。スタックから物事を引き出し続けるだけです。

于 2010-08-03T14:23:49.857 に答える
2

va_arg()問題は、特定の定義された終わりなしでリストにアクセスしているコードのビットで:

va_start(ap, fmt);
while (part = va_arg(ap, char *))
    len += strlen(part);
va_end(ap);

ファシリティには、stdargs.hいつ終了するかを決定するための組み込みのメソッドがありませんva_list()。思いついた規則によって明示的に実行する必要があります。( bstpierreの回答のように)番兵の値を使用するか、カウントを提供することによって。printf()countは、提供される明示的なパラメーターにすることも、暗黙的に指定することもできます(ファミリーのように、フォーマット文字列内のフォーマット指定子の数をカウントするなど)。

もちろん、コードが現在1種類のフォーマット指定子(%s)しかサポートしていないという問題もありますが、これは現時点では意図的なものだと思います。

于 2010-08-03T20:10:53.173 に答える
1

あなたの答えとアイデアをどうもありがとう!だから私はこのように私の関数を書き直しました:

void fatal(const char *msg)/*{{{*/
{
  fprintf(stderr, "program: %s", msg);
  abort ();
}/*}}}*/

void *xmalloc(size_t size)/*{{{*/
{
  register void *value = malloc(size);
  if (value == 0)
    fatal ("Virtual memory exhausted");
  return value;
}/*}}}*/

void *xrealloc(void *ptr, size_t size)/*{{{*/
{
  register void *value = realloc(ptr, size);
  if (value == 0)
    fatal ("Virtual memory exhausted");
  return value;
}/*}}}*/

char *xsprintf(const char *fmt, ...)/*{{{*/
{
    /* Heavily inspired from http://perfec.to/vsprintf/pasprintf */
    va_list args;
    char *buf;
    size_t bufsize;
    char *newbuf;
    size_t nextsize;
    int outsize;
    int FIRSTSIZE = 20;

    bufsize = 0;

    for (;;) {
        if(bufsize == 0){
            buf = (char*)  xmalloc(FIRSTSIZE);
            bufsize = FIRSTSIZE;
        }
        else{
            newbuf = (char *)xrealloc(buf, nextsize);
            buf = newbuf;
            bufsize = nextsize;
        }

        va_start(args, fmt);
        outsize = vsnprintf(buf, bufsize, fmt, args);
        va_end(args);

        if (outsize == -1) {
            /* Clear indication that output was truncated, but no
             * clear indication of how big buffer needs to be, so
             * simply double existing buffer size for next time.
             */
            nextsize = bufsize * 2;

        } else if (outsize == bufsize) {
            /* Output was truncated (since at least the \0 could
             * not fit), but no indication of how big the buffer
             * needs to be, so just double existing buffer size
             * for next time.
             */
            nextsize = bufsize * 2;

        } else if (outsize > bufsize) {
            /* Output was truncated, but we were told exactly how
             * big the buffer needs to be next time. Add two chars
             * to the returned size. One for the \0, and one to
             * prevent ambiguity in the next case below.
             */
            nextsize = outsize + 2;

        } else if (outsize == bufsize - 1) {
            /* This is ambiguous. May mean that the output string
             * exactly fits, but on some systems the output string
             * may have been trucated. We can't tell.
             * Just double the buffer size for next time.
             */
            nextsize = bufsize * 2;

        } else {
            /* Output was not truncated */
            break;
        }
    }
    return buf;
}/*}}}*/

そしてそれは魅力のように働いています!百万回ありがとう:)

于 2010-08-05T15:06:41.490 に答える