I'm currently learning C and I have trouble understanding the following code:
struct dns_header
{
unsigned char ra : 1;
unsigned char z : 1;
unsigned char ad : 1;
unsigned char cd : 1;
unsigned char rcode : 4;
unsigned short q_count : 16;
};
int main(void)
{
struct dns_header *ptr;
unsigned char buffer[256];
ptr = (struct dns_header *) &buffer;
ptr->ra = 0;
ptr->z = 0;
ptr->ad = 0;
ptr->cd = 0;
ptr->rcode = 0;
ptr->q_count = htons(1);
}
The line I don't understand is ptr = (struct dns_header *) &buffer;
Can anyone explain this in detail?
Your buffer is simply a contiguous array of raw bytes. They have no semantic from the buffer point of view: you cannot do something like buffer->ra = 1.
However, from a struct dns_header * point of view those bytes would become meaningful. What you are doing with ptr = (struct dns_header *) &buffer; is mapping your pointer to your data.
ptr will now points on the beginning of your array of data. It means that when you write a value (ptr->ra = 0), you are actually modifying byte 0 from buffer.
You are casting the view of a struct dns_header pointer of your buffer array.
The buffer is just serving as an area of memory -- that it's an array of characters is unimportant to this code; it could be an array of any other type, as long as it were the correct size.
The struct defines how you're using that memory -- as a bitfield, it presents that with extreme specificity.
That said, presumably you're sending this structure out over the network -- the code that does the network IO probably expects to be passed a buffer that's in the form of a character array, because that's intrinsically the sanest option -- network IO being done in terms of sending bytes.
Suppose you want to allocate space for the struct so you could
ptr = malloc(sizeof(struct dns_header));
which will return a pointer to the allocated memory,
ptr = (struct dns_header *) &buffer;
is almost the same, except that in this case it's allocated in the stack, and it's not necessary to take the address of the array, it can be
ptr = (struct dns_header *) &buffer[0];
or just
ptr = (struct dns_header *) buffer;
there is no problem in that though, because the addresses will be the same.
The line I don't understand is ptr = (struct dns_header *) &buffer;
You take the address of the array and pretend like it is a pointer to a dns_header. It is basically raw memory access, which is unsafe, but OK if you know what you are doing. Doing so will grant you access to write a dns_header in the beginning of the array.
Ideally, it should be an array of dns_headers not a byte array. You have to be cautious about the fact that dns_header contains bit fields, the implementation of which is not enforced by the standard, it is entirely up to the compiler vendors. Although bit field implementations are fairly "sane", there is no guarantee, so the size of a byte array might actually be mismatched with your intent.
Adding to the other answers posted:
This code is illegal since ANSI C. ptr->q_count = htons(1); violates the strict aliasing rule.
It is only permitted to use an unsigned short lvalue (i.e. the expression ptr->q_count) to access memory that either has no declared type (e.g. malloc'd space), or has declared type of short or unsigned short or compatible.
To use this code as-is, you should pass -fno-strict-aliasing to gcc or clang. Other compilers may or may not have a similar flag.
An improved version of the same code (which also has some forwards-compatibility to the structure size changing) is:
struct dns_header d = { 0 };
d.q_count = htons(1);
unsigned char *buffer = (unsigned char *)&d;
This is legal because the strict aliasing rule permits unsigned char to alias anything.
Note that buffer is currently unused in this code. If your code is actually a smaller snippet of larger code then buffer may have to be defined differently. In any case, it could be in a union with d.
A struct directly references a contiguous block of memory and each field within a struct is located at a certain fixed offset from the start. Variables can then be accessed via a struct pointer or by the struct declared name which returns the same address.
Here we declare a packed struct which references a contiguous block of memory:
#pragma pack(push, 1)
struct my_struct
{
unsigned char b0;
unsigned char b1;
unsigned char b2;
unsigned char b3;
unsigned char b4;
};
#pragma pack(pop)
Pointers can then be used to refer to the struct by its address. See this example:
int main(void)
{
struct my_struct *ptr;
unsigned char buffer[5];
ptr = (struct my_struct *) buffer;
ptr->b0 = 'h';
ptr->b1 = 'e';
ptr->b2 = 'l';
ptr->b3 = 'l';
ptr->b4 = 'o';
for (int i = 0; i < 5; i++)
{
putchar(buffer[i]); // Print "hello"
}
return 0;
}
Here we explicitly map 1:1 the struct contiguous block of memory to the contiguous block of memory pointed by buffer (using the address to the first element).
An array address and the name of the address are numerically identical but have different types. These two lines are thus equivalent:
ptr = (struct my_struct *) buffer;
ptr = (struct my_struct *) &buffer;
This is usually not a problem if we use the address as is and cast it appropriately. Dereferencing an array address of type pointer to array-of-type yields the same pointer but with a different type array-of-type.
Although it might seem convenient to manipulate memory in this fashion, it is strongly discouraged as the resulting code becomes painfully difficult to understand. If you really have no choice, I suggest using an union to specify that the struct is to be used in a particular manner.
Related
I would like to know, if its possible to cast struct to short but only 2 bites of its adress and save value in there. I personally dont even know if its possible just wanna get any ideas how to do that.
In my project i link void adress of char to struct and then doing something similar like malloc but without using malloc.. making somthing like function malloc.
My struct and its pointer:
typedef struct mem_list {
int size;
struct mem_list *next;
struct mem_list *prev;
}mem_list;
mem_list *start;
my function memory init:
void memory_init(void *ptr, unsigned int size){
mem_list *temp;
temp = (mem_list*)ptr;
if(size <= sizeof(mem_list)){
temp->size = 0;
printf("Failed\n");
return;
}
else
{
temp->size = size - sizeof(mem_list);
temp->next = NULL;
*((unsigned short*)(&temp + size - sizeof(unsigned short))) = 0;
start = temp;
printf("Inicialized was %d bits\n",size-sizeof(mem_list));
return;
}
}
My main:
int main() {
char region[100];
memory_init(region, 60);
//char* pointer = memory_alloc(20);
//printf("adresa %d\n", pointer);
return 0;
}
My problem is in function memory init in this part of code:
*((unsigned short*)(&temp + size - sizeof(unsigned short))) = 0;
What i want to do is to move to end of my inicialized memory and save there short typed zero for showing me later where is end of my memory. And also would like to ask how can i acces that value later? I know there maybe are mistakes in my code. Woul be happy if you point me where and give me some ideas how to do that. thank you :)
(&temp + size - sizeof(unsigned short))): &temp is the address of the pointer to your mem_list, so &temp + xxx is the address of somewhere in the stack :-(
The address of the last byte of your mem_list object is (char*)temp + size.
To be cleaner you could define your
typedef struct mem_list {
int size;
struct mem_list *next;
struct mem_list *prev;
unsigned short body[]
} mem_list_t ;
Then:
blen = (size + sizeof(unsigned short) - 1) / sizeof(unsigned short) ;
temp->body[blen] = 0 ;
writes 0 to the last unsigned short of the body of the mem_list_t.
Note that this assumes that ptr points to an object which has been allocated with asize bytes:
asize = offsetof(mem_list_t, body[blen+1]) ;
with blen calculated as above. (And ptr needs to be aligned as required for mem_list_t, of course.)
It is not clear whether you can reuse a char buffer to create objects of other types in it(*), but you should at least care about alignment. Some processors require non char types to be correctly aligned, for example that:
the address of an int16_t shall be even
the address of an int32_t or larger shall be a multiple of 4
And even if some other processors do not enforce this rule, accessing mis-aligned data often adds a significant overhead. That is the reason for padding in structs.
So without more precautions, this line:
*((unsigned short*)(&temp + size - sizeof(unsigned short))) = 0;
could break because if size is odd, you are trying to write an unsigned short at an odd address.
(*) For more details, you can read that other post from mine, specialy the comments on my own answer
if its possible to cast struct to short but only 2 bites of its adress and save value in there
No, it isn't possible. *((unsigned short*)(&temp...) invokes undefined behavior. It is a so-called "strict aliasing violation" and can also lead to misalignment issues depending on system. What is the strict aliasing rule?
The rule of thumb is: never wildly cast between completely different pointer types. You need a lot of detailed knowledge about C in order to so in a safe manner.
You can do "type punning" either by using a union between the struct and a unsigned short though. Please note that endianess is an issue to consider when doing so.
Other than that, you can safely memcpy the contents of a struct into an allocated unsigned short or vice versa. memcpy is excempt from pointer aliasing rules and will handle alignment safely.
Frankly, is such a code valid or does it invoke undefined behavior?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct two_values
{
int some;
char value;
};
int main(void) {
int some = 5;
char value = 'a';
unsigned char *data = malloc(sizeof(struct two_values));
memcpy(data, &some, sizeof(int));
memcpy(data+sizeof(int), &value, sizeof(char));
struct two_values dest;
memcpy(&dest, data, sizeof(struct two_values));
printf("some = %d, value = %c\n", dest.some, dest.value);
return 0;
}
http://ideone.com/4JbrP9
Can I just put the binary representation of two struct field together and reinterpret this as the whole struct?
You had better to not disturb the internal compiler doings in your code, as it would lead you to incorrect code and undefined behaviour. You can switch compilers, or just updating the version of your favourite, and run into trouble.
The best way to solve the thing you show of having two variables and to store them properly in the struct fields is to use properly the types provided by C, and use a pointer typed to the proper type. If you use
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct two_values
{
int some;
char value;
};
int main(void) {
int some = 5;
char value = 'a';
/* next instead of unsigned char *data = malloc(sizeof(struct two_values)); */
struct two_values *data = malloc(sizeof(struct two_values));
/* next instead of memcpy(data, &some, sizeof(int)); */
data->some = some;
/* next instead of memcpy(data+sizeof(int), &value, sizeof(char)); */
data->value = value;
struct two_values dest;
/* next instead of memcpy(&dest, data, sizeof(struct two_values)); */
dest = *data;
printf("some = %d, value = %c\n", dest.some, dest.value);
return 0;
}
You'll avoid all compiler alignment issues. It is always possible to do it with the language operators & (address of) and * (points to) or -> (field of struct pointed to).
Anyway, if you prefer the memcpy approach (no idea of why, but you are on your way, anyway) you can substitute:
data->some = some;
...
data->value = value;
...
dest = *data;
by
memcpy(&data->some, &some, sizeof data->some);
...
memcpy(&data->value, &value, sizeof data->value);
...
memcpy(&dest, data, sizeof dest);
And that will take internally the alignments that the compiler could make by itself.
All compilers have defined some pragma, or keyword, to control alignment. This is also nonportable, as you can switch compilers and get to the issue of having to change the way you expressed things. C11 has some standard means to control for packed structs and use no alignment in the compiler. This is done mainly when you have to serialize some structure and don't want to deal with holes on it. Look at the C11 specs for that.
Serializing structs is not completely solved by just making them packed, as normally you have to deal with the serialized representations of integer, floating point or char data (which can or cannot coincide with the internal representation used by the compiler) so you again face the problem of being compiler agnostic and have to think twice before using externally the internal representation of data.
My recomendation anyway, is never trust how the compiler stores data internally.
The padding is determined by the compiler. The order is guaranteed. If you need something similar to your code above, I would recommend the offsetof-macro in <stddef.h>.
memcpy(data + offsetof(struct two_values, value), &value, sizeof(char));
Or without explicitly adding the offset at all:
memcpy(&data->value, &value, sizeof(char));
It depend on how your structure is aligned. You can check by verifying sizeof(two_values), if it comes 5(assuming sizeof int is 4), you probably are ok.
If its more than that it implies filler bytes are inserted in your structure to align each element of your structure at correct byte boundry
May I assume that struct fields are placed in order
Yes, this is guaranteed by the standard. C11 6.2.5/20:
a structure is a type consisting of a sequence of members, whose
storage is allocated in an ordered sequence
and with no padding?
No, you cannot assume this. C11 6.7.1/15:
Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. /--/
There may be unnamed padding within a structure object, but not at its beginning.
Padding and alignment are implementation-defined behavior.
You are however guaranteed that two structs of the same type have the same padding. Copying from a struct to another struct of same type, as in your example, is safe and well-defined.
I've recently found this page:
Making PyObject_HEAD conform to standard C
and I'm curious about this paragraph:
Standard C has one specific exception to its aliasing rules precisely designed to support the case of Python: a value of a struct type may also be accessed through a pointer to the first field. E.g. if a struct starts with an int , the struct * may also be cast to an int * , allowing to write int values into the first field.
So I wrote this code to check with my compilers:
struct with_int {
int a;
char b;
};
int main(void)
{
struct with_int *i = malloc(sizeof(struct with_int));
i->a = 5;
((int *)&i)->a = 8;
}
but I'm getting error: request for member 'a' in something not a struct or union.
Did I get the above paragraph right? If no, what am I doing wrong?
Also, if someone knows where C standard is referring to this rule, please point it out here. Thanks.
Your interpretation1 is correct, but the code isn't.
The pointer i already points to the object, and thus to the first element, so you only need to cast it to the correct type:
int* n = ( int* )i;
then you simply dereference it:
*n = 345;
Or in one step:
*( int* )i = 345;
1 (Quoted from: ISO:IEC 9899:201X 6.7.2.1 Structure and union specifiers 15)
Within a structure object, the non-bit-field members and the units in which bit-fields
reside have addresses that increase in the order in which they are declared. A pointer to a
structure object, suitably converted, points to its initial member (or if that member is a
bit-field, then to the unit in which it resides), and vice versa. There may be unnamed
padding within a structure object, but not at its beginning.
You have a few issues, but this works for me:
#include <malloc.h>
#include <stdio.h>
struct with_int {
int a;
char b;
};
int main(void)
{
struct with_int *i = (struct with_int *)malloc(sizeof(struct with_int));
i->a = 5;
*(int *)i = 8;
printf("%d\n", i->a);
}
Output is:
8
Like other answers have pointed out, I think you meant:
// Interpret (struct with_int *) as (int *), then
// dereference it to assign the value 8.
*((int *) i) = 8;
and not:
((int *) &i)->a = 8;
However, none of the answers explain specifically why that error makes sense.
Let me explain what ((int *) &i)->a means:
i is a variable that holds an address to a (struct with_int). &i is the address on main() function's stack space. This means &i is an address, that contains an address to a (struct with_int). In other words, &i is a pointer to a pointer to (struct with_int). Then the cast (int *) of this would tell the compiler to interpret this stack address as an int pointer, that is, address of an int. Finally, with that ->a, you are asking the compiler to fetch the struct member a from this int pointer and then assign the value 8 to it. It doesn't make sense to fetch a struct member from an int pointer. Hence, you get error: request for member 'a' in something not a struct or union.
Hope this helps.
Given the below simple code, where you have process_payload is given a pointer to the payload portion of the packet, how do you access the header portion? Ideally the caller should simply give a pointer to full packet from beginning, but there are cases where you don't have the beginning of the message and need to work backwards to get to the header info. I guess this question becomes a understanding of walking through the memory layout of a struct.
The header computes to 8 bytes with sizeof operation. I assume Visual C++ compiler added 3 bytes padding to header.
The difference between pptr and pptr->payload is decimal 80 (not sure why this value??) when doing ptr arith (pptr->payload - pptr). Setting ptr = (struct Packet*)(payload - 80) works but seems more a hack. I don't quite understand why subtracting sizeof(struct header) doesn't work.
Thanks for any help you can give.
struct Header
{
unsigned char id;
unsigned int size;
};
struct Packet
{
struct Header header;
unsigned char* payload;
};
void process_payload(unsigned char* payload);
int main()
{
struct Packet* pptr = (struct Packet*)malloc(sizeof(struct Packet));
pptr->payload = (unsigned char*)malloc(sizeof(unsigned char)*10);
process_payload(pptr->payload);
return 1;
}
// Function needs to work backwards to get to header info.
void process_payload(unsigned char* payload)
{
// If ptr is correctly setup, it will be able to access all the fields
// visible in struct Packet and not simply payload part.
struct Packet* ptr;
// This does not work when intuitively it should?
ptr = (struct Packet*)(payload - sizeof(struct Header));
}
It's because in main you allocate two pointers, and pass the second pointer to the process_payload function. The two pointers are not related.
There are two ways of solving this problem, where both include a single allocation.
The first solution is to used so called flexible arrays, where you have an array member last in the structure without any size:
struct Packet
{
struct Header header;
unsigned char payload[];
};
To use it you make one allocation, with the size of the structure plus the size of the payload:
struct Packet *pptr = malloc(sizeof(struct Packet) + 10);
Now pptr->payload is handled like a normal pointer pointing to 10 unsigned characters.
Another solution, which is a mix of your current solution and the solution with flexible arrays, is to make one allocation and make the payload pointer to point to the correct place in the single allocated memory block:
struct Packet
{
struct Header header;
unsigned char *payload;
};
// ...
struct Packet *pptr = malloc(sizeof(struct Packet) + 10);
pptr->payload = (unsigned char *) ((char *) pptr + sizeof(struct Packet);
Note that in this case, to get the Packet structure from the payload pointer, you have to use sizeof(Packet) instead of only sizeof(Header).
Two things to note about the code above:
I don't cast the result of malloc
sizeof(char) (and also the size of unsigned char) is specified to always be one, so no need for sizeof
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 e.g. 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, i.e.
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 many ways 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.
EDIT
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!