Related
Is there a possible way to make static array of structs points to NULL since I can't delete arrays and I want to clean the memory?
Suppose we have the following code:
struct_x defaultStructX[6];
struct_x requiredStructX [6];
gettingDefaultX(defaultStructX, 6);
for (uint8_t i = 0; i <6; i++)
{
setStructX(requiredStructX[i].index, requiredStructX[i].icirate, requiredStructX[i].icis, requiredStructX[i].iei);
//error handle case
if (status == STATUS_SUCCESS)
{
writeResponse.writeStatus = status_ok; /*This is another struct not important at this point*/
} else
{
errorHandleQciFlowMeter(defaultStructX, 6);
writeResponse.writeStatus = status_nok;
break;
}
}
/*here I want to write code line to clean the defaultStructX from memory. Is it possible? I have tried *defaultStructX[i]= NULL and ##((void*)defaultQciFlowMeter) = NULL; ## and many other methods but it didn't work*/
Is there a possible way to make static array of stucts points to NULL since I can't delete arrays and I want to clean the memory?
No. Arrays are not pointers, nor are the elements of your particular arrays pointers, either. Pointer values cannot be assigned to either the arrays themselves or to the elements, and in particular, NULL cannot be assigned to them.
You can overwrite the memory occupied by the array with, say,
memset(defaultStructX, 0, sizeof(defaultStructX));
That will replace the data previously stored within,* which might be useful if those data were sensitive. The application would then need to assign new, valid values to the array elements before using them again.
Any way around, however, you cannot free the memory of an object with static storage duration, which is any object declared at file scope, outside all functions, or with the static qualifier inside a function. The entire point of static storage duration is for objects' lifetimes to be the entire duration of the program's run. If you want to be able to release the memory then you should allocate space for your arrays dynamically, or, if it works for your particular application, automatically (as a local variable of a well-chosen function).
* In principle. As #chux noted in comments, it may be the case that the compiler chooses to optimize out such an overwrite, which it might do if it could determine that the zeroed out data were never read. If this is a concern, then the best mitigation would probably be to declare the arrays volatile.
struct_x defaultStructX[6];
struct_x requiredStructX [6];
With the above statements, you have reserved memory for the two arrays of structs. This memory will remain allocated to you during the entire lifetime of your program and cannot be de-allocated until your program exits.
However, what you want to store in this memory is completely under your control. What is your definition of 'cleaning from memory'?
Do you haves some sensitive data that you want to erase from memory? You can always memset them to zero (or any other value) with:
memset(defaultStructX, 0x00, sizeof(defaultStructX));
Do you want to physically de-allocate the memory? If you want to have control over allocation and deallocation of chunks of memory, you should do so with malloc and free.
EDIT: Apparently the memset() solution can be derailed by compiler optimizations.
Here is a useful description of this issue from the SEI CERT C page.
You cannot manually deallocate anything that wasn’t allocated with malloc, calloc, or realloc. If you declared your arrays at file scope (outside of any function) or with the static keyword, then their memory won’t be released until the program terminates. Otherwise, their memory will be released when the function in which they were declared exits.
You can overwrite elements not currently in use with zeros or some other “not a value” value, but you cannot free the memory they occupy.
Arrays are not pointers. Expressions of array type are converted to pointers as necessary, but the array object itself is not a pointer.
Is there a possible way to make static array of structs points to NULL since I can't delete arrays and I want to clean the memory?
No. Arrays do not point. The array could be cleared.
I want to write code line to clean the defaultStructX from memory.
Simply assigned zeros to it.
memset() can get optimized out if the compiler sees the data was not subsequently read.
Alternative: use a loop with a pointer to volatile data to avoid loop from being optimized out.
volatile unsigned char *vp = defaultStructX;
for (size_t i = 0; i<sizeof defaultStructX; i++ {
vp[i] = 0;
}
Hmm... On one hand I love the detailed answer offered by John Bollinger and I believe he gave the correct answer for your use case... however...
There is a way to make the statically allocated array appear to point to NULL.
Obviously you can't assign NULL to an array. However, you can make a statically allocated "Array" seem to point to NULL by hiding it behind a pointer.
It might look like this:
#define STRUCT_X_ARRAY_LENGTH 6
struct_x defaultStructX___[STRUCT_X_ARRAY_LENGTH];
struct_x requiredStructX___[STRUCT_X_ARRAY_LENGTH];
struct_x * defaultStructX = defaultStructX___;
struct_x * requiredStructX = requiredStructX___;
gettingDefaultX(defaultStructX, STRUCT_X_ARRAY_LENGTH);
Now you could set the (no longer) "Array" to NULL using:
defaultStructX = NULL;
requiredStructX = NULL;
You could also reset the state (zero out the memory and reassign the correct memory address) using:
defaultStructX = defaultStructX___;
requiredStructX = requiredStructX___;
memset(defaultStructX___, 0 , sizeof(defaultStructX___));
memset(defaultStructX___, 0 , sizeof(requiredStructX___));
Not that this fits your use case, but it's a good enough technique that others might want to know about it.
I must say, I have quite a conundrum in a seemingly elementary problem. I have a structure, in which I would like to store an array as a field. I'd like to reuse this structure in different contexts, and sometimes I need a bigger array, sometimes a smaller one. C prohibits the use of variable-sized buffer. So the natural approach would be declaring a pointer to this array as struct member:
struct my {
struct other* array;
}
The problem with this approach however, is that I have to obey the rules of MISRA-C, which prohibits dynamic memory allocation. So then if I'd like to allocate memory and initialize the array, I'm forced to do:
var.array = malloc(n * sizeof(...));
which is forbidden by MISRA standards. How else can I do this?
Since you are following MISRA-C, I would guess that the software is somehow mission-critical, in which case all memory allocation must be deterministic. Heap allocation is banned by every safety standard out there, not just by MISRA-C but by the more general safety standards as well (IEC 61508, ISO 26262, DO-178 and so on).
In such systems, you must always design for the worst-case scenario, which will consume the most memory. You need to allocate exactly that much space, no more, no less. Everything else does not make sense in such a system.
Given those pre-requisites, you must allocate a static buffer of size LARGE_ENOUGH_FOR_WORST_CASE. Once you have realized this, you simply need to find a way to keep track of what kind of data you have stored in this buffer, by using an enum and maybe a "size used" counter.
Please note that not just malloc/calloc, but also VLAs and flexible array members are banned by MISRA-C:2012. And if you are using C90/MISRA-C:2004, there are no VLAs, nor are there any well-defined use of flexible array members - they invoked undefined behavior until C99.
Edit: This solution does not conform to MISRA-C rules.
You can kind of include VLAs in a struct definition, but only when it's inside a function. A way to get around this is to use a "flexible array member" at the end of your main struct, like so:
#include <stdio.h>
struct my {
int len;
int array[];
};
You can create functions that operate on this struct.
void print_my(struct my *my) {
int i;
for (i = 0; i < my->len; i++) {
printf("%d\n", my->array[i]);
}
}
Then, to create variable length versions of this struct, you can create a new type of struct in your function body, containing your my struct, but also defining a length for that buffer. This can be done with a varying size parameter. Then, for all the functions you call, you can just pass around a pointer to the contained struct my value, and they will work correctly.
void create_and_use_my(int nelements) {
int i;
// Declare the containing struct with variable number of elements.
struct {
struct my my;
int array[nelements];
} my_wrapper;
// Initialize the values in the struct.
my_wrapper.my.len = nelements;
for (i = 0; i < nelements; i++) {
my_wrapper.my.array[i] = i;
}
// Print the struct using the generic function above.
print_my(&my_wrapper.my);
}
You can call this function with any value of nelements and it will work fine. This requires C99, because it does use VLAs. Also, there are some GCC extensions that make this a bit easier.
Important: If you pass the struct my to another function, and not a pointer to it, I can pretty much guarantee you it will cause all sorts of errors, since it won't copy the variable length array with it.
Here's a thought that may be totally inappropriate for your situation, but given your constraints I'm not sure how else to deal with it.
Create a large static array and use this as your "heap":
static struct other heap[SOME_BIG_NUMBER];
You'll then "allocate" memory from this "heap" like so:
var.array = &heap[start_point];
You'll have to do some bookkeeping to keep track of what parts of your "heap" have been allocated. This assumes that you don't have any major constraints on the size of your executable.
I have a struct defined in this way.
typedef struct COUNTRY {
char Code[3];
char Country[30];
int Population;
float Expectancy;
struct Country *Pointer;
} COUNTRY;
I have seen an array of structs allocated like this:
COUNTRY *countries = calloc(128, sizeof(COUNTRY));
or maybe like this:
COUNTRY *countries = malloc(128 * sizeof(COUNTRY));
But what does this do:
COUNTRY countries[128] = {};
Because I am still able to write to each entries' fields in all cases. Is the third option just bad form? It seems better to me because you can put that line up with the rest of your variable declarations outside of main(). Otherwise, you can only calloc() or malloc() inside of main() or other function.
Am I doing something wrong?
This:
COUNTRY countries[128];
simply defines an object whose type is "array of 128 COUNTRY elements".
The = {} is an initializer -- but empty initializers are illegal in C (I think gcc supports them as an extension). A portable alternative is:
COUNTRY countries[128] = { 0 };
which initializes all members of all elements to zero (0 for integers, \0' for characters, 0.0 for floating-point, NULL for pointers, and recursively for sub-elements). But since you specified the number of elements in the array (as 128), the initializer has no effect on how the array object is allocated.
If the declaration occurs inside a function definition, the array object has automatic storage duration, which means that it ceases to exist when execution reaches the end of the enclosing block. Such objects are commonly allocated on "the stack".
If it occurs outside any function definition (at file scope) or if it has the keyword static, then it has static storage duration, which means that it continues to exist for the entire execution of the program.
Objects allocated with malloc or calloc have allocated storage duration, which means that they continue to exist until they're explicitly deallocated by a call to free(). Such objects are commonly allocated on "the heap". (I'm ignoring realloc(), which complicates the description a bit.)
The first two statements will allocate array of structs on the heap, while the last one will initialize the array of structs on the stack.
It is not a bad form, it is just a matter where you want your data to be stored - on the stack ( freed automatically when your variable goes out of the scope, stack usually have significantly smaller size then heap, so you could overflow it if you place big data structures there), or on the heap (lifetime of data is not related to the scope, you need to manually free your memory).
It seems better to me because you can put that line up with the rest of your variable declarations outside of main().
If you need statically allocated object with the lifetime of the program, use this approach, there's nothing wrong with it. Please note that in this particular case, variable is not stored on the stack, but in the .data segment of your program (check this question for more details: How are global variables stored?).
The last form is 'stack allocated' or 'statically allocated'. Like calloc, all of the fields will be zeroed out.
Inside a function it is 'stack allocated' and that memory will go away when the function returns.
Outside any function, at file scope, it is statically allocated and a global piece of memory allocated before main() starts.
malloc/calloc are used when you don't know how many you need at compile time. For example in a linked list, you need to allocate/deallocate nodes on the fly. When you use an array, you the know exactly how many you need at compile time.
What also differs is where the memory is taken from. If you declare an array in a function, the memory will be taken from the stack. In the case of malloc/calloc, the memory is set aside in the heap.
= {};
is GNU C extension and is the same as:
= {0};
Well, I can't understand when and why it is needed to allocate memory using malloc.
Here is my code:
#include <stdlib.h>
int main(int argc, const char *argv[]) {
typedef struct {
char *name;
char *sex;
int age;
} student;
// Now I can do two things
student p;
// Or
student *ptr = (student *)malloc(sizeof(student));
return 0;
}
Why is it needed to allocate memory when I can just use student p;?
malloc is used for dynamic memory allocation. As said, it is dynamic allocation which means you allocate the memory at run time. For example, when you don't know the amount of memory during compile time.
One example should clear this. Say you know there will be maximum 20 students. So you can create an array with static 20 elements. Your array will be able to hold maximum 20 students. But what if you don't know the number of students? Say the first input is the number of students. It could be 10, 20, 50 or whatever else. Now you will take input n = the number of students at run time and allocate that much memory dynamically using malloc.
This is just one example. There are many situations like this where dynamic allocation is needed.
Have a look at the man page malloc(3).
You use malloc when you need to allocate objects that must exist beyond the lifetime of execution of the current block (where a copy-on-return would be expensive as well), or if you need to allocate memory greater than the size of that stack (i.e., a 3 MB local stack array is a bad idea).
Before C99 introduced VLAs, you also needed it to perform allocation of a dynamically-sized array. However, it is needed for creation of dynamic data structures like trees, lists, and queues, which are used by many systems. There are probably many more reasons; these are just a few.
Expanding the structure of the example a little, consider this:
#include <stdio.h>
int main(int argc, const char *argv[]) {
typedef struct {
char *name;
char *sex;
char *insurance;
int age;
int yearInSchool;
float tuitionDue;
} student;
// Now I can do two things
student p;
// Or
student *p = malloc(sizeof *p);
}
C is a language that implicitly passes by value, rather than by reference. In this example, if we passed 'p' to a function to do some work on it, we would be creating a copy of the entire structure. This uses additional memory (the total of how much space that particular structure would require), is slower, and potentially does not scale well (more on this in a minute). However, by passing *p, we don't pass the entire structure. We only are passing an address in memory that refers to this structure. The amount of data passed is smaller (size of a pointer), and therefore the operation is faster.
Now, knowing this, imagine a program (like a student information system) which will have to create and manage a set of records in the thousands, or even tens of thousands. If you pass the whole structure by value, it will take longer to operate on a set of data, than it would just passing a pointer to each record.
Let's try and tackle this question considering different aspects.
Size
malloc allows you to allocate much larger memory spaces than the one allocated simply using student p; or int x[n];. The reason being malloc allocates the space on heap while the other allocates it on the stack.
The C programming language manages memory statically, automatically, or dynamically. Static-duration variables are allocated in main memory, usually along with the executable code of the program, and persist for the lifetime of the program; automatic-duration variables are allocated on the stack and come and go as functions are called and return. For static-duration and automatic-duration variables, the size of the allocation must be compile-time constant (except for the case of variable-length automatic arrays[5]). If the required size is not known until run-time (for example, if data of arbitrary size is being read from the user or from a disk file), then using fixed-size data objects is inadequate. (from Wikipedia)
Scope
Normally, the declared variables would get deleted/freed-up after the block in which it is declared (they are declared on the stack). On the other hand, variables with memory allocated using malloc remain till the time they are manually freed up.
This also means that it is not possible for you to create a variable/array/structure in a function and return its address (as the memory that it is pointing to, might get freed up). The compiler also tries to warn you about this by giving the warning:
Warning - address of stack memory associated with local variable 'matches' returned
For more details, read this.
Changing the Size (realloc)
As you may have guessed, it is not possible by the normal way.
Error detection
In case memory cannot be allocated: the normal way might cause your program to terminate while malloc will return a NULL which can easily be caught and handled within your program.
Making a change to string content in future
If you create store a string like char *some_memory = "Hello World"; you cannot do some_memory[0] = 'h'; as it is stored as string constant and the memory it is stored in, is read-only. If you use malloc instead, you can change the contents later on.
For more information, check this answer.
For more details related to variable-sized arrays, have a look at this.
malloc = Memory ALLOCation.
If you been through other programming languages, you might have used the new keyword.
Malloc does exactly the same thing in C. It takes a parameter, what size of memory needs to be allocated and it returns a pointer variable that points to the first memory block of the entire memory block, that you have created in the memory. Example -
int *p = malloc(sizeof(*p)*10);
Now, *p will point to the first block of the consecutive 10 integer blocks reserved in memory.
You can traverse through each block using the ++ and -- operator.
In this example, it seems quite useless indeed.
But now imagine that you are using sockets or file I/O and must read packets from variable length which you can only determine while running. Or when using sockets and each client connection need some storage on the server. You could make a static array, but this gives you a client limit which will be determined while compiling.
I have been writing C for only a scant few weeks and have not taken the time to worry myself too much about malloc(). Recently, though, a program of mine returned a string of happy faces instead of the true/false values I had expected to it.
If I create a struct like this:
typedef struct Cell {
struct Cell* subcells;
}
and then later initialize it like this
Cell makeCell(int dim) {
Cell newCell;
for(int i = 0; i < dim; i++) {
newCell.subcells[i] = makeCell(dim -1);
}
return newCell; //ha ha ha, this is here in my program don't worry!
}
Am I going to end up accessing happy faces stored in memory somewhere, or perhaps writing over previously existing cells, or what? My question is, how does C allocate memory when I haven't actually malloc()ed the appropriate amount of memory? What's the default?
Short answer: It isn't allocated for you.
Slightly longer answer: The subcells pointer is uninitialized and may point anywhere. This is a bug, and you should never allow it to happen.
Longer answer still: Automatic variables are allocated on the stack, global variables are allocated by the compiler and often occupy a special segment or may be in the heap. Global variables are initialized to zero by default. Automatic variables do not have a default value (they simply get the value found in memory) and the programmer is responsible for making sure they have good starting values (though many compilers will try to clue you in when you forget).
The newCell variable in you function is automatic, and is not initialized. You should fix that pronto. Either give newCell.subcells a meaningful value promptly, or point it at NULL until you allocate some space for it. That way you'll throw a segmentation violation if you try to dereference it before allocating some memory for it.
Worse still, you are returning a Cell by value, but assigning it to a Cell * when you try to fill the subcells array. Either return a pointer to a heap allocated object, or assign the value to a locally allocated object.
A usual idiom for this would have the form something like
Cell* makeCell(dim){
Cell *newCell = malloc(sizeof(Cell));
// error checking here
newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0?
// more error checking
for (int i=0; i<dim; ++i){
newCell->subCells[i] = makeCell(dim-1);
// what error checking do you need here?
// depends on your other error checking...
}
return newCell;
}
though I've left you a few problems to hammer out..
And note that you have to keep track of all the bits of memory that will eventually need to be deallocated...
There is no default value for your pointer. Your pointer will point to whatever it stores currently. As you haven't initialized it, the line
newCell.subcells[i] = ...
Effectively accesses some uncertain part of memory. Remember that subcells[i] is equivalent to
*(newCell.subcells + i)
If the left side contains some garbage, you will end up adding i to a garbage value and access the memory at that uncertain location. As you correctly said, you will have to initialize the pointer to point to some valid memory area:
newCell.subcells = malloc(bytecount)
After which line you can access that many bytes. With regards to other sources of memory, there are different kind of storage that all have their uses. What kind you get depends on what kind of object you have and which storage class you tell the compiler to use.
malloc returns a pointer to an object with no type. You can make a pointer point to that region of memory, and the type of the object will effectively become the type of the pointed to object type. The memory is not initialized to any value and access usually is slower. Objects so obtained are called allocated objects.
You can place objects globally. Their memory will be initialized to zero. For points, you will get NULL pointers, for floats you will get a proper zero too. You can rely on a proper initial value.
If you have local variables but use the static storage class specifier, then you will have the same initial value rule as for global objects. The memory usually is allocated the same way like global objects, but that's in no way a necessity.
If you have local variables without any storage class specifier or with auto, then your variable will be allocated on the stack (even though not defined so by C, this is what compilers do practically of course). You can take its address in which case the compiler will have to omit optimizations like putting it into registers of course.
Local variables used with the storage class specifier register, are marked as having a special storage. As a result, you cannot take its address anymore. In recent compilers, there is normally no need to use register anymore, because of their sophisticated optimizers. If you are really expert, then you may get some performance out of it if using it, though.
Objects have associated storage durations that can be used to show the different initialization rules (formally, they only define how long at least the objects live). Objects declared with auto and register have automatic storage duration and are not initialized. You have to explicitly initialize them if you want them to contain some value. If you do not, they will contain whatever the compiler left on the stack before they began lifetime. Objects that are allocated by malloc (or another function of that family, like calloc) have allocated storage duration. Their storage is not initialized either. An exception is when using calloc, in which case the memory is initialized to zero ("real" zero. i.e all bytes 0x00, without regard to any NULL pointer representation). Objects that are declared with static and global variables have static storage duration. Their storage is initialized to zero appropriate for their respective type. Note that an object must not have a type, but the only way to get a type-less object is using allocated storage. (An object in C is a "region of storage").
So what is what? Here is the fixed code. Because once you allocated a block of memory you can't get back anymore how many items you allocated, best is to always store that count somewhere. I've introduced a variale dim to the struct that gets the count stored.
Cell makeCell(int dim) {
/* automatic storage duration => need to init manually */
Cell newCell;
/* note that in case dim is zero, we can either get NULL or a
* unique non-null value back from malloc. This depends on the
* implementation. */
newCell.subcells = malloc(dim * sizeof(*newCell.subcells));
newCell.dim = dim;
/* the following can be used as a check for an out-of-memory
* situation:
* if(newCell.subcells == NULL && dim > 0) ... */
for(int i = 0; i < dim; i++) {
newCell.subcells[i] = makeCell(dim - 1);
}
return newCell;
}
Now, things look like this for dim=2:
Cell {
subcells => {
Cell {
subcells => {
Cell { subcells => {}, dim = 0 }
},
dim = 1
},
Cell {
subcells => {
Cell { subcells => {}, dim = 0 }
},
dim = 1
}
},
dim = 2
}
Note that in C, the return value of a function is not needed to be an object. No storage at all is required to exist. Consequently, you are not allowed to change it. For example, the following is not possible:
makeCells(0).dim++
You will need a "free function" that free's the allocated memory again. Because storage for allocated objects is not freed automatically. You have to call free to free that memory for every subcells pointer in your tree. It's left as an exercise for you to write that up :)
Anything not allocated on the heap (via malloc and similar calls) is allocated on the stack, instead. Because of that, anything created in a particular function without being malloc'd will be destroyed when the function ends. That includes objects returned; when the stack is unwound after a function call the returned object is copied to space set aside for it on the stack by the caller function.
Warning: If you want to return an object that has pointers to other objects in it, make sure that the objects pointed to are created on the heap, and better yet, create that object on the heap, too, unless it's not intended to survive the function in which it is created.
My question is, how does C allocate memory when I haven't actually malloc()ed the appropriate amount of memory? What's the default?
To not allocate memory. You have to explicity create it on the stack or dynamically.
In your example, subcells points to an undefined location, which is a bug. Your function should return a pointer to a Cell struct at some point.
Am I going to end up accessing happy faces stored in memory somewhere, or perhaps writing over previously existing cells, or what?
You are lucky that you got a happy face. On one of those unlucky days, it could've wiped your system clean ;)
My question is, how does C allocate memory when I haven't actually malloc()ed the appropriate amount of memory?
It doesn't. However, what happens is when you define you Cell newCell, the subCells pointer is initialized to garbage value. Which may be a 0 (in which case you'd get a crash) or some integer big enough to make it look like an actual memory address. The compiler, on such cases, would happily fetch whatever value is residing there and bring it back to you.
What's the default?
This is the behavior if you don't initialize your variables. And your makeCell function looks a little under-developed.
There are really three sections where things can be allocated - data, stack & heap.
In the case you mention, it would be allocated on the stack. The problem with allocating something on the stack is that it's only valid for the duration of the function. Once your function returns, that memory is reclaimed. So, if you return a pointer to something allocated on the stack, that pointer will be invalid. If you return the actual object though (not a pointer), a copy of the object will automatically be made for the calling function to use.
If you had declared it as a global variable (e.g. in a header file or outside of a function) it would be allocated in the data section of memory. The memory in this section is allocated automatically when your program starts and deallocated automatically when it finishes.
If you allocate something on the heap using malloc(), that memory is good for as long as you want to use it - until you call free() at which point it is released. This gives you the flexibility to allocate and deallocate memory as you need it (as opposed to using globals where everything is allocated up front and only released when your program terminates).
Local variables are "allocated" on the stack. The stack is a preallocated amount of memory to hold those local variables. The variables cease to be valid when the function exits and will be overwritten by whatever comes next.
In your case, the code is doing nothing since it doesn't return your result. Also, a pointer to an object on the stack will also cease to be valid when the scope exits, so I guess in your precise case (you seems to be doing a linked list), you will need to use malloc().
I'm going to pretend I'm the computer here, reading this code...
typedef struct Cell {
struct Cell* subcells;
}
This tells me:
We have a struct type called Cell
It contains a pointer called subcells
The pointer should be to something of type struct Cell
It doesn't tell me whether the pointer goes to one Cell or an array of Cell. When a new Cell is made, the value of that pointer is undefined until a value is assigned to it. It's Bad News to use pointers before defining them.
Cell makeCell(int dim) {
Cell newCell;
New Cell struct, with an undefined subcells pointer. All this does is reserve a little chunk of memory to be called newCell that is the size of a Cell struct. It doesn't change the values that were in that memory - they could be anything.
for(int i = 0; i < dim; i++) {
newCell.subcells[i] = makeCell(dim -1);
In order to get newCell.subcells[i], a calculation is made to offset from subcells by i, then that is dereferenced. Specifically, this means the value is pulled from that memory address. Take, for instance, i==0... Then we would be dereferencing the subcells pointer itself (no offset). Since subcells is undefined, it could be anything. Literally anything! So, this would ask for a value from somewhere completely random in memory. There's no guarantee of anything with the result. It may print something, it may crash. It definitely should not be done.
}
return newCell;
}
Any time you work with a pointer, it's important to make sure it's set to a value before you dereference it. Encourage your compiler to give you any warnings it can, many modern compilers can catch this sort of thing. You can also give pointers cutesy default values like 0xdeadbeef (yup! that's a number in hexadecimal, it's just also a word, so it looks funny) so that they stand out. (The %p option for printf is helpful for displaying pointers, as a crude form of debugging. Debugger programs also can show them quite well.)