Related
I have been reading that malloc is used for dynamic memory allocation. But if the following code works...
int main(void) {
int i, n;
printf("Enter the number of integers: ");
scanf("%d", &n);
// Dynamic allocation of memory?
int int_arr[n];
// Testing
for (int i = 0; i < n; i++) {
int_arr[i] = i * 10;
}
for (int i = 0; i < n; i++) {
printf("%d ", int_arr[i]);
}
printf("\n");
}
... what is the point of malloc? Isn't the code above just a simpler-to-read way to allocate memory dynamically?
I read on another Stack Overflow answer that if some sort of flag is set to "pedantic", then the code above would produce a compile error. But that doesn't really explain why malloc might be a better solution for dynamic memory allocation.
Look up the concepts for stack and heap; there's a lot of subtleties around the different types of memory. Local variables inside a function live in the stack and only exist within the function.
In your example, int_array only exists while execution of the function it is defined in has not ended, you couldn't pass it around between functions. You couldn't return int_array and expect it to work.
malloc() is used when you want to create a chunk of memory which exists on the heap. malloc returns a pointer to this memory. This pointer can be passed around as a variable (eg returned) from functions and can be used anywhere in your program to access your allocated chunk of memory until you free() it.
Example:
'''C
int main(int argc, char **argv){
int length = 10;
int *built_array = make_array(length); //malloc memory and pass heap pointer
int *array = make_array_wrong(length); //will not work. Array in function was in stack and no longer exists when function has returned.
built_array[3] = 5; //ok
array[3] = 5; //bad
free(built_array)
return 0;
}
int *make_array(int length){
int *my_pointer = malloc( length * sizeof int);
//do some error checking for real implementation
return my_pointer;
}
int *make_array_wrong(int length){
int array[length];
return array;
}
'''
Note:
There are plenty of ways to avoid having to use malloc at all, by pre-allocating sufficient memory in the callers, etc. This is recommended for embedded and safety critical programs where you want to be sure you'll never run out of memory.
Just because something looks prettier does not make it a better choice.
VLAs have a long list of problems, not the least of which they are not a sufficient replacement for heap-allocated memory.
The primary -- and most significant -- reason is that VLAs are not persistent dynamic data. That is, once your function terminates, the data is reclaimed (it exists on the stack, of all places!), meaning any other code still hanging on to it are SOL.
Your example code doesn't run into this problem because you aren't using it outside of the local context. Go ahead and try to use a VLA to build a binary tree, then add a node, then create a new tree and try to print them both.
The next issue is that the stack is not an appropriate place to allocate large amounts of dynamic data -- it is for function frames, which have a limited space to begin with. The global memory pool, OTOH, is specifically designed and optimized for this kind of usage.
It is good to ask questions and try to understand things. Just be careful that you don't believe yourself smarter than the many, many people who took what now is nearly 80 years of experience to design and implement systems that quite literally run the known universe. Such an obvious flaw would have been immediately recognized long, long ago and removed before either of us were born.
VLAs have their place, but it is, alas, small.
Declaring local variables takes the memory from the stack. This has two ramifications.
That memory is destroyed once the function returns.
Stack memory is limited, and is used for all local variables, as well as function return addresses. If you allocate large amounts of memory, you'll run into problems. Only use it for small amounts of memory.
When you have the following in your function code:
int int_arr[n];
It means you allocated space on the function stack, once the function will return this stack will cease to exist.
Image a use case where you need to return a data structure to a caller, for example:
Car* create_car(string model, string make)
{
Car* new_car = malloc(sizeof(*car));
...
return new_car;
}
Now, once the function will finish you will still have your car object, because it was allocated on the heap.
The memory allocated by int int_arr[n] is reserved only until execution of the routine ends (when it returns or is otherwise terminated, as by setjmp). That means you cannot allocate things in one order and free them in another. You cannot allocate a temporary work buffer, use it while computing some data, then allocate another buffer for the results, and free the temporary work buffer. To free the work buffer, you have to return from the function, and then the result buffer will be freed to.
With automatic allocations, you cannot read from a file, allocate records for each of the things read from the file, and then delete some of the records out of order. You simply have no dynamic control over the memory allocated; automatic allocations are forced into a strictly last-in first-out (LIFO) order.
You cannot write subroutines that allocate memory, initialize it and/or do other computations, and return the allocated memory to their callers.
(Some people may also point out that the stack memory commonly used for automatic objects is commonly limited to 1-8 mebibytes while the memory used for dynamic allocation is generally much larger. However, this is an artifact of settings selected for common use and can be changed; it is not inherent to the nature of automatic versus dynamic allocation.)
If the allocated memory is small and used only inside the function, malloc is indeed unnecessary.
If the memory amount is extremely large (usually MB or more), the above example may cause stack overflow.
If the memory is still used after the function returned, you need malloc or global variable (static allocation).
Note that the dynamic allocation through local variables as above may not be supported in some compiler.
The following code shows an example that repeatedly allocates memory without first calling free. Instead, it frees **sign after the loop.
#include <stdio.h>
#include <stdlib.h>
float ** fun(int nloc)
{
float **sign;
int i,nt=100,it,k;
sign=(float **)calloc(nloc,sizeof(float *));
for (i=0;i<nloc;i++)
sign[i] = (float *)calloc(nt,sizeof(float *));
for (it=0;it<nt;it++){
for (k=0;k<nloc;k++){
sign[k][it]=it*0.2;
}
}
return sign;
}
int main(int argc, char *argv[])
{
int i,isrc,n=3,nloc=1;
float **sign=NULL;
for (isrc=0;isrc<n;isrc++){
sign = fun(nloc);
}
for (i=0;i<nloc;i++){
free(sign[i]);
}
free(sign);
exit(0);
}
This is a correct code snippet. My question is: why is it legal that we can allocate memory for a pointer in each iteration without having to free it first?
[Supplementary message]:
Hi all, I think there's one case we cannot free memory in the loop. If buffer=p and p is defined outside the loop, like:
float *buffer, *p;
/* Elements of p calculated */
for (...){
/* allocate memory for **buffer */
buffer = p;
free(buffer)
/* if free here will cause p lost */
}
If free buffer at the end of each loop, it may cause p lost because buffer and p share the same memory address.
why is it legal that we can allocate memory for a pointer in each iteration without having to free it first?
The responsibility of freeing dynamically allocated memory is left on the programmer. It is legal because the compiler does not enforce it, although there are code checking tools that can flag this problem.
Freeing dynamically allocated memory should be done in the reverse order of allocation. For ex:
for (i=0;i<nloc;i++)
free(sign[i]);
free(sign);
It is legal because in C, you as the programmer are responsible for memory management. There is a very good reason for this. To quote another thread
Garbage collection requires data structures for tracking allocations
and/or reference counting. These create overhead in memory,
performance, and the complexity of the language. C++ is designed to be
"close to the metal", in other words, it takes the higher performance
side of the tradeoff vs convenience features. Other languages make
that tradeoff differently. This is one of the considerations in
choosing a language, which emphasis you prefer.
Not only is performance and code size a consideration, but different systems have difference addressing schemes for memory. It is also for this reason that C is easy to port and maintain across platforms, given that there is no garbage collection to alter.
EDIT: Some answers mention freeing memory space as opposed to the pointer itself, it is worth further specifying what that means: free() simply marks the allocated space as available, it is not 'freed' or erased, nor does any other operation occur on that memory space. It is then still incumbent on the programmer to delete the address that was assigned to the pointer variable.
My question is: why is it legal that we can allocate memory for a pointer in each iteration without having to free it first?
Short answer
C trades away safety to gain simplicity and performance.
Longer answer
You don't free the pointer. You free the memory block the pointer is pointing at.
Think about what malloc (and calloc) does. They both allocate a piece of memory, and if the allocation is successful they return a pointer to that memory block. The allocation functions (like all functions) has no insight, nor control whatsoever of what you do with the return value. The functions does not even see the pointer that you are assigning the return value to.
It would be fairly complex (relatively speaking, C has a pretty simple structure) to implement a protection against it. First, consider this code:
int * a = malloc(1);
int * b = a;
a = malloc(1);
free(b);
free(a);
This code has no memory leaks, even though we did the precise thing you asked about. We reassigned a before calling free upon the memory from the first malloc. It works fine, because we have saved the address in b.
So in order to disallow reassigning pointers that points to a memory block that no other pointer points at, the runtime environment would need to keep track of this, and it is not entirely trivial. And it would also need to create extra code to handle all this. Since this check needs to be done at runtime, it may affect performance.
Also, remember that even though C may seem very low level today, it was considered a high level language when it came.
I am really struggling with understanding what is actually going on when using malloc and reallocwhile using structures in C. I am trying to solve the phonebook problem where I have to create a phonebook that can add, delete, and show entries. There can be an unlimited number of entries so I have to define my structure array dynamically. My delete feature also has to find the desired entry, shift back all entries after that, then use realloc to free last entry. This is where a basic understanding of realloc would really help me I think.
The only part I think I have working properly is the add feature and I still don't know if I set that up properly - I just know it is working when I run it. If someone on a very basic level can help me I would appreciate it.
Here is my mess of code :-(
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void getEntry(int *);
typedef struct person {
char fname[20];
char lname[20];
int number[10];
}person;
void getInfo(int *,int,person*);
void delInfo(int*,int,person*);
void showInfo(int*,int,person*);
int main(){
int a=0;
int i=0;
int con=0;
person* contacts=(person*)malloc(sizeof(person));
person* pcontacts;
pcontacts=(person*)calloc(0,sizeof(person));
getEntry(&a);
while (a!=4){
switch (a){
case 1:
pcontacts=(person*)realloc(pcontacts,con* sizeof(person));
getInfo(&con,i,contacts);
break;
case 2:
delInfo(&con,i,contacts);
break;
case 3:
showInfo(&con,i,contacts);
break;
default:
printf("\n Error in response. Please try again: ");
break;
}
getEntry(&a);
}
printf("\n Thank you for using the Phone Book Application!\n\n");
free(contacts);
return 0;
}
void getEntry(int *a1){
int b;
printf("\n\n\n Phone Book Application\n\n 1) Add Friend\n 2) Delete Friend\n 3) Show Phone Book Entries\n 4) Exit\n\n Make a selection: ");
scanf("%d",&b);
*a1 = b;
}
void getInfo(int *con,int i,person*contacts){
printf("\n Enter first name: ");
scanf("%s",contacts[*con].fname);
printf(" Enter last name: ");
scanf("%s",contacts[*con].lname);
printf(" Enter telephone number without spaces or hyphens: ");
scanf("%d",contacts[*con].number);
(*con)++;
printf("\n Entry Saved.");
}
void delInfo(int *con,int i,person*contacts){
char delfirst[20];
char dellast[20];
printf("\n First Name: ");
scanf("%s",delfirst);
printf(" Last Name: ");
scanf("%s",dellast);
for (i=0; i<*con;i++){
if (delfirst==contacts[i].fname && dellast==contacts[i].lname){
}
}
}
void showInfo(int *con,int i,person*contacts){
char nullstr[1]={"\0"};
if (*con>0){
printf("\n Current Listings:\n");
for (i=0;i<*con;i++){
if (strcmp(nullstr,contacts[i].fname)!=0){
printf(" %s %s %i\n",contacts[i].fname,contacts[i].lname,contacts[i].number);
}
else {};
}
}
else {
printf("\n No Entries\n");
}
}
Firstly, let me clarify a point that was made in another answer:
A word of caution concerning portability: according to the The GNU C Library Reference Manual, implementations before ISO C may not support this behavior.
Unless you are working with decades old embedded hardware and their proprietary compilers, you will rarely (if ever) run into a compiler that doesn't support at least the C89 (ANSI C) standard.
Now that's out of the way, let's move on to malloc() and realloc(). To understand the latter, let's look at the former.
The man page for malloc() states the following:
The malloc() . . . [and] realloc() . . . functions allocate memory. The allocated memory is aligned such that it can be used for any data type. . . The free() function frees allocations that were created via the preceding allocation functions.
That kind of helps us. Basically, your operating system "owns" all available memory physically installed in your computer. All of the RAM, swap space, etc. is all allocated by the system at the kernel level.
However, processes in user space (e.g. your program) don't want/need to manage all of the memory in the system - not to mention the security risks involved (think if I could just willy nilly edit the memory in another process with no security measures!).
Thus, the operating system will manage a process' memory and allocate different parts of the system's memory for the process to use.
Generally speaking, the operating system divides up the memory into what is called a block or a page. Simply put, if you divide your memory up into appropriate sizes (generally an exponent of 2, and at the very least a multiple of the CPU's register size) you get some very steep optimizations since the CPU can only access memory at certain granularities.
Due to all of this, the program itself must ask the kernel that memory be allocated to it so the program can use it and ensure that it is the only process that has access to it.
malloc() is the standard function call that performs this task. When you call malloc() with a length, the system will attempt to find a contiguous range of unused system memory and maps it into the process' virtual memory space, storing the address and the length of the memory that you requested.
If it can't find enough memory, it simply returns NULL (0) - as you can read in the man page :)
Subsequently, when you call free(), it looks for the address in the virtual memory table, retrieves the length that you originally malloc()'d, and tells the system that the process no longer needs that memory region.
One last point: a segmentation fault is oftentimes caused by the access of memory no longer allocated, even though it once was. This is why C++ paradigms such as RAII were invented - simply to reduce the likelihood of prematurely freeing memory, or not freeing it at all (i.e. using up a bunch of memory that isn't deallocated until the process returns).
Armed with the above knowledge, realloc() should be a piece of cake. Once again, let's take a look at its man page:
The realloc(ptr, size) function tries to change the size of the allocation pointed to by ptr to size, and returns ptr. If there is not enough room to enlarge the memory allocation pointed to by ptr, realloc() creates a new allocation, copies as much of the old data pointed to by ptr as will fit to the new allocation, frees the old allocation, and returns a pointer to the allocated memory. If ptr is NULL, realloc() is identical to a call to malloc() for size bytes. If size is zero and ptr is not NULL, a new, minimum sized object is allocated and the original object is freed.
It's kind of long, but basically tells you exactly what you need to know.
realloc() is very similar to malloc() in that you pass it a length, but you also pass a previously malloc()'d pointer to it to resize.
In short, you're basically extending a region of memory you previously allocated via malloc().
The confusion is usually brought about by the fact realloc() doesn't always expand the initial memory region, and what's more, will even free() the memory region you passed into it!
Remember my point above about contiguous memory: if you asked malloc() to allocate 10 bytes, and those 10 bytes just happened to fit right between two other regions of memory that are already allocated, and then you want to resize that 10 byte region to, say, 20 bytes -- what would have to happen?
Since the memory region must be contiguous, that means it would have to find a new spot for the memory to live that could house all 20 bytes contiguously!
In that scenario, realloc() is nice enough to find the new region with enough space, copy all of the old data from the original memory region (pointed to by the pointer you supplied realloc()), free the old region, and then return the address of the new region.
This is why you must always store the return address of realloc():
char *ptr = malloc(100);
ptr = realloc(ptr, 400); // not guaranteed the first and
// second addresses will be the same!
One way you can handle this is by declaring a pointer to a person struct, and accessing the referenced memory like an array:
person *contacts;
You can initialize with no entries:
int num_entries = 0;
contacts = NULL;
Then, as you add entries:
num_entries++;
contacts = realloc(contacts, sizeof(*contacts) * num_entries);
Then, you can read the new contact information into the new struct, which is contacts[num_entries - 1]. As you delete entries, after copying the person structs to their new locations in the dynamic array (if the ordering of entries is not an issue, you could just copy the last entry over the one you want to delete: contacts[delete_index] = contacts[num_entries - 1]), you simply do:
num_entries--;
contacts = realloc(contacts, sizeof(*contacts) * num_entries);
You should probably check the return value of realloc() to handle allocation errors.
One nice feature of realloc() is that, when passed a null pointer, it behaves as if malloc() had been called. So, you can start with a null pointer representing an empty contacts list, and use realloc() for all additions and deletions. A word of caution concerning portability: according to the The GNU C Library Reference Manual, implementations before ISO C may not support this behavior. I take this to mean that you are safe using this with ANSI C and later. I have never had a problem with this, but maybe someone who has will chime in.
From what I understand, the malloc function takes a variable and allocates memory as asked. In this case, it will ask the compiler to prepare memory in order to fit the equivalence of twenty double variables. Is my way of understanding it correctly, and why must it be used?
double *q;
q=(double *)malloc(20*sizeof(double));
for (i=0;i<20; i++)
{
*(q+i)= (double) rand();
}
You don't have to use malloc() when:
The size is known at compile time, as in your example.
You are using C99 or C2011 with VLA (variable length array) support.
Note that malloc() allocates memory at runtime, not at compile time. The compiler is only involved to the extent that it ensures the correct function is called; it is malloc() that does the allocation.
Your example mentions 'equivalence of ten integers'. It is very seldom that 20 double occupy the same space as 10 int. Usually, 10 double will occupy the same space as 20 int (when sizeof(int) == 4 and sizeof(double) == 8, which is a very commonly found setting).
It's used to allocate memory at run-time rather than compile-time. So if your data arrays are based on some sort of input from the user, database, file, etc. then malloc must be used once the desired size is known.
The variable q is a pointer, meaning it stores an address in memory. malloc is asking the system to create a section of memory and return the address of that section of memory, which is stored in q. So q points to the starting location of the memory you requested.
Care must be taken not to alter q unintentionally. For instance, if you did:
q = (double *)malloc(20*sizeof(double));
q = (double *)malloc(10*sizeof(double));
you will lose access to the first section of 20 double's and introduce a memory leak.
When you use malloc you are asking the system "Hey, I want this many bytes of memory" and then he will either say "Sorry, I'm all out" or "Ok! Here is an address to the memory you wanted. Don't lose it".
It's generally a good idea to put big datasets in the heap (where malloc gets your memory from) and a pointer to that memory on the stack (where code execution takes place). This becomes more important on embedded platforms where you have limited memory. You have to decide how you want to divvy up the physical memory between the stack and heap. Too much stack and you can't dynamically allocate much memory. Too little stack and you can function call your way right out of it (also known as a stack overflow :P)
As the others said, malloc is used to allocate memory. It is important to note that malloc will allocate memory from the heap, and thus the memory is persistent until it is free'd. Otherwise, without malloc, declaring something like double vals[20] will allocate memory on the stack. When you exit the function, that memory is popped off of the stack.
So for example, say you are in a function and you don't care about the persistence of values. Then the following would be suitable:
void some_function() {
double vals[20];
for(int i = 0; i < 20; i++) {
vals[i] = (double)rand();
}
}
Now if you have some global structure or something that stores data, that has a lifetime longer than that of just the function, then using malloc to allocate that memory from the heap is required (alternatively, you can declare it as a global variable, and the memory will be preallocated for you).
In you example, you could have declared double q[20]; without the malloc and it would work.
malloc is a standard way to get dynamically allocated memory (malloc is often built above low-level memory acquisition primitives like mmap on Linux).
You want to get dynamically allocated memory resources, notably when the size of the allocated thing (here, your q pointer) depends upon runtime parameters (e.g. depends upon input). The bad alternative would be to allocate all statically, but then the static size of your data is a strong built-in limitation, and you don't like that.
Dynamic resource allocation enables you to run the same program on a cheap tablet (with half a gigabyte of RAM) and an expensive super-computer (with terabytes of RAM). You can allocate different size of data.
Don't forget to test the result of malloc; it can fail by returning NULL. At the very least, code:
int* q = malloc (10*sizeof(int));
if (!q) {
perror("q allocation failed");
exit(EXIT_FAILURE);
};
and always initialize malloc-ed memory (you could prefer using calloc which zeroes the allocated memory).
Don't forget to later free the malloc-ed memory. On Linux, learn about using valgrind. Be scared of memory leaks and dangling pointers. Recognize that the liveness of some data is a non-modular property of the entire program. Read about garbage collection!, and consider perhaps using Boehm's conservative garbage collector (by calling GC_malloc instead of malloc).
You use malloc() to allocate memory dynamically in C. (Allocate the memory at the run time)
You use it because sometimes you don't know how much memory you'll use when you write your program.
You don't have to use it when you know thow many elements the array will hold at compile time.
Another important thing to notice that if you want to return an array from a function, you will want to return an array which was not defined inside the function on the stack. Instead, you'll want to dynamically allocate an array (on the heap) and return a pointer to this block:
int *returnArray(int n)
{
int i;
int *arr = (int *)malloc(sizeof(int) * n);
if (arr == NULL)
{
return NULL;
}
//...
//fill the array or manipulate it
//...
return arr; //return the pointer
}
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.