Search code examples
cstringdynamic-allocation

Return an array of strings from a function


I need to return a char** but when I try to do this, the compiler tells me that I want to return the address of a local variable. How can I do that? I know that I should allocate space for this variable but how? Here is my code, but the second printf doesn't appear and the function returns nothing:

char** parse_cmd(const char* cmdline) {
char** arguments = (char**)malloc(100);
int i;
int j=0, k=0;
printf("%s\n", cmdline);

for(i=0; i<100; i++) {
    arguments[i] = malloc(100);
}

for(i = 0; i < strlen(cmdline); i ++) {
    if(cmdline[i] != ' ') {
        arguments[j][k] = cmdline[i];
        k++;
    } else {
        arguments[j][k] = '\0';
        j++;
        k = 0;
    }
}

printf("%s\n", arguments[1]);

return arguments;
}

Solution

  • Here's the code I assembled - and tested. It uses dynamic memory allocation for both the argv argument list and for each argument as it is assembled. The function release_cmd() releases the allocated space. The function cleanup() is internal and releases allocated space on a failure, before returning a null double-pointer. This simplifies the error handling. There's a minimal test harness in the prompt() function and main(). I haven't run in under valgrind, but the MacOS X implementation of malloc() quite often spots problems so I'm moderately confident there's no gross memory abuse - but there could be an off-by-one leak. I haven't tested the cleanup() code by faking an allocation failure.

    #include <ctype.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void release_cmd(char **argv)
    {
        for (size_t i = 0; argv[i] != 0; i++)
            free(argv[i]);
        free(argv);
    }
    
    static char **cleanup(size_t argc, char **argv)
    {
        argv[argc] = 0;
        release_cmd(argv);
        return 0;
    }
    
    char **parse_cmd(const char* cmdline)
    {
        size_t argc = 2;
        char **argv = malloc(argc * sizeof(char *));
    
        if (argv == 0)
            return 0;
    
        size_t j = 0;  // Index into argv
        size_t len = strlen(cmdline);
    
        for (size_t i = 0; i < len; i++)
        {
            while (isspace(cmdline[i]))
                i++;
            if (cmdline[i] == '\0')
                break;
            if (j > argc - 2)
            {
                size_t newc = (argc * 2);
                char **newv = realloc(argv, newc * sizeof(char *));
                if (newv == 0)
                    return cleanup(argc, argv);
                argv = newv;
                argc = newc;
            }
            size_t argl = 2;    // Length of argument string
            argv[j] = malloc(argl);
            size_t k = 0;       // Index into argv[j]
            while (cmdline[i] != '\0' && !isspace(cmdline[i]))
            {
                if (k > argl - 2)
                {
                    size_t newl = argl * 2;
                    char  *news = realloc(argv[j], newl);
                    if (news == 0)
                        return cleanup(argc, argv);
                    argv[j] = news;
                    argl    = newl;
                }
                argv[j][k++] = cmdline[i++];
            }
            argv[j][k] = '\0';
            argv[j] = realloc(argv[j], k+1);    // Shrink to fit!
            j++;
        }
        argv[j] = 0;
        argv = realloc(argv, (j+1)*sizeof(*argv));  // Shrink to fit!
    
        return argv;
    }
    
    static int prompt(const char *prompt, char *buffer, size_t bufsiz)
    {
        printf("%s", prompt);
        return (fgets(buffer, bufsiz, stdin) != 0);
    }
    
    int main(void)
    {
        char line[1024];
    
        while (prompt("cmd? ", line, sizeof(line)) != 0)
        {
            char **argv = parse_cmd(line);
            char **args = argv;
            while (*args)
                puts(*args++);
            release_cmd(argv);
        }
        putchar('\n');
        return 0;
    }