背景:我目前正在尝试“扩展”标准C格式,支持处理某个结构,类似于Objective-C扩展C格式以允许支持带有“%@”序列的NSString。
我正在努力解决的一个问题是vsprintf在OS X和Linux上的表现似乎不一样(我用Ubuntu 10.10和12.04测试过)。在OS X上,它表现得我认为应该如何,在调用vsprintf之后,调用va_arg返回ms指针(好像vsprintf函数调用va_arg来获取5)。但是,在Linux上,va_list不会从vsprintf更改,并且调用va_arg将返回5.
我真的想找出一种实现此功能的方法,以便它在不同平台上的行为一致。假设您可以期望vsprintf始终更改va_list中的指针以便下次调用va_arg它返回下一个尚未使用的参数时,这是错误的吗?
我尽可能简化了我的代码以演示此问题。在OS X上,此代码打印从malloc返回的指针的正确地址。在Linux上,foo中ms的值变为5,因此它打印5。
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
static void foo(void *, ...);
typedef struct {
char *value;
} mystruct;
int main(int argc, char *argv[]) {
mystruct *ms = malloc(sizeof(mystruct));
foo(NULL, "%d %@", 5, ms);
}
void foo(void *dummy, ...) {
va_list args;
va_start(args, dummy);
char buffer[512];
int buffer_ptr = 0;
int i = 0;
char *format = va_arg(args, char *);
buffer[0] = '\0';
for (i = 0; i < strlen(format); i++) {
if (i <= strlen(format) - 1 && (format[i] == '%' && format[i+1] == '@')) {
vsprintf(buffer, buffer, args);
/* can expect the next argument to be a mystruct pointer */
mystruct *ms = va_arg(args, mystruct *);
buffer[buffer_ptr+1] = '\0';
fprintf(stderr, "%p", ms); /* SHOULD NOT PRINT 5 */
/* concatenate here */
} else {
buffer[buffer_ptr++] = format[i];
buffer[buffer_ptr] = '\0';
}
}
va_end(args);
}
答案 0 :(得分:4)
如果您不止一次使用参数列表,则需要使用va_copy
- 未执行此操作是未定义的行为。您的代码应如下所示:
va_list args;
va_start(args, dummy);
...
char *format = va_arg(args, char *);
...
va_list argsCopy;
va_copy(argsCopy, args);
vsprintf(..., argsCopy);
va_end(argsCopy);
...
mystruct *ms = va_arg(args, mystruct *);
...
va_end(args);
答案 1 :(得分:1)
问题在于实现如何实现va_list
- 它可能包含直接提取参数的所有信息和状态,或者它可能包含指向间接保存状态的东西的指针。因此将其传递给vsprintf可能会复制所有相关状态,也可能不会。
您想要做的是一个类似于vspintf的函数,它需要va_list *
而不是va_list
,因此您可以确保在返回后具有正确的状态。不幸的是,该标准没有提供任何此类功能。