Johan
Johan

Reputation: 80

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

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

Upvotes: 0

Views: 542

Answers (2)

Lundin
Lundin

Reputation: 213513

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.

Upvotes: 1

ACoelho
ACoelho

Reputation: 341

Unfortunately you cannot mixing return types in C is a terrible mistake and should not be done, you can either:

  • return the pointer you need and add a error code to a variable
  • return the error code and add the pointer to a variable

Both are valid strategies and I'd pick the one that matches the rest of your code to have consistency.

Upvotes: 0

Related Questions