1

I'm testing out what we recently learned in class about structs and pointers by writing a small C program. However, after running it i came across a segmentation fault (core dumped) error. Can someone help me figure out where exactly that's caused? Did i miss any dangling pointers or did i do something wrong using malloc()?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char *admin = "Administrator";
const char *mng = "Manager";
const char *prog = "Programmer";

struct employee {
    char *name;
    char title[20];
    char id[8];
    int yearsExp;
};

typedef struct employee emp_t;

emp_t *everyone[1];

emp_t *create_emp(const char *name,const char *title,const char *id,int yrxp) {
    emp_t *new;

    new = (emp_t *) malloc(sizeof(emp_t));
    new->name = (char*) malloc(strlen(name) + 1);
    strcpy(new->name,name);
    strcpy(new->title,title);
    strcpy(new->id,id);
    new->yearsExp = yrxp;
    return new;
}

void free_emp(emp_t *employee) {
    free(employee->name);
    free(employee);
}

int main() {
    int i;

    everyone[0] = create_emp("Mike Papamichail",prog,"A197482",3);
    everyone[1] = create_emp("Maria Mamalaki",mng,"Z104781",6);

    for(i = 0; i < 2;i++) {
        printf("%15s \t %15s \t %10s \t %4d\n",
        everyone[0]->name,everyone[0]->title,everyone[0]->id,everyone[0]->yearsExp);
        free_emp(everyone[i]);
        everyone[i] = NULL;
    }
    return 0;
}

Updated Code for clarity

    #include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char *admin = "Administrator";
const char *mng = "Manager";
const char *prog = "Programmer";

struct employee {
    char *name;
    char title[20];
    char id[8];
    int yearsExp;
};

typedef struct employee emp_t;

emp_t *create_emp(const char *name,const char *title,const char *id,int yrxp) {
    emp_t *new;

    new = (emp_t *) malloc(sizeof(emp_t));
    new->name = (char*) malloc(strlen(name) + 1);
    strcpy(new->name,name);
    strcpy(new->title,title);
    strcpy(new->id,id);
    new->yearsExp = yrxp;
    return new;
}

void free_emp(emp_t *employee) {
    free(employee->name);
    free(employee);
}

