简体   繁体   中英

Dynamic array of pointers to structs

I have to use the following block of code for a school assignment, STRICTLY WITHOUT ANY MODIFICATIONS.

typedef struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
}* pStudentRecord;

pStudentRecord* g_ppRecords;
int g_numRecords =0;

Here g_ppRecords is supposed to be an array of pointers to structs. What I am completely failing to understand is that how can the statement pStudentRecords *g_ppRecords; mean g_ppRecords to be an array because an array should be defined as

type arrayname[size];

I tried allocating memory to g_ppRecords dynamically, but that's not helping.

g_ppRecords = (pStudentRecord*) malloc(sizeof(pStudentRecord*)*(g_numRecords+1));

EDIT: updated the "BIG MISTAKE" section.

A quick lesson on C-style (different from C++!) typedefs, and why it is how it is, and how to use it.

Firstly, a basic typedef trick.

typedef int* int_pointer;
int_pointer ip1;
int *ip2;
int a;    // Just a variable
ip1 = &a; // Sets the pointer to a
ip2 = &a; // Sets the pointer to a
*ip1 = 4; // Sets a to 4
*ip2 = 4; // Sets a to 4

ip1 and ip2 are the same type: a pointer-to-type-int, even though you didn't put a * in the declaration of ip1. That * was instead in the declaration.

Switching topics. You speak of declaring arrays as

int array1[4];

To do this dynamically at runtime, you might do:

int *array2 = malloc(sizeof(int) * 4);
int a = 4;
array1[0] = a;
array2[0] = a; // The [] implicitly dereferences the pointer

Now, what if we want an array of pointers? It would look like this:

int *array1[4];
int a;
array1[0] = &a; // Sets array[0] to point to variable a
*array1[0] = 4; // Sets a to 4

Let's allocate that array dynamically.

int **array2 = malloc(sizeof(int *) * 4);
array2[0] = &a; // [] implicitly dereferences
*array2[0] = 4; // Sets a to 4

Notice the int **. That means pointer-to pointer-to-int. We can, if we choose, use a pointer typedef.

typedef int* array_of_ints;
array_of_ints *array3 = malloc(sizeof(array_of_ints) * 4);
array3[0] = &a; // [] implicitly dereferences
*array3[0] = 4; // Sets a to 4

See how there's only one * in that last declaration? That's because ONE of them is "in the typedef." With that last declaration, you now have an array of size 4 that consists of 4 pointers to ints (int *).

It's important to point out OPERATOR PRECEDENCE here. The dereference operator[] takes preference over the * one. SO to be absolutely clear, what we're doing is this:

*(array3[0]) = 4;

Now, let's change topics to structs and typedefs.

struct foo { int a; }; // Declares a struct named foo
typedef struct { int a; } bar; // Typedefs an "ANONYMOUS STRUCTURE" referred to by 'bar'

Why would you ever typedef an anonymous struct? Well, for readability!

struct foo a; // Declares a variable a of type struct foo
bar b;        // Notice how you don't have to put 'struct' first

Declaring a function...

funca(struct foo* arg1, bar *arg2);

See how we didn't have to put 'struct' in front of arg2?

Now, we see that the code you have to use defines a structure IN THIS MANNER:

typedef struct { } * foo_pointers;

That is analogous to how we did an array of pointers before:

typedef int* array_of_ints;

Compare side-by-side

typedef struct { } * foo_pointers;
typedef int* array_of_ints;

The only difference is that one is to a struct {} and the other is to int.

With our foo_pointers, we can declare an array of pointers to foo as such:

foo_pointers fooptrs[4];

Now we have an array that stores 4 pointers to an anonymous structure that we can't access.

TOPIC SWITCH!

UNFORTUNATELY FOR YOU, your teacher made a mistake. If one looks at the sizeof() of the type foo_pointers above, one will find it returns the size of a pointer to that structure, NOT the size of the structure. This is 4 bytes for 32-bit platform or 8 bytes for 64-bit platform. This is because we typedef'd a POINTER TO A STRUCT, not a struct itself. sizeof(pStudentRecord) will return 4.

So you can't allocate space for the structures themselves in an obvious fashion! However, compilers allow for this stupidity. pStudentRecord is not a name/type you can use to validly allocate memory, it is a pointer to an anonymous "conceptual" structure, but we can feed the size of that to the compiler.

pStudnetRecord g_ppRecords[2]; pStudentRecord *record = malloc(sizeof(*g_ppRecords[1]));

A better practice is to do this:

typedef struct { ... } StudentRecord;  // Struct
typedef StudentRecord* pStudentRecord; // Pointer-to struct

We'd now have the ability to make struct StudentRecord's, as well as pointers to them with pStudentRecord's, in a clear manner.

Although the method you're forced to use is very bad practice, it's not exactly a problem at the moment. Let's go back to our simplified example using ints.

What if I want to be make a typedef to complicate my life but explain the concept going on here? Let's go back to the old int code.

