Skip to content

Initializing a struct with a string array member in C

Last week, I was writing some C code which used a struct that contained, among other members, an array of strings, and I wanted to initialize an array of these structs for use elsewhere in the code. My initial attempts failed with compiler messages that I’d never seen before, so instead of taking the easy way out by creating individual structs, explicitly setting each member’s value, and then stuffing all the structs into an array, I tried to figure out exactly how to make initialization work.

Take 1

struct s {
      char *arr[];
};

int main(int argc, char *argv[]) {
   struct s s1 = {argv};

   return 0;
}

Compiler sez:

arrinit.c:2: error: flexible array member in otherwise empty struct

Explanation:
In C, you cannot declare a struct that has a member that is an array without explicit dimensions, with one exception; in C99, you can include one as a “flexible array member,” as long as the struct has at least one other member other than that array and the flexible array member is declared last in the struct definition. See 5.12 Arrays of Length Zero in the GCC docs and this IBM page for more.

Solution:
Luckily, I was planning on putting other members in this struct anyway, so I’ll just add a dummy member.

Take 2

struct s {
      int dummy;
      char *arr[];
};

int main(int argc, char *argv[]) {
   struct s s1 = {0, argv};

   return 0;
}

Compiler sez:

arrinit.c: In function ‘main’:
arrinit.c:7: error: non-static initialization of a flexible array member
arrinit.c:7: error: (near initialization for ‘s1’)

Explanation:
Again, from the GCC docs:

Instead GCC allows static initialization of flexible array members. This is equivalent to defining a new structure containing the original structure followed by an array of sufficient size to contain the data.

Since I’m declaring s1 in the main function body, it’s part of the auto storage class, not the static one.

Solution:
The obvious way to fix this would be to declare s1 as static, either by putting the static keyword at the start of that line or moving the declaration and initialization out main() and straight into the body of the code. I want argv in this struct, so I have to do it the first way, since argv is clearly not defined outside of main().

Take 3

struct s {
      int dummy;
      char *arr[];
};

int main(int argc, char *argv[]) {
   static struct s s1 = {0, argv};

   return 0;
}

Compiler sez:

arrinit.c: In function ‘main’:
arrinit.c:7: error: initializer element is not constant
arrinit.c:7: error: (near initialization for ‘s1.arr[0]’)

Explanation:
You can only initialize static variables with constant values. And “constant values” here doesn’t include const variables, but only literals.

Solution:
Making the struct static felt like a hack anyway; declaring it global only to use it in one function smells something fierce. I think we’re in need of a different approach. Let’s ditch the flexible array member thing altogether and redefine our array member as a pointer to a pointer instead of an array of pointers. Since arr is now a pointer, we can remove the dummy member as well.

Take 4a

struct s {
      char **arr;
};

int main(int argc, char *argv[]) {
   struct s s1 = {argv};

   return 0;
}

Compiler sez:
Success! This should work now, so instead of using argv, let’s get started with code more resembling how the program will use it.

Take 4b

struct s {
      char **arr;
};

int main(int argc, char *argv[]) {
   struct s s1 = { {"a", "b", "c"} };

   return 0;
}

Compiler sez:

arrinit.c:6: warning: braces around scalar initializer
arrinit.c:6: warning: (near initialization for ‘s1.arr’)
arrinit.c:6: warning: initialization from incompatible pointer type
arrinit.c:6: warning: excess elements in scalar initializer
arrinit.c:6: warning: (near initialization for ‘s1.arr’)
arrinit.c:6: warning: excess elements in scalar initializer
arrinit.c:6: warning: (near initialization for ‘s1.arr’)

Explanation:
Arrays and pointers are not identical.

int arr1[] = {1, 2, 3}; // okay
int *arr2 = {1, 2, 3};  // bad; using array initializer with pointer

The curly bracket syntax can only be used when initializing array variables, not pointers. Since arr is now a pointer to a pointer and not an array of pointers, we can’t just one-line it like this.

Solution:
Declare the array of strings first as a separate variable, and then initialize the struct with that variable.

Take 5

struct s {
      char **arr;
};

int main(int argc, char *argv[]) {
   char *arr[] = {"a", "b", "c"};
   struct s s1 = {arr};

   return 0;
}

Compiler sez:
Success! Finally we can start writing some real code.

Coda

#include <stdbool.h>
#include <stdio.h>

struct application {
      char *name;
      char **args;
      bool waitAfterLaunch;
      bool needToOrphan;
};

int main(int argc, char *argv[]) {
   char *gnuplotArgs[] = {"/usr/bin/gnuplot", NULL};
   char *octaveArgs[] = {"/usr/bin/octave", NULL};
   char *bashArgs[] = {"/bin/bash", NULL};
   char *launchArgs[] = {"/usr/bin/gnome-terminal", "-e", argv[0], NULL};
   char *ooWriterArgs[] = {"/usr/bin/oowriter", NULL};
   char *ooCalcArgs[] = {"/usr/bin/oocalc", NULL};
   char *firefoxDefaultArgs[] = {"/usr/bin/firefox", NULL};
   char *firefoxUbuntuArgs[] = {"/usr/bin/firefox", "http://www.ubuntu.com/", NULL};

   struct application apps[] = {
      {"gnuplot", gnuplotArgs, true, false},
      {"octave", octaveArgs, true, false},
      {"bash", bashArgs, true, false},
      {"launch", launchArgs, false, true},
      {"OpenOffice Writer", ooWriterArgs, false, false},
      {"OpenOffice Calc", ooCalcArgs, false, false},
      {"Firefox - default home page", firefoxDefaultArgs, false, false},
      {"Firefox - Ubuntu home page", firefoxUbuntuArgs, false, false}
   };

   return 0;
}

Compiler sez:
Looks okay to me!

Explanation:
As you might be able to infer, this is a snippet of code from an application launcher, where the launchable applications are hardcoded into the program (as opposed to being read from a configuration file). The string arrays are used for the arguments of an execv call. The other members of the struct are for display and forking purposes.

Having to declare the argument arrays ahead of time is pretty ugly, but at least it’s not as bad as initializing every single application struct one data member at a time and then stuffing them all into an array.

Solution:
Use a different language.

Post a Comment

You must be logged in to post a comment.