int main() {
    int i;
    emp_t *everyone[2];

    everyone[0] = create_emp("Mike Papamichail",prog,"A197482",3);
    everyone[1] = create_emp("Maria Mamalaki",mng,"Z104781",6);

    for(i = 0; i < 2;i++) {
        printf("%15s \t %15s \t %10s \t %4d\n",
        everyone[0]->name,everyone[0]->title,everyone[0]->id,everyone[0]->yearsExp);
        free_emp(everyone[i]);
        everyone[i] = NULL;
    }
    return 0;
}
Stelios Papamichail
  • 955
  • 2
  • 19
  • 57
  • 5
    Hint: `emp_t *everyone[1]` creates an array of *length 1*, so the maximum index is 0. `everyone[1]` is out of range. – tadman Dec 28 '18 at 22:50
  • Tip: Instead of fixed length arrays like that, use `malloc` to allocate *N* pointers into a dynamically allocated array. This avoids mix-ups like this. You should also use `char*` consistently inside your struct. Fixed length `char` buffers are asking for buffer overflow bugs, especially super tiny ones of length 20. You can use `strdup` to copy these. No need for `malloc` and then `strcpy`. – tadman Dec 28 '18 at 22:51
  • Avoid the use of *Globals*, declare `everyone` within `main()` and pass a pointer as a parameter to any function where it may be required. – David C. Rankin Dec 28 '18 at 22:53
  • @tadman: Can't you still overrun a dynamically-allocated array? – Robert Harvey Dec 28 '18 at 22:57
  • 1
    @RobertHarvey It's C. You can do anything, *especially* that. C presumes you know what you're doing when you ask to overrun something, so it's up to you as the programmer to not do that if you don't want *undefined behaviour*. The reason I suggest dynamically allocating is that way the size declaration and the population code exist side-by-side so mistakes are more obvious. – tadman Dec 28 '18 at 22:57
  • 1
    @tadman you are right about the first part, i already changed it in my code. As for the second part, i'll apply the necessary changes to my code asap! – Stelios Papamichail Dec 28 '18 at 23:01
  • @tadman , i just update the code in the post. Mind taking another look at it since i'm not sure if i allocated memory correctly for the `everyone` array. Also, in the output i get all the right info for `everyone[0]` but a `seg fault` for the other one. – Stelios Papamichail Dec 28 '18 at 23:11
  • 1
    Why are you calling `malloc` on the array, and then throwing those away when calling `create_emp` right on top? A habit you need to ditch is casting your `malloc` result. It's not necessary. Let the compiler deal with that. – tadman Dec 28 '18 at 23:14
  • @SteliosPapamichail - when you **"Edit"** you question, **add** new information at the end, do NOT **delete** content from your original post. Why? Every comment addressing problems in the original now no longer make sense. It makes it impossible to follow your progress. (I know you are new, so no dings for it this time, but avoid repeating it in the future) – David C. Rankin Dec 28 '18 at 23:16
  • @tadman i probably misunderstood your second comment, i was trying to improve on that and obviously messed up. Do you mind explaining it again for me? About the bad habit, i didn't know it's a "bad practice", our college professor says that we should always explicitly cast the result of our `malloc` just for clarity issues so i started doing that. – Stelios Papamichail Dec 28 '18 at 23:21
  • @DavidC.Rankin , you are correct. Should i just add a copy of my code below the old one and update that only or is there some other way? – Stelios Papamichail Dec 28 '18 at 23:21
  • 2
    Yeah, well, sadly there's a long tradition of computer science professors saying things that [simply aren't true](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). What specific thing do you need explained better? – tadman Dec 28 '18 at 23:22
  • @tadman thanks for the link, i had no idea i shouldn't be doing that. Now for the explanation part, why would i need a dynamically allocated array instead of a fixed one since i know i'm only gonna be needing two elements? Lastly, when you said that i should be consistent with my `char *` inside my `struct`, did you mean that i should not use `title[] & id[]` but instead `char *title & char *id`? How does that benefit my program since i know the size of the data i'll be using and could it be why i'm getting a `seg fault`? (Sorry for the long comment, i'm new in C) – Stelios Papamichail Dec 28 '18 at 23:30
  • @Bob__ aren't they allocated memory automatically since they are fixed size? – Stelios Papamichail Dec 28 '18 at 23:31
  • 1
    @SteliosPapamichail - you can revert the to the original and add your changes there and save to make it all make sense again -- optimal solution, (but it is fairly clear what happened since the original code was captured in the comments) – David C. Rankin Dec 28 '18 at 23:34
  • @DavidC.Rankin done, how does it look now? – Stelios Papamichail Dec 28 '18 at 23:43
  • Much better now (even with the additional indentation before `#include ` in the addition `:)` – David C. Rankin Dec 28 '18 at 23:46
  • The reason you use `char*` is because you can allocate any sized buffer as necessary, and you can use tools like `strdup` to make quick copies that are precisely the right size. Using a pre-defined buffer size is something you want to avoid as much as possible, *especially* inside structures where it can create a tremendous amount of waste. Fixed-length buffers like that are normally used *only* when reading in from files or other inputs and are temporary in nature. – tadman Dec 30 '18 at 21:17

2 Answers2

3

You are close on all points @tadman caught your biggest error in creating an array of 1 pointer with emp_t *everyone[1] (now deleted in your question).

The remainder of the issues with your code are more a number of small corrections or improvement comparatively speaking.

For starters, avoid using "magic-numbers" in your code (e.g. 20, 8, 2). If you need constants, #define them, or use a global enum to define multiple constants at once, e.g.

/* an enum can be used to #define multiple constants */
enum { MAXID = 8, MAXTITLE = 20, MAXEMP = 128 };

and then

typedef struct {    
    char *name;
    char title[MAXTITLE];   /* avoid "magic-numbers", use constants */
    char id[MAXID];
    int yearsExp;
} emp_t;

Your create_emp() function will largely work as is, but there is no need to cast the return of malloc, calloc or realloc in C, see: Do I cast the result of malloc?. Also, I would avoid the use of new as a temporary struct name. While there is no actual conflict in C, new is a keyword in C++ and if you will code in both, best to keep that in mind. With a couple of tweaks, you would write create_emp() as follows:

emp_t *create_emp (const char *name, const char *title, 
                    const char *id, int yrxp) {
    emp_t *newemp;  /* new is a keyword in C++ best to avoid */

    newemp = malloc (sizeof *newemp);   /* don't cast return of malloc */
    /* if you allocate, you must validate */
    if (newemp == NULL) {
        perror ("malloc-newemp");
        return NULL;
    }
    newemp->name = malloc (strlen(name) + 1);
    if (newemp->name == NULL) {
        perror ("malloc-newemp->name");
        free (newemp);
        return NULL;
    }
    strcpy (newemp->name, name);
    strcpy (newemp->title, title);
    strcpy (newemp->id, id);
    newemp->yearsExp = yrxp;

    return newemp;
}

(note: always validate each allocation. malloc, calloc & realloc can and do fail)

Lastly, in main(), you can use an index (ndx below) instead of the magic-number 2 and increment the index with each addition. While you are doing well to use field-width modifiers to control your output field size, for strings (and all conversion specifiers) you can include the '-' flag as part of the conversion specifier to make the field Left-Justified to align your output properly. The remainder of your code is fine.

Putting it altogether, you could do something like the following:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* an enum can be used to #define multiple constants */
enum { MAXID = 8, MAXTITLE = 20, MAXEMP = 128 };
#define ADMIN   "Administrator" /* simple #defines are fine */
#define MNG     "Manager"       /* string literals are fine as well */
#define PROG    "Programmer"    /* (up to you) */

typedef struct {    
    char *name;
    char title[MAXTITLE];   /* avoid "magic-numbers", use constants */
    char id[MAXID];
    int yearsExp;
} emp_t;

emp_t *create_emp (const char *name, const char *title, 
                    const char *id, int yrxp) {
    emp_t *newemp;  /* new is a keyword in C++ best to avoid */

    newemp = malloc (sizeof *newemp);   /* don't cast return of malloc */
    /* if you allocate, you must validate */
    if (newemp == NULL) {
        perror ("malloc-newemp");
        return NULL;
    }
    newemp->name = malloc (strlen(name) + 1);
    if (newemp->name == NULL) {
        perror ("malloc-newemp->name");
        free (newemp);
        return NULL;
    }
    strcpy (newemp->name, name);
    strcpy (newemp->title, title);
    strcpy (newemp->id, id);
    newemp->yearsExp = yrxp;

    return newemp;
}

void free_emp (emp_t *employee) {
    free (employee->name);
    free (employee);
}

int main (void) {

    int i, ndx = 0;     /* use an index instead of magic-numbers */

    emp_t *everyone[MAXEMP] = {NULL};

    everyone[ndx++] = create_emp ("Mike Papamichail", PROG, "A197482", 3);
    everyone[ndx++] = create_emp ("Maria Mamalaki", MNG, "Z104781", 6);
    everyone[ndx++] = create_emp ("Sam Sunami", ADMIN, "B426310", 10);

    for (i = 0; i < ndx; i++) { /* %- to left justify fields */
        if (everyone[i]) {      /* validate not NULL */
            printf ("%-15s \t %-15s \t %-10s \t %4d\n", everyone[i]->name, 
                    everyone[i]->title, everyone[i]->id, 
                    everyone[i]->yearsExp);
            free_emp (everyone[i]);
            everyone[i] = NULL;
        }
    }

    return 0;
}

(note: the change from everyone[0]->name to everyone[i]->name, etc.., otherwise the output never changes)

Example Use/Output

$ ./bin/emp_struct
Mike Papamichail         Programmer              A197482            3
Maria Mamalaki           Manager                 Z104781            6
Sam Sunami               Administrator           B426310           10

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thank you so much for the answer, since it's too late here i will review it in the morning and post here again, hope you don't mind. Thanks again! – Stelios Papamichail Dec 28 '18 at 23:45
  • No problem, note: I added additional validations for `malloc` and then within the output loop (which you would usually do in your fill loop, but that is hardcoded here) – David C. Rankin Dec 28 '18 at 23:47
  • Thank you so much for the answer and the explanations.I finally understand and hopefully i will pick up some of those best practices displayed here. One last question if you don't mind, what does `left-justified` mean? – Stelios Papamichail Dec 29 '18 at 20:10
  • 2
    That means the names are displayed to the left of the field, e.g. `"Maria Mamalaki "` instead of `" Maria Mamalaki"`. (the default, without the `'-'` is Right-Justified where the words are aligned to the right end of the field) – David C. Rankin Dec 29 '18 at 20:42
0

At the end, you're looping through your array, printing everyone[0] and then freeing everyone[i]. So on the second iteration, everyone[0] will be null and the code crashes...

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226