typedef int* array_of_ints;
int *array1[4];
int **array2 = malloc(sizeof(int *) * 4); // Equivalent-ish to the line above
array_of_ints *array3 = malloc(sizeof(array_of_ints) * 4);
int a, b, c, d;
*array1[0] = &a; *array1[1] = &b; *array1[2] = &c; *array1[3] = &d;
*array2[0] = &a; *array2[1] = &b; *array2[2] = &c; *array2[3] = &d;
*array3[0] = &a; *array3[1] = &b; *array3[2] = &c; *array3[3] = &d;

As you can see, we can use this with our pStudentRecord:

pStudentRecord array1[4];
pStudentRecord *array2 = malloc(sizeof(pStudentRecord) * 4);

Put everything together, and it follows logically that:

array1[0]->firstName = "Christopher";
*array2[0]->firstName = "Christopher";

Are equivalent. (Note: do not do exactly as I did above; assigning a char* pointer at runtime to a string is only OK if you know you have enough space allocated already).

This only really brings up one last bit. What do we do with all this memory we malloc'd? How do we free it?

free(array1);
free(array2);

And there is a the end of a late-night lesson on pointers, typedefs of anonymous structs, and other stuff.

Observe that pStudentRecord is typedef'd as a pointer to a structure. Pointers in C simply point to the start of a memory block, whether that block contains 1 element (a normal "scalar" pointer) or 10 elements (an "array" pointer). So, for example, the following

char c = 'x';
char *pc = &c;

makes pc point to a piece of memory that starts with the character 'x' , while the following

char *s = "abcd";

makes s point to a piece of memory that starts with "abcd" (and followed by a null byte). The types are the same, but they might be used for different purposes.

Therefore, once allocated, I could access the elements of g_ppRecords by doing eg g_ppRecords[1]->firstName .

Now, to allocate this array: you want to use g_ppRecords = malloc(sizeof(pStudentRecord)*(g_numRecords+1)); (though note that sizeof(pStudentRecord*) and sizeof(pStudentRecord) are equal since both are pointer types). This makes an uninitialized array of structure pointers . For each structure pointer in the array, you'd need to give it a value by allocating a new structure. The crux of the problem is how you might allocate a single structure, ie

g_ppRecords[1] = malloc(/* what goes here? */);

Luckily, you can actually dereference pointers in sizeof :

g_ppRecords[1] = malloc(sizeof(*g_ppRecords[1]));

Note that sizeof is a compiler construct. Even if g_ppRecords[1] is not a valid pointer, the type is still valid, and so the compiler will compute the correct size.

An array is often referred to with a pointer to its first element. If you malloc enough space for 10 student records and then store a pointer to the start of that space in g_ppRecords, g_ppRecords[9] will count 9 record-pointer-lengths forward and dereference what's there. If you've managed your space correctly, what's there will be the last record in your array, because you reserved enough room for 10.

In short, you've allocated the space, and you can treat it however you want if it's the right length, including as an array.

I'm not sure why you're allocating space for g_numRecords + 1 records. Unless g_numRecords is confusingly named, that's space for one more in your array than you need.

Here g_ppRecords is supposed to be an array of pointers to structs. What I am completely failing to understand is that how can the statement *pStudentRecords g_ppRecords; mean g_ppRecords to be an array. as an array should be defined as

type arrayname[size];

umm type arrayname[size]; is one way of to define an array in C. 一种方法

this statically defines an array, with most of the values being stored on the stack depending the location of it definition, the size of the array must be known at compile time, though this may no longer be the case in some modern compilers.

another way would be to dynamically create an array at runtime, so we don't have to know the size at compile time, this is where pointers come in, they are variables who store the address of dynamically allocated chunks of memory.

a simple example would be something like this type *array = malloc(sizeof(type) * number_of_items); malloc returns a memory address which is stored in array , note we don't typecast the return type for safety reasons.

Going back to the problem at hand.

typedef struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
}*  pStudentRecord;

pStudentRecord* g_ppRecords;
int g_numRecords = 0;

this typedef is a bit different from most note the }* basically its a pointer to a struct so this:

pStudentRecord* g_ppRecords;

is actually:

struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
}** pStudentRecord;

its a pointer to a pointer, as to why they would define the typedef in this way, its beyond me, and I personally don't recommend it, why?

well one problem woud be how can we get the size of the struct through its name? simple we can't! if we use sizeof(pStudentRecord) we'll get 4 or 8 depending on the underlying architecture, because thats a pointer, without knowing the size of the structure we can't really dynamically allocated it using its typedef name, so what can we do, declare a second struct such as this:

typedef struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
} StudentRecord;

g_ppRecords = malloc(sizeof(StudentRecord) * g_numRecords);

Either way you really need to contact the person who original created this code or the people maintaining and raise your concerns.

g_ppRecords=(pStudentRecord) malloc( (sizeof(char*) + 
                                  sizeof(char*) + 
                                  sizeof(int)   + 
                                  sizeof(float)) *(g_numRecords+1));

this may seem like one possible way, unfortunately, there are no guarantees about structs, so they can actually containg padding in between the members so the total size of the struct can be actually larger then its combined members, not to mention there address would probably differ.

Apparently we can get the size of the struct by simply inferring its type

so:

pStudentRecord g_ppRecords = malloc(sizeof(*g_ppRecords) * g_numRecords);

works fine!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM