3

我编写了一个可变参数 C 函数,其任务是为缓冲区分配所需的内存,然后在该缓冲区中 sprintf 给该函数的参数。但我看到它有一种奇怪的行为。它只工作一次。如果我对此函数有两次调用,则会出现段错误。

#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_start在一个函数中多次使用吗?你有其他选择吗?非常感谢!:)

4

5 回答 5

5

你应该使用vsnprintf. 使用它两次。一次使用NULL目标/零大小找出您需要分配的缓冲区的长度,然后第二次填充缓冲区。这样,即使所有参数都不是字符串,您的函数也可以工作。

如所写,如果有任何非字符串参数(%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()系列一样)。

当然,您还有一个问题,即您的代码目前仅支持一种格式说明符 ( %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 回答