3

For a project of mine, I created a function to run external commands (reduced to what's important for my question):

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);
    }
    ...
}

The function is called like this:

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

Now, I created a wrapper that uses variadic arguments instead of an array of arguments:

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

EDIT: note on do-while loop:
For the last element, fprintf(stderr, "Argument %i: %s\n", i, NULL) is called, which is valid on GCC and will simply print '(null)'. For other compilers, behavior might be different or undefined. Thanks to @GiovanniCerretani for pointing this out.

The wrapper is called like this:

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

My question:

The wrapper seems to work fine (Linux/x64/GCC 9.2.0), but is this actually a valid way to convert variadic arguments to array? Or does this just work by accident? The documentation on va_* is quite thin, e.g. there's no mention if a string retrieved using va_arg() remains valid when va_arg() is called again or after calling va_end().

11
  • 1
    It shoud be fine, even if the do/while seems bugged as you are printing a NULL pointer with %s. Commented Jan 3, 2020 at 9:54
  • It seems the code shown missed to set the sentinel NULL, that is set args's last element to NULL. Commented Jan 3, 2020 at 10:43
  • @alk the do-while-loop makes sure args's last element is NULL Commented Jan 3, 2020 at 16:23
  • 1
    No need to malloc/free the argument list (args). You can use variable length array (VLA), after counting the number of arguments: char *args[argc[ ; argv[0] = command ; ... Commented Jan 5, 2020 at 6:40
  • @dash-o: thanks, didn't known about VLAs. But it would seem Linus Torvalds does not approve ;) lkml.org/lkml/2018/3/7/621 Commented Jan 5, 2020 at 19:32

3 Answers 3

3

What you are doing will work as expected.

The calls to va_arg get you access to the char * arguments that were passed to the function. The values of these pointers is what was passed to run_command2 meaning their scope is valid at least in the calling function.

So they are valid even after calling va_end.

Sign up to request clarification or add additional context in comments.

Comments

1

As there's not much available on this topic, I decided to implement all possible wrapper variants I could think of and put them in a Gist: Link

Hopefully this helps others facing the same task.

Comments

-2

This args = (const char **) malloc(argc * sizeof(char*)); is quite strange. I would rather first allocate char **args = malloc(sizeof(char*) * (argc)); and then args[i] = malloc(sizeof(char) * (strlen(val) + 1));

You need to allocate the char ** and then allocate each string in this array.

2 Comments

I currently don't copy the strings, I only use references. Thus, args is simply an array of references.
Oh i just figured it out. So it looks good, and you can call var_arg after a va_end, it's valid and working.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.