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

发布于 2024-09-12 19:48:06 字数 1206 浏览 7 评论 0原文

我编写了一个可变参数 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 吗?你还有其他选择吗?多谢! :)

I wrote a variadic C function which mission is to allocate the needed memory for a buffer, and then sprintf the args given to this function in that buffer. But I'm seeing a strange behavior with it. It works only once. If I have two calls for this function it 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;
}

here's the output of this program:

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

Am I doing something wrong? Shouldn't I have used va_start multiple times in a single function? Do you have any alternatives? Thanks a lot! :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

最单纯的乌龟 2024-09-19 19:48:06

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

正如所写,如果存在任何非字符串参数(%d%x%f 等),它将失败。并且计算 % 字符的数量并不是获取参数数量的有效方法。您的结果可能太多(如果有编码为 %% 的文字 % 字符)或太少(如果 %*s 还需要参数) code>、%.*d 等宽度/精度说明符)。

You should be using vsnprintf. Use it twice. Once with a NULL destination/zero size to find out the length of the buffer you need to allocate, then a second time to fill the buffer. That way your function will work even if all the arguments are not strings.

As written, it will fail if there are any non-string arguments (%d, %x, %f, etc.). And counting the number of % characters is not a valid way to get the number of arguments. Your result could be too many (if there are literal % characters encoded as %%) or too few (if arguments are also needed for %*s, %.*d, etc. width/precision specifiers).

小草泠泠 2024-09-19 19:48:06

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

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

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

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

我在这里的目的只是演示哨兵与 va_arg 的使用。

Pass NULL as the last arg to xsprintf():

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

Then your while() loop will see the NULL and terminate properly.

As R.. mentions in the comment below and in another answer, the xsprintf function will fail if there are other format arguments. You are better off using vsprintf as explained in the other answer.

My intent here was simply to demonstrate the use of a sentinel with va_arg.

独行侠 2024-09-19 19:48:06

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

但这不是你的问题。您的问题是您调用 va_arg 的次数不能超过参数的次数。它不返回参数的数量。您必须传入一个参数来告诉它有多少个,或者提取格式字符串中特殊标记的数量以计算出必须隐式有多少个。

这就是为什么如果你传递的参数太少,printf 会阻塞你的程序。它只会不断地从堆栈中取出东西。

First off, try using vsnprintf. It's just a good idea.

That's not your problem, though. Your problem is that you can't call va_arg more times than there are arguments. It doesn't return the number of arguments. You must either pass in a parameter telling it how many there are, or extract the number of special tokens in the format string to figure out how many there must implicitly be.

That's the reason why printf can choke your program if you pass it too few arguments; it will just keep pulling things off the stack.

☆獨立☆ 2024-09-19 19:48:06

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

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

stdargs.h 设施没有任何内置的-in 方法来确定 va_list() 何时结束 - 您需要通过您提出的约定明确完成该操作。使用哨兵值(如 bstpierre 的答案),或者提供计数。计数可以是提供的显式参数,也可以是隐式参数(例如通过计算格式字符串中格式说明符的数量,如 printf() 系列所做的那样)。

当然,您还存在一个问题,即您的代码当前仅支持一种格式说明符 (%s),但我认为此时这是有意的。

The problem is that in the bit of code where you're accessing the va_arg() list without a particular defined end:

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

The stdargs.h facilities don't have any built-in method for determining when the end of the va_list() occurs - you need to have that explicitly done by a convention you come up with. Either using a sentinel value (as in bstpierre's answer), or by having a count provided. A count can be an explicit parameter that's provided, or it can be implicit (such as by counting the number of format specifiers in the format string like the printf() family does).

Of course, you also have the issue that your code currently only supports the one kind of format-specifier (%s), but I assumed that that's intentional at this point.

野侃 2024-09-19 19:48:06

非常感谢您的回答和想法!所以我重写了我的函数,如下所示:

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;
}/*}}}*/

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

Thanks a lot for your answers and ideas! So I rewrote my function like this:

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;
}/*}}}*/

And it's working like a charm! Thanks a million times :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文