Search code examples
cstructinitializationdynamic-memory-allocationstring-literals

Designated Initialiser on struct causes segfault in strcpy and realloc


I'm using WSL Ubuntu-20.04 LTS and compiling with gcc.

I'm preparing some examples to teach the basics of C to CS Students and as I was reading about designated Initialisers, the following bit of code caused errors,


    typedef enum {male, female} sexe;

    struct human_t {
        char * name;
        char * title;
        sexe gender;
    };

    struct human_t steve = {.name = "Steve", 
                              .title = "Mr.", 
                              .gender = male
                             };

    strcpy(steve.name, "AA");

And reading the manual on strcpy the destination buffer being of larger size than "AA" I'm simply at a loss as to why this doesn't work.

I also observed the following :

    struct human_t steve = {.name = "Steve", 
                              .title = "Mr.", 
                              .gender = male
                             };
    char * new_name = "Gobeldydukeryboo";
    realloc(steve.name, strlen(new_name));
    strcpy(steve.name, new_name);

And this returns the error realloc(): invalid pointer.

Reading the manual on realloc the pointer must have been returned by a malloc call for it to work. I'm having a feeling that designated initialisation does not call malloc, which would explain why realloc fails, it doesn't, however, explain why strcpy fails.

What's going on here ?


Solution

  • struct human_t steve = {.name = "Steve", 
                              .title = "Mr.", 
                              .gender = male
                             };
    

    Here name (and title) is initialized with the address of a string literal. String literals are not allocated from heap and in fact they are (usually) read-only. So string literals should always be assigned to const char* if you want to write more robust code.


    So in the first snippet, strcpy(steve.name, "AA"); will try to write to read-only memory and crashes. If you had made steve.name a const char, it would have produced a compiler error and you would have been safe.


    Then 2nd snippet, realloc(steve.name, strlen(new_name)); will try to resize a memory block which was not allocated with malloc. So you get a crash.

    Also, this is not how you use realloc at all. You need to do it like this:

    char *tmp = realloc(steve.name, strlen(new_name)+1);
    if (!tmp) { perror("realloc error"); exit(1); }
    strcpy(tmp, new_name);
    steve.name = tmp;
    

    To fix, store copy of the string:

    struct human_t steve = {.name = strdup("Steve"), 
                              .title = strdup("Mr."), 
                              .gender = male
                             };
    

    Here strdup is a POSIX function, but not standard C function, so you may need to define it yourself:

    char *strdup(const char *src)
    {
        char *dst = src ? malloc(strlen(src)+1) : NULL;
        if (dst) strcpy(dst, src);
        return dst;
    }