我编写了一个可变参数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
吗?你有其他选择吗?非常感谢! :)
答案 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;
}/*}}}*/
它的工作就像一个魅力!万分感谢:))