Can I declare an array of different length arrays in C without variable names?

183 Views Asked by At

I am trying to figure out if it's possible to do the following, but without having to declare the individual variables...

static char *set_list1[] = { "set", "one", NULL };
static char *set_list2[] = { "set", "one", "negative", NULL };
static char *set_list3[] = { "set", NULL };
static char *set_list4[] = { "set", "one", "new", "item", "here", NULL };
static char **set_list[] = { set_list1, set_list2, set_list3, set_list4, NULL };

Basically I need an array of pointers, each pointer then points to a variable length array of strings (which happen to be terminated with a NULL pointer.

Obviously I can declare it like the above, but is there a way to do this without having to declare each one with a name?

Intuitively this feels like it should work, but doesn't...

static char **my_array[] = {
    { "one", "two", NULL },
    { "another", NULL },
    { "more", "items", "here", NULL },
    NULL
};

Any help appreciated

3

There are 3 best solutions below

5
Vlad from Moscow On BEST ANSWER

For example in C23 you may use compound literals like

static char **my_array[] = {
    ( static char * [] ){ "one", "two", NULL },
    ( static char * [] ){ "another", NULL },
    ( static char * [] ){ "more", "items", "here", NULL },
    NULL
};

Or the same declaration but without the storage class specifier static

static char **my_array[] = {
    ( char * [] ){ "one", "two", NULL },
    ( char * [] ){ "another", NULL },
    ( char * [] ){ "more", "items", "here", NULL },
    NULL
};
0
arfneto On

You can just go on with this same concept, over and over again, as in

    const char** other_list[] = {
        (const char*[]){"set", "one", NULL},
        (const char*[]){"set", "one", "negative", NULL},
        (const char*[]){"set", NULL},
        (const char*[]){"set", "one", "new", NULL},
        (const char*[]){
            "set", "one", "new", "item", "here", NULL},
    NULL};

a complete example

// https://stackoverflow.com/questions/77976322/can-i-declare-an-array-of-different-length-arrays-in-c-without-variable-names

#include <stdio.h>

int so_print(const char*[], const char*);

int main(void)
{
    const char** other_list[] = {
        (const char*[]){"set", "one", NULL},
        (const char*[]){"set", "one", "negative", NULL},
        (const char*[]){"set", NULL},
        (const char*[]){"set", "one", "new", NULL},
        (const char*[]){
            "set", "one", "new", "item", "here", NULL},
    NULL};

    size_t n_lists = 0;
    while (other_list[n_lists] != NULL) ++n_lists;
    printf("total of %llu lists\n", n_lists);
    if (n_lists == 0) return 0;
    for (size_t ix = 0; ix < n_lists; ++ix)
    {
        char msg[20];
        sprintf(msg,"\nList %llu:\n", ix);
        so_print(other_list[ix], msg);
    }
    return 0;
}

int so_print(const char* list[], const char* msg)
{
    if (msg != NULL) printf("%s", msg);
    size_t n_words = 0;
    while (list[n_words] != NULL)
    {
        printf("%llu\t%s\n", n_words, list[n_words]); 
        ++n_words;
    }
    printf("\n%llu words in this list\n", n_words); 
    return 0;
}

output

total of 5 lists

List 0:
0       set
1       one

2 words in this list

List 1:
0       set
1       one
2       negative

3 words in this list

List 2:
0       set

1 words in this list

List 3:
0       set
1       one
2       new

3 words in this list

List 4:
0       set
1       one
2       new
3       item
4       here

5 words in this list
6
ad absurdum On

Since C99 you can use compound literals to create unnamed objects. But until C23 you could not use storage class specifiers with compound literals. This feature is not widely supported currently (GCC 13 is the only one supporting it as reported here).

In practice the lack of support for storage class specifiers is often not a problem.

For compound literals occurring inside a function body the storage duration is automatic, so the lifetime of such objects ends when the function body is exited. Note that calling a function from within another function does not exit the caller, so you can create an object with a compound literal in main and call functions which use that object.

For compound literals occurring outside of a function body the storage duration is static.

Here is a little example program showing how compound literals might be used.

#include <stdio.h>

// `my_array` and the objects it references have static duration.
char **my_array[] = {
    (char *[]){ "one", "two", NULL},
    (char *[]){ "another", NULL},
    (char *[]){ "more", "items", "here", NULL },
    NULL
};

void print_words(char **arr[]) {
    for (size_t i = 0; arr[i]; i++) {
        for (size_t j = 0; arr[i][j]; j++) {
            puts(arr[i][j]);
        }
    }
}

char ***make_my_array(void) {
    // These unnamed objects have static storage duration: C23 only.
    return (static char **[]){
        (static char *[]){ "one", "two", NULL},
        (static char *[]){ "another", NULL},
        (static char *[]){ "more", "items", "here", NULL },
        NULL
    };
}

int main(void) {
    puts("Printing `my_array`:");
    for (size_t i = 0; my_array[i]; i++) {
        for (size_t j = 0; my_array[i][j]; j++) {
            puts(my_array[i][j]);
        }
    }

    // `my_array_auto` and the objects it references have automatic storage duration.
    char **my_array_auto[] = {
        (char *[]){ "one", "two", NULL},
        (char *[]){ "another", NULL},
        (char *[]){ "more", "items", "here", NULL },
        NULL
    };

    putchar('\n');
    puts("Printing `my_array_auto` with a function:");
    print_words(my_array_auto);

    char ***my_array_c23 = make_my_array();
    puts("Printing `my_array_c23` with a function:");
    print_words(my_array_c23);
}

I tested here on the Godbolt Compiler Explorer using GCC 13.2 to be sure that the C23 features are working.