使用stdargs(va_start)的C程序的奇怪行为(SEGFAULT)

时间:2010-08-03 14:19:02

标签: c malloc variadic segmentation-fault

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

#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吗?你有其他选择吗?非常感谢! :)

5 个答案:

答案 0 :(得分:5)

您应该使用vsnprintf。使用它两次。一旦使用NULL目的地/零大小来找出您需要分配的缓冲区的长度,那么第二次填充缓冲区。这样,即使所有参数都不是字符串,您的函数也会起作用。

如上所述,如果存在任何非字符串参数(%d%x%f等),则会失败。计算%个字符的数量并不是获取参数数量的有效方法。您的结果可能太多(如果文字%字符编码为%%)或太少(如果%*s%.*d等也需要参数,则/ precision specifiers)。

答案 1 :(得分:3)

NULL作为最后一个arg传递给xsprintf():

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

然后你的while()循环将看到NULL并正确终止。

正如R ..在下面的评论和另一个答案中提到的,如果有其他格式参数,xsprintf函数将失败。你最好使用vsprintf,如另一个答案中所解释的那样。

我的目的只是为了演示使用va_arg的哨兵。

答案 2 :(得分:2)

首先,尝试使用vsnprintf。这只是一个好主意。

但这不是你的问题。你的问题是你不能比争论更多次调用va_arg。它不返回参数的数量。您必须传入一个参数,告诉它有多少,或者在格式字符串中提取特殊标记的数量,以确定必须隐含的数量。

这就是为什么printf可以阻止你的程序,如果你传递的论点太少;它会不断地将东西从堆栈中拉出来。

答案 3 :(得分:2)

问题在于,在没有特定定义结束的情况下访问va_arg()列表的代码位:

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

stdargs.h工具没有任何内置方法来确定何时发生va_list()的结束 - 您需要通过您提出的约定明确地完成该工作。使用标记值(如bstpierre's answer)或提供​​计数。计数可以是提供的显式参数,也可以是隐式的(例如通过计算格式字符串中的格式说明符的数量,如printf()系列)。

当然,您还遇到的问题是您的代码目前仅支持一种格式说明符(%s),但我认为这是故意的。

答案 4 :(得分: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;
}/*}}}*/

它的工作就像一个魅力!万分感谢:))