Search code examples
cpointerscharreturn-valuedynamic-memory-allocation

How to return a char** as a function argument


I have a function that returns a pointer to pointers of chars (char**). The function takes 2 arguments

  • int* num_paths: a pointer to an integer to indicate the number of strings to be returned.
  • int* errno: a pointer to an integer to indicate an error code. This can contain different values, so I cannot simply check if NULL is returned in case of error.

Some example code is written below (with the majority of error checks omitted for simplicity):

char** get_paths(int* num_paths, int* errno) {
    char* path1 = NULL;
    char* path2 = NULL;
    char** paths = NULL;

    if(errno == NULL) {
        printf("Set errno in case of error, but cannot dereference NULL pointer\n");
        goto exit;
    }

    path1 = calloc(1, strlen("foo") + 1);
    path2 = calloc(1, strlen("bar") + 1);

    strcpy(path1, "foo");
    strcpy(path2, "bar");

    *num_paths = 2;
    paths = calloc(1, *num_paths*sizeof(char *));
    paths[0] = path1;
    paths[1] = path2;

    *errno = 0;
exit:
    return paths;
}

int main(void) {
    char** paths = NULL;
    int num_paths = 0;
    int errno = 0;

    paths = get_paths(&num_paths, &errno);
    if(errno != 0) {
        return -1;
    }

    for(int i = 0; i < num_paths; i++) {
        printf("%s\n", paths[i]);
        free(paths[i]);
    }
    free(paths);
}

The problem I have with this is that I can't set the error code in case a NULL pointer is passed as argument for errno. You could argue that this is a user error, but I would still like to avoid this situation in the first place.

So my question is: can I rewrite my get_paths function such that it returns an integer as error code, but also returns a char** through the function arguments without resorting to char*** like in the following example:

int get_paths_3(char*** paths, int* num_paths) {
    char* path1 = NULL;
    char* path2 = NULL;

    path1 = calloc(1, strlen("foo") + 1);
    path2 = calloc(1, strlen("bar") + 1);

    strcpy(path1, "foo");
    strcpy(path2, "bar");

    *num_paths = 2;
    *paths = calloc(1, *num_paths*sizeof(char *));
    (*paths)[0] = path1;
    (*paths)[1] = path2;

    return 0;
}

Solution

  • This is pretty much the only case where "three star" pointers are fine to use. It's fairly common practice in API design to reserve the return value for error codes, so this situation isn't uncommon.

    There are alternatives, but they are arguably not much better. You could abuse the fact that void* can be converted to/from char** but it isn't much prettier and less type safe:

    // not recommended
    
    int get_paths_4 (void** paths, size_t* num_paths)
    {
        char* path1 = calloc(1, strlen("foo") + 1);
        char* path2 = calloc(1, strlen("bar") + 1);
    
        strcpy(path1, "foo");
        strcpy(path2, "bar");
    
        *num_paths = 2;
        char** path_array;
        path_array= calloc(1, *num_paths*sizeof(char *));
        path_array[0] = path1;
        path_array[1] = path2;
        *paths = path_array;
        return 0;
    }
    
    ...
    
    void* vptr;
    size_t n;
    
    get_paths_4 (&vptr, &n);
    char** paths = vptr;
    
    for(size_t i=0; i<n; i++)
    {
      puts(paths[i]);
    }
    

    A more sound alternative might be wrap all your parameters into a single struct type and pass that one as a pointer.