execvp() in C, and passing the arguments

441 Views Asked by At

Regarding the execvp() function which takes in the string of the program to run and the array of strings of the arguments for that program (with the first string being the name of the program), I know we can just create a static array by doing char* args[] = {"arg1", "arg2" , NULL}, but what if we wanted to make it dynamic since we don't how large the array needs to be until runtime when I pass in a number specifying the arg count. I figured I could just create and pass in a 2d array dynamically to exec, but then when would I free that array since no code can run after the exec call?

A solution would be to make the static array sufficiently large where we can just overwrite the values, but its inefficient to do so wasting that much space.

1

There are 1 best solutions below

0
Luis Colorado On

You may be considering using an array allocated dynamically, and reallocating entries on a scheduled basis. Let's say we want to get the argument list from the standard input, and call execvp with a dynamically created array.

I have created a pseudo object that has a char **data field, the count of allocated entries for the arguments, and the actual size_t capacity field that allows us to check how many elements it still can handle without having to be reallocated.

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define LINE_SIZE               80
#define CAPACITY_INCREMENT      32

struct dynamic_array {
    char **data;
    size_t count;
    size_t capacity;
};

/* this function ensures, by reallocating dynamically the array
 * (initially set to NULL) will have space for at least one more
 * element. */
void
add_to_array(
    struct dynamic_array *inst, /* the instance */
    char *elem) /* the element to add */
{
    /* if the array if full, we need to reallocate.  This routine
     * is not well suited to make big arrays, as each
     * reallocation requires normally to copy the array contents,
     * and this can be costly, but using the a large increment
     * minimizes the number of reallocations */
    if (inst->count == inst->capacity) {
        inst->capacity += CAPACITY_INCREMENT;
        inst->data = realloc(inst->data,
                inst->capacity * sizeof *inst->data);
    }
    inst->data[inst->count++] = elem;
} /* add_to_array */

int main()
{
    /* we build the string vector dynamically from standard
     * input, one argument per line, then call execvp() with it.
     */
    struct dynamic_array array = {0};

    char argument[LINE_SIZE];
    while (fgets(argument, sizeof argument, stdin)) {
        char *p = strtok(argument, "\n"); /* chop last \n */
        assert(p != NULL);
        p = strdup(p); /* allocate memory for the element */
        assert(p != NULL);
        /* ... and add it to the array */
        add_to_array(&array, p);
    }
    /* Now, we need to add a NULL pointer */
    add_to_array(&array, NULL);

    execvp(array.data[0], array.data);

    fprintf(stderr, "Error: execvp: %s\n",
            strerror(errno));

    exit(EXIT_FAILURE);
} /* main */

This will read the program name from the standard input, will allocate strings, calling strdup() for each argument and realloc() when the array has to be reallocated to hold more elements, and it builds an array of strings (quasy optimun size, but speed efficient, by no calling realloc each time a new string has to be added) suitable to be given to execvp().

Of course, after you are finished, you must iterate all entries free()ing them and finally free() the data element of the structure.

Each reallocation normally forces the realloc() routine to copy the array to a new place, if the memory allocated cannot be resized, and this is very inefficient, in case you need to reallocate every time you add a new element. For that reason, the capacity field allows to batch reallocations and do them only once per CAPACITY_INCREMENT additions.