Hello, I've to build a database of students in a certain school in which each student has a list of her/his passed exams along with the grade gotten.
I have 3 structs and I need an advice on how to carry out memory allocation, eg. when I add a student.

struct exam  {
    char exam_code[4];
    int grade;
};

struct student   {
    char name[16];
    int num_exams;
    struct exam **exams;
};

struct school {
    int total_students;
    struct student **students;
};

int main()  {
    
    struct school sc;
    sc.total_students = 0;
    
    // now I add ths first student (with no exams)
    sc.total_students++;
    
    students = malloc(sizeof(struct student));
    strcpy(students[0]->name, "jack");
    students[0]->num_exams = 0;

    // then the second
    students = realloc(students, 2 * sizeof(struct student));
    strcpy(students[1]->name, "peter");
    students[1]->num_exams = 0;

    return 0;
    
}

Any hint will be highly appreciated...

#
students = malloc(sizeof(struct student));
#
strcpy(students[0]->name, "jack");

I might have missed it, but of what type is "students"? It seems to be an int, since the declaration is lacking a type.

#
students = malloc(sizeof(struct student));
#
strcpy(students[0]->name, "jack");

I might have missed it, but of what type is "students"? It seems to be an int, since the declaration is lacking a type.

You can find it in the struct school:

struct school {
    int total_students;
    struct student **students;
};

I mean school is made of students, plus a counter to check how many students are registered. Obviously there's something wrong with my implementation...

malloc() always returns a void pointer. You need to typecast it. students = (struct student**)malloc(sizeof(struct student));

>malloc() always returns a void pointer. You need to typecast it.
C is kind enough to implicitly convert to and from pointers to void without the need for a cast. In fact, if you get into the habit of casting the result of malloc, your code is both more prone to subtle errors and harder to maintain.

The recommended way to use malloc is as follows (assuming a pointer called p is declared):

/* Allocate one item of the type p points to */
p = malloc ( sizeof *p );

/* Allocate N items of the type p points to */
p = malloc ( N * sizeof *p );

Notice that all of the type information is acquired dynamically rather than being hard coded.

/* Allocate one item of the type p points to */
p = malloc ( sizeof *p );

That's looks like a really neat way of allocating memory, and I can see how it can be less error-prone. Thanks!

hehe, you should access sc.students then, not students. ;)

Yes, obviously, it was an oversight when copying into the post form..

The problem is I don't know how much space to allocate: students is declared as struct student **students;
What does it mean? is it
1) a single pointer to an array of type "struct student",
2) a single pointer to an array of pointers each pointing to a "struct student" or
3) an array of pointers each pointing to a "struct student" ?

The problem is I don't know how much space to allocate: students is declared as struct student **students;
What does it mean? is it
1) a single pointer to an array of type "struct student",
2) a single pointer to an array of pointers each pointing to a "struct student" or
3) an array of pointers each pointing to a "struct student" ?

It's a pointer to a pointer, nothing more. However, you can generally assume that T **p means that p is a dynamically sized array of pointers to T, and you allocate memory as such:

T **p = malloc ( N * sizeof *p );

for ( i = 0; i < N; i++ )
  p[i] = malloc ( sizeof *p[i] );

It's a pointer to a pointer, nothing more. However, you can generally assume that T **p means that p is a dynamically sized array of pointers to T (...)

Pardon me for having cut your reply to the bone, you write "p is a dynamically sized array of pointers to T", I used to think p was *only* a pointer to a dynamically sizeable array of pointers to T..
In the struct declaration memory it's not automatically reserved for a pointer only?

It is only a pointer, don't worry. It's a pointer that points to pointers.

The struct declaration indeed only reserves one pointer, not an array or anything.

However, I guess you want to have *students in your struct. An "array" of students, right? That makes a bit more sense than what you have now (which resembles a 2D array of students).

It is only a pointer, don't worry. It's a pointer that points to pointers.

The struct declaration indeed only reserves one pointer, not an array or anything.

However, I guess you want to have *students in your struct. An "array" of students, right? That makes a bit more sense than what you have now (which resembles a 2D array of students).

Yes, if I've understood properly, for my purpose it's enough an "array of students", I only want to list the students in the school with their exams.
Now, I'm asking myself, why in the spec, that was given as a hint for implementaion, there's such a double pointer variable, is it somewhat better, for ex. more flexible or efficient?

Not in any imaginable way if you ask me. Like I said, a pointer to an array of pointers (an array of "arrays") resembles a 2D array. Unless you want that, there's no use for a double pointer.

You can always create the double pointer at any time by &students, so if the need would arise it has a simple solution.

This question has already been answered. Start a new discussion instead.