【问题标题】:Platform inconsistencies with vsprintf and va_list平台与 vsprintf 和 va_list 不一致
【发布时间】:2012-05-29 22:00:24
【问题描述】:

背景:我目前正在尝试“扩展”标准 C 格式以支持处理特定结构,类似于 Objective-C 如何扩展 C 格式以支持使用“%@”序列的 NSString。

我遇到的一个问题是 vsprintf 在 OS X 和 Linux 上的表现似乎不同(我已经用 Ubuntu 10.10 和 12.04 进行了测试)。在 OS X 上,它的行为是我认为应该的,在调用 vsprintf 之后,调用 va_arg 返回 ms 指针(好像调用 va_arg 的 vsprintf 函数得到 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);
}

【问题讨论】:

  • POSIX specification之外:“由于这些函数调用va_arg宏,返回后ap的值是未指定的。” (apva_list

标签: c variadic-functions printf


【解决方案1】:

如果您要多次使用参数列表,则需要使用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);

【讨论】:

  • 我现在明白了。谢谢你。是实现我真正想要编写自定义 vsprintf 的唯一方法,我可以在使用 va_list 时依赖它的行为,以便将 foo 中的 ms 变量设置为正确的值?
  • 嗯,我不认为有一个简单的答案。如果您想要完全可移植的代码,您可能需要遍历格式字符串并解析每个格式说明符:对于每个可能的格式说明符,使用 va_arg 获取正确的数据类型,然后将其传递给 sprintf(非v 版本)带有一个格式字符串,仅用于那个说明符(显然对于 %@,你拦截它并做你自己的事情)。
【解决方案2】:

问题在于如何实现va_list 取决于实现——它可能包含用于直接提取参数的所有信息和状态,或者它可能包含指向间接持有状态的指针。因此,将其传递给 vsprintf 可能会复制所有相关状态,也可能不会。

你想要做的是一个类似于 vspintf 的函数,它接受 va_list * 而不是 va_list,所以你可以确保在它返回后你有正确的状态。不幸的是,该标准没有提供任何此类功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-23
    • 2019-10-07
    • 2018-12-25
    • 2021-01-26
    • 2015-04-07
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    相关资源
    最近更新 更多