如何在C语言中将可变参数转换为数组

3

为了我的一个项目,我创建了一个运行外部命令的函数(只保留与我的问题相关的部分):

int run_command(char **output, int *retval, const char *command, const char* const args[])
{
    ...
    pid_t pid = fork();
    if (pid == 0) {
        ...
        execvp(command, (char * const *)args);
    }
    ...
}

这个函数的调用方式是这样的:

char *output;
int retval;
const char *command = "find";
const char* const args[] = { command, "/tmp", "-type", "f", NULL };
run_command(&output, &retval, command, args);

现在,我创建了一个使用可变参数而不是参数数组的包装器:
int run_command2(char **output, int *retval, const char *command, ...)
{
    va_list val;
    const char **args = NULL;
    int argc;
    int result;

    // Determine number of variadic arguments
    va_start(val, command);
    argc = 2; // leading command + trailing NULL
    while (va_arg(val, const char *) != NULL)
        argc++;
    va_end(val);

    // Allocate args, put references to command / variadic arguments + NULL in args
    args = (const char **) malloc(argc * sizeof(char*));
    args[0] = command;
    va_start(val, command);
    int i = 0;
    do {
        fprintf(stderr, "Argument %i: %s\n", i, args[i]);
        i++;
        args[i] = va_arg(val, const char *);
    } while (args[i] != NULL);
    va_end(val);

    // Run command, free args, return result
    result = run_command(output, retval, command, args);
    free(args);
    return result;
}

编辑:do-while循环的说明:
对于最后一个元素,这将导致fprintf(stderr, "Argument %i: %s\n", i, NULL),在GCC上是有效的,并将打印“(null)”,但在其他编译器上可能未定义。感谢@GiovanniCerretani指出。

这样调用:

char *output;
int retval;
run_command2(&output, &retval, "find", "/tmp", "-type", "f", NULL);

这个包装器可以正常工作(Linux/x64/GCC 9.2.0),但这是否是将可变参数转换为数组的有效方法?还是仅仅是偶然起作用?
关于 va_* 的文档很少,例如没有提示当再次调用 va_arg() 或调用 va_end() 时,使用 va_arg() 检索的字符串是否仍然有效。

1
即使do/while循环似乎有错误,由于你正在使用%s打印一个NULL指针,这应该是可以的。 - undefined
看起来代码中漏掉了设置哨兵NULL的部分,即将args的最后一个元素设置为NULL - undefined
@alk do-while循环确保args的最后一个元素是NULL。 - undefined
1
不需要为参数列表(args)分配内存。您可以使用可变长度数组(VLA),在计算参数数量后:char *args[argc] ; argv[0] = command ; ... - undefined
@GiovanniCerretani:在GCC中,printf("%s", NULL)实际上是有效的,并打印“(null)”,但总体上你是对的,在其他编译器中,这将导致未定义的行为。 - undefined
显示剩余6条评论
3个回答

3

你正在做的工作将按预期进行。

va_arg 的调用使您可以访问传递给函数的 char * 参数。这些指针的值是传递给 run_command2 的,这意味着它们的范围至少在调用函数中有效。

因此,即使在调用 va_end 之后,它们仍然是有效的。


1

由于这个主题上没有太多可用的内容,我决定实现我能想到的所有可能的封装变体,并将它们放在一个Gist(代码片段)中:链接

希望这对其他面临同样任务的人有所帮助。


-2
这个 args = (const char **) malloc(argc * sizeof(char*)); 看起来很奇怪。我更倾向于先分配 char **args = malloc(sizeof(char*) * (argc));,然后再分配 args[i] = malloc(sizeof(char) * (strlen(val) + 1));

你需要先分配 char **,然后再为数组中的每个字符串分配内存。


1
我目前不复制字符串,只使用引用。因此,args只是一个引用数组。 - undefined
哦,我刚才搞清楚了。所以看起来还不错,你可以在va_end之后调用var_arg,这是有效的,并且可以正常工作。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接