Category: 14. Memory Management

https://cdn3d.iconscout.com/3d/premium/thumb/memory-3d-icon-png-download-12641016.png

  • Memory Leaks

    Memory leaks take place when the memory allocated to variables is not deallocated after their use. The allocated memory is no longer in use by the program, but the space remains reserved for no reason. That’s why it is called a memory leak.

    In a memory leak, some blocks of memory may be wasted. Memory leaks can slow down the system performance, even when the system has enough memory.

    In C programming, memory is allocated using the malloc() / calloc() methods and released using the free() method. Let us understand by example how memory leaks occur −

    Example: Memory Leak in C

    In this example, we are allocating the size of the variable but not deallocating the space after its use, so it may cause a memory leak −

    #include <stdio.h>#include <stdlib.h>	intmain(){// allocate memoryint*ptr =(int*)malloc(sizeof(int));*ptr =100;printf("%d\n",*ptr);// we are just allocating the memory// not deallocatingreturn0;}

    The code will work as expected, but without freeing the allocated memory, it causes a memory leak. Given below is the output of the code −

    100
    

    Causes of Memory Leaks

    C allows programmers to allocate memory during program execution using functions from the stdlib.h library, such as malloc() (allocate a block of memory), calloc() (allocate zero-initialized memory), and realloc() (resize an allocated block). This memory is allocated on the heap. If the allocated memory is not released when no longer needed, it remains reserved unnecessarily, even after the program has finished using it.

    Following are the prime causes that can result in memory leaks −

    • Memory is allocated but not released or deallocated.
    • If a pointer to allocated memory is overwritten or goes out of scope without being freed, the memory it points to becomes unreachable.
    • In long-running programs, even small memory leaks can accumulate over time, gradually consuming system memory and eventually slowing down or crashing the program.

    Example: Memory Leak in C Using calloc()

    In this example, we are allocating memory using the calloc() function, but we are not freeing the memory, which causes a memory leak −

    #include <stdio.h>#include <stdlib.h>intmain(){int*ptr;// Allocate memory for 5 integers using calloc
       ptr =(int*)calloc(5,sizeof(int));if(ptr ==NULL){printf("Memory allocation failed.\n");return1;}// Use the allocated memoryfor(int i =0; i <5; i++){
    
      ptr&#91;i]= i +1;printf("%d ", ptr&#91;i]);}printf("\n");// This line is intentionally commented to // create a memory leak// free(ptr);     return0;}</code></pre>

    When you run this code, it will produce the following output −

    1 2 3 4 5
    

    How to Prevent Memory Leaks

    Following are the ways to prevent memory leaks −

    • Always free/deallocate the memory after use.
    • Do not overwrite the pointer before freeing its old memory.
    • Unlike C++, C does not have smart pointers, so memory must be explicitly freed using the free() function.
    • We can use tools like Valgrind in C to detect memory leaks during testing.

    To prevent memory leaks, we use the free() function. It deallocates the allocated memory and helps avoid memory leaks.

    Example: Preventing Memory Leaks

    In this example, we use the free() function to prevent memory leak −

    #include <stdio.h>#include <stdlib.h>intmain(){// allocate memoryint*ptr =(int*)malloc(sizeof(int));*ptr =100;printf("%d\n",*ptr);// free allocated memoryfree(ptr);return0;}

    Following is the output of the above code −

    100
    

    Conclusion

    Memory leaks take place when a program allocates memory to its variables and fuctions but does not free/deallocate the memory after its use. Over time, unused memory occupies space and becomes wasted, which can slow down the program or even cause it to crash. Proper memory management is important to prevent these issues.

  • Dynamic Array Resizing

    Dynamic arrays are arrays whose size can change during runtime. To manage dynamic arrays in C, memory is allocated at runtime using pointers and functions from the <stdlib.h> header file. Specifically, malloc() and calloc() are the two primary functions used to allocate memory for dynamic arrays.

    • malloc() (memory allocation) allocates a specified number of bytes of memory but does not initialize them.
    • calloc() (contiguous allocation) allocates memory for an array of elements and initializes all the bytes to zero.

    In this chapter, we will see how we can resize this allocated memory for dynamic arrays.

    Dynamic Array Resizing

    We can resize dynamic arrays by increasing or decreasing the dynamically allocated memory using the realloc() function. If more elements need to be stored, we can increase the size or if fewer elements are required, we can decrease it.

    We will look at how to resize memory using the realloc() function, and then how to free the allocated memory in C using −

    • realloc() function
    • free() function

    We will also look at −

    • Variable-Length Arrays (VLAs)
    • Array Members in Structures

    Next, we will go through each function in detail and see how it allocates and frees memory with examples.

    Dynamic Memory Using realloc() Function

    The realloc() funciton in C is used to resize a previously allocated memory block. It can increase or decrease the size of a dynamic array while preserving the existing data.

    Following is the syntax for reallocating memory using the realloc() function in C −

    ptr =(castType*)realloc(pointer, new_size_in_bytes);

    Here, ptr is the pointer to the previously allocated memory, castType is the type of the pointer (like int* or float*), and new_size_in_bytes is the new size of the memory block.

    Example of realloc() Function

    In this example, we first allocate memory for an array of three integers using malloc() funciton. Then, we resize the array to five integers using realloc() function, assign values, and print the array elements.

    #include <stdio.h>#include <stdlib.h>intmain(){int*numbersArray;int index;// Allocate memory for 3 integers
    
    numbersArray =(int*)malloc(3*sizeof(int));// Check if memory allocation was successfulif(numbersArray ==NULL){printf("Memory allocation failed\n");return1;}else{printf("Memory successfully allocated for 3 elements.\n");}// Assign values to the arrayfor(index =0; index &lt;3; index++){
        numbersArray&#91;index]=(index +1)*10;// Store 10, 20, 30}// Resize memory to hold 5 integers
    numbersArray =(int*)realloc(numbersArray,5*sizeof(int));// Check if memory reallocation was successfulif(numbersArray ==NULL){printf("Memory reallocation failed\n");return1;}else{printf("Memory successfully reallocated for 5 elements.\n");}// Assign values to the new elementsfor(index =3; index &lt;5; index++){
        numbersArray&#91;index]=(index +1)*10;// Store 40, 50}// Print all valuesprintf("Array elements after realloc: ");for(index =0; index &lt;5; index++){printf("%d ", numbersArray&#91;index]);}return0;}</code></pre>

    Following is the output of the above program where we reallocate the previously allocated array and display its elements.

    Memory successfully allocated for 3 elements.
    Memory successfully reallocated for 5 elements.
    Array elements after realloc: 10 20 30 40 50 
    

    free() Function

    The free() function in C is used to deallocate/release memory that was previously allocated dynamically using malloc()calloc(), or realloc(). This gives the memory back to the system and helps prevent memory leaks. After freeing, the pointer still exists, but the memory it points to is no longer valid, so don't use the pointer after freeing it.

    The syntax for using free() function is −

    free(pointer);

    Here, pointer is the pointer to the memory block we want to deallocate.

    Example of free() Function

    In this example, we use malloc() fucntion to allocate memory for an array of five integers. We store values in the array, display them, and then free the allocated memory using the free() funciton.

    #include <stdio.h>#include <stdlib.h>intmain(){int*numbersArray;// Pointer for dynamic arrayint index;// Allocate memory for 5 integers
    
    numbersArray =(int*)malloc(5*sizeof(int));// Check if memory allocation was successfulif(numbersArray ==NULL){printf("Memory allocation failed\n");return1;}else{printf("Memory successfully allocated for the array.\n");}// Assign values to the arrayfor(index =0; index &lt;5; index++){
        numbersArray&#91;index]=(index +1)*10;// Store 10, 20, 30, 40, 50}// Print the valuesprintf("Array elements: ");for(index =0; index &lt;5; index++){printf("%d ", numbersArray&#91;index]);}printf("\n");// Free the allocated memoryfree(numbersArray);printf("Memory has been freed.\n");return0;}</code></pre>

    Following is the output of the program showing the array allocation, its elements, and successful memory deallocation.

    Memory successfully allocated for the array.
    Array elements: 10 20 30 40 50 
    Memory has been freed.
    

    Variable-Length Arrays (VLAs)

    Variable-Length Arrays (VLAs) in C are arrays whose size is decided at runtime. Unlike dynamic arrays that are created on the heap using functions like malloc(), variable-length arrays are created on the stack, which means they are automatically freed when the function ends.

    Variable-Length Arrays are supported only in C99 and later standards, and their size must always be a positive value. It cannot be zero.

    Following is the syntax for declaring a variable length array in C −

    data_type arrayName[size];

    Here, data_type is the type of elements (like int or float), arrayName is the name of the array, and size is a value that is decided at runtime.

    Example of Variable-Length Array

    In this example, we decide the array size at runtime using a variable. The VLA numbers[size] is created on the stack. We store values in a loop and print them. The memory is automatically freed when the program ends.

    #include <stdio.h>intmain(){int size =5;// Size decided at runtimeint index;// Create a Variable-Length Arrayint numbers[size];// Store values in the arrayfor(index =0; index < size; index++){
    
        numbers&#91;index]=(index +1)*10;// Store 10, 20, 30, 40, 50}// Print the arrayprintf("Array elements: ");for(index =0; index &lt; size; index++){printf("%d ", numbers&#91;index]);}return0;}</code></pre>

    Following is the output of the above program that displays the elements of the variable length array.

    Array elements: 10 20 30 40 50
    

    Array Members in Structures

    Arrays can be members of structures in C. They can have a fixed size, which is allocated along with the structure, or be pointer-based, which requires dynamic memory allocation using malloc() or calloc() functions.

    Following is the syntax for a fixed-size array inside a structure in C −

    structStructName{
    
    data_type arrayName&#91;size];// other members};</code></pre>

    Here, data_type is the type of elements, arrayName is the name of the array, and size is determined at compile time.

    Following is the syntax for a pointer-based dynamic array inside a structure in C −

    structStructName{
    
    data_type *arrayName;int size;// store the array size// other members};</code></pre>

    Here, data_type *arrayName is a pointer to the array elements, and size stores the number of elements.

    Some important points to know −

    • Fixed-size arrays inside structures have memory allocated along with the structure.
    • Pointer-based arrays require dynamic memory allocation using malloc() or calloc() functions.
    • Always free dynamically allocated memory after use to prevent memory leaks.

    Example of Array as Structure Member

    In this example, we create a structure Student with a pointer to an array of marks. Memory is dynamically allocated for 3 subjects. Then, we store the values in the array and print them. Finally, we free the allocated memory.

    #include <stdio.h>#include <stdlib.h>structStudent{int*marks;// Pointer for dynamic arrayint subjects;};intmain(){structStudent s1;int i;
    
    s1.subjects =3;// Allocate memory for marks
    s1.marks =(int*)malloc(s1.subjects *sizeof(int));if(s1.marks ==NULL){printf("Memory allocation failed\n");return1;}// Store values in the arrayfor(i =0; i &lt; s1.subjects; i++){
        s1.marks&#91;i]=(i +1)*10;// 10, 20, 30}// Print the marksprintf("Marks of student: ");for(i =0; i &lt; s1.subjects; i++){printf("%d ", s1.marks&#91;i]);}// Free allocated memoryfree(s1.marks);return0;}</code></pre>

    Following is the output that displays the marks of the student −

    Marks of student: 10 20 30
    

    In this chapter, we learned how to resize arrays in C using realloc() function and free memory using free() function. We also covered variable-length arrays, arrays in structures, and overall memory management in C. We can also create a two-dimensional dynamic array in C, and there are different functions for this.

  • Storage Classes

    C storage classes define the scope (visibility) and lifetime of variables and/or functions within a C Program. They precede the type that they modify.

    We have four different storage classes in a C program −

    • auto
    • register
    • static
    • extern

    The auto Storage Class

    The auto is a default storage class for all variables that are declared inside a function or a block. The keyword “auto“, which is optional, can be used to define local variables.

    The scope and lifetime of auto variables are within the same block in which they are declared.

    Example of auto Storage Class

    The following code statements demonstrate the declaration of an automatic (auto) variable −

    {int mount;autoint month;}

    The example above defines two variables with in the same storage class. ‘auto’ can only be used within functions, i.e., local variables.

    The register Storage Class

    The register storage class is used to define local variables that should be stored in a register instead of RAM. This means that the variable has a maximum size equal to the register size (usually one word) and can’t have the unary ‘&’ operator applied to it (as it does not have a memory location).

    The register should only be used for variables that require quick access such as counters. It should also be noted that defining ‘register’ does not mean that the variable will be stored in a register. It means that it MIGHT be stored in a register depending on hardware and implementation restrictions.

    Example of register Storage Class

    The following code statement demonstrates the declaration of a register variable −

    {registerint  miles;}

    The static Storage Class

    The static storage class instructs the compiler to keep a local variable in existence during the life-time of the program instead of creating and destroying it each time it comes into and goes out of scope. Therefore, making local variables static allows them to maintain their values between function calls.

    The static modifier may also be applied to global variables. When this is done, it causes that variable’s scope to be restricted to the file in which it is declared.

    In C programming, when static is used on a global variable, it causes only one copy of that member to be shared by all the objects of its class.

    Example of static Storage Class

    The following example demonstrates the use of a static storage class in a C program −

    #include <stdio.h>/* function declaration */voidfunc(void);staticint count =5;/* global variable */main(){while(count--){func();}return0;}/* function definition */voidfunc(void){staticint i =5;/* local static variable */
       i++;printf("i is %d and count is %d\n", i, count);}

    Output

    When the above code is compiled and executed, it produces the following result −

    i is 6 and count is 4
    i is 7 and count is 3
    i is 8 and count is 2
    i is 9 and count is 1
    i is 10 and count is 0
    

    The extern Storage Class

    The extern storage class is used to give a reference of a global variable that is visible to ALL the program files. When you use ‘extern’, the variable cannot be initialized however, it points the variable name at a storage location that has been previously defined.

    When you have multiple files and you define a global variable or function, which will also be used in other files, then extern will be used in another file to provide the reference of defined variable or function. Just for understanding, extern is used to declare a global variable or function in another file.

    The extern modifier is most commonly used when there are two or more files sharing the same global variables or functions as explained below.

    Example of extern Storage Class

    The example of an extern storage class may contain two or more files. Here is an example demonstrating the use of an extern storage class in C language −

    First File: main.c

    #include <stdio.h>int count;externvoidwrite_extern();main(){
       count =5;write_extern();}

    Second File: support.c

    #include <stdio.h>externint count;voidwrite_extern(void){printf("Count is %d\n", count);}

    Here, extern is being used to declare count in the second file, whereas it has its definition in the first file (main.c). Now, compile these two files as follows −

    $gcc main.c support.c
    

    It will produce the executable program a.out. When this program is executed, it will produce the following output −

    Count is 5
    

    Use of storage classes

    Storage classes are used to define the scope, visibility, lifetime, and initial (default) value of a variable.

    Summary of Storage Classes

    The following table provides a summary of the scope, default value, and lifetime of variables having different storage classes −

    Storage ClassNameMemoryScope, Default ValueLifetime
    autoAutomaticInternal MemoryLocal Scope, Garbage ValueWithin the same function or block in which they are declared.
    registerRegisterRegisterLocal Scope, 0Within the same function or block in which they are declared.
    staticStaticInternal MemoryLocal Scope, 0Within the program i.e., as long as program is running.
    externExternalInternal MemoryGlobal Scope, 0Within the program i.e., as long as program is running.
  • Memory Address

    Memory Address in C

    The memory address is assigned to a variable when a variable is declared in C language. C compiler stores the value of the variable in the different segments of the memory.

    Segments of Memory

    Different elements of a C program are stored in different segments of the computers memory, which has the following segments −

    • Text segment − A text segment, also known as a code segment or simply as text, is one of the sections of a progris used to store the object version of the C program.
    • Initialized data segment − The global variables and static variables that are initialized by the programmer are allocated the memory in the initialized data segment.
    • Uninitialized data segment − An uninitialized data segment also called as bss (stands for block started by symbol). The program allocates memory for this segment when it loads. Every data in bss is initialized to arithmetic “0” and pointers to null pointer by the kernel before the C program executes.
    • Stack − Stack is a LIFO (last in first out) data structure. Stack segment stores the value of local variables and values of parameters passed to a function. It also maintains the pointer to which a function call returns.
    • Heap − Heap is used for allocating memory during the runtime. All the functions that perform dynamic memory allocation deal with heap.

    Accessing Memory Address

    The memory addresses in C can be accessed or specified through the Address of (&) operator. To print a memory address using the printf() function, you need to use %p format specifier.

    Syntax

    Below is the syntax to access memory address −

    &variable_name
    

    Example

    In the following example, we are declaring two variables and printing their memory addresses −

    #include <stdio.h>intmain(){// Declaring two variablesint a;int b;// Accessing their memory// addresses and print themprintf("Memory address of a is %p\n",&a);printf("Memory address of b is %p\n",&b);return0;}

    How Does C Compiler Allocate Memory?

    Memory can be considered of as an array of bytes where each address is on index in the array and holds 1 byte.

    When you declare a variable in a C program, the C compiler allocates a random memory location to it, depending on the size requirement, which depends on the type.

    When an int variable is declared −

    int x =10;

    The compiler assigns the value in a random byte address. Since an int type needs 4 bytes, the next four addresses are earmarked for it.

    C allows you to find out which address has been allocated to a variable. You can use the %p format specifier to print the hexadecimal address of the memory location.

    char x ='A';printf("address of x: %p\n",&x);

    This prints the address of “x” in hexadecimal format −

    Address of x:000000000061FE1F
    

    Example

    Arrays in C are contiguous memory areas that hold a number of values of the same data type (int, long, *char, etc.).

    #include <stdio.h>intmain(){// initialize an array of intsint numbers[5]={1,2,3,4,5};int i =0;// print the address of the array variableprintf("numbers = %p\n", numbers);// print addresses of each array indexdo{printf("numbers[%u] = %p\n", i,(void*)(&numbers[i]));
    
      i++;}while(i &lt;5);// print the size of the arrayprintf("sizeof(numbers) = %lu\n",sizeof(numbers));}</code></pre>

    Output

    When you run this code, it will produce the following output −

    numbers = 0x7fff0815c0e0
    numbers[0] = 0x7fff0815c0e0
    numbers[1] = 0x7fff0815c0e4
    numbers[2] = 0x7fff0815c0e8
    numbers[3] = 0x7fff0815c0ec
    numbers[4] = 0x7fff0815c0f0
    sizeof(numbers) = 20
    
  • Memory Management

    One of the important characteristics of C is that the compiler manages how the memory is allocated to the variables declared in the code. Once the compiler allocates the required bytes of memory, it cannot be changed during the runtime.

    The compiler employs static memory allocation approach. However, there are times where you may need to allocate the memory on demand, during the runtime. Read this chapter to understand how dynamic memory management works in C.

    Functions for Dynamic Memory Management in C

    The C programming language provides several functions for dynamic memory allocation and management. These functions can be found in the <stdlib.h> header file.

    FunctionDescription
    void *calloc(int num, int size);This function allocates an array of num elements each of which size in bytes will be size.
    void free(void *address);This function releases a block of memory block specified by address.
    void *malloc(size_t size);This function allocates an array of num bytes and leave them uninitialized.
    void *realloc(void *address, int newsize);This function re-allocates memory extending it up to newsize.

    Allocating Memory Dynamically

    If you are aware of the size of an array, then it is easy and you can define it as an array. For example, if you need to store the name of a person, then you can safely define an array to hold a maximum of 100 characters (assuming that a name wouldn’t contain more than 100 characters). So, you can define an array as follows −

    char name[100];

    This is an example of static memory allocation. Now let us consider a situation where you have no idea about the length of the text you need to store, for example, you want to store a detailed description about a topic. In such a case, if the content is less than the allocated size, the allocated memory is wasted during the programs execution.

    On the other hand, if the size required is more than the allocated memory size, it may lead to unpredictable behaviour, including causing the data to be corrupted, as the size of the array cannot be dynamically altered.

    It is in these kind of situations you need to use the dynamic memory allocation methods as described in this chapter.

    The malloc() Function

    This function is defined in the stdlib.h header file. It allocates a block memory of the required size and returns a void pointer.

    void*malloc(size)

    The size parameter refers to the block of memory in bytes. To allocate a memory required for a specified data type, you need to use the typecasting operator.

    For example, the following snippet allocates the memory required to store an int type −

    int*ptr;
    ptr =(int*)malloc(sizeof(int));

    Here we need to define a pointer to character without defining how much memory is required and later, based on requirement, we can allocate memory.

    Example

    The following example uses the malloc() function to allocate the required memory to store a string (instead of declaring a char array of a fixed size) −

    #include <stdio.h>#include <stdlib.h>#include <string.h>intmain(){char*name;
       name =(char*)malloc(strlen("TutorialsPoint"));strcpy(name,"TutorialsPoint");if(name  ==NULL){fprintf(stderr,"Error - unable to allocate required memory\n");}else{printf("Name = %s\n", name);}}

    Output

    When the above code is compiled and executed, it produces the following output −

    Name = TutorialsPoint
    

    The calloc() Function

    The calloc() function (stands for contiguous allocation) allocates the requested memory and returns a pointer to it.

    void*calloc(n, size);

    Here, “n” is the number of elements to be allocated and “size” is the byte-size of each element.

    The following snippet allocates the memory required to store 10 int types −

    int*ptr;
    ptr =(int*)calloc(25,sizeof(int));

    Example

    Let’s rewrite the above program using the calloc() function. All that you need to do is replace malloc with calloc −

    #include <stdio.h>#include <stdlib.h>#include <string.h>intmain(){char*name;
       name =(char*)calloc(strlen("TutorialsPoint"),sizeof(char));strcpy(name,"TutorialsPoint");if(name  ==NULL){fprintf(stderr,"Error - unable to allocate required memory\n");}else{printf("Name = %s\n", name);}}

    So you have complete control and you can pass any size value while allocating memory, unlike arrays where once the size is defined, you cannot change it.

    Resizing and Releasing the Memory

    When your program comes out, the operating system automatically releases all the memory allocated by your program. However, it is a good practice to release the allocated memory explicitly by calling the free() function, when you are not in need of using the allocated memory anymore.

    In this section, we will highlight the use of two functions, realloc() and free(), that you can use to resize and release the allocated memory.

    The realloc() Function

    The realloc() (re-allocation) function in C is used to dynamically change the memory allocation of a previously allocated memory. You can increase or decrease the size of an allocated memory block by calling the realloc() function.

    The prototype of using the realloc() function is like this −

    void*realloc(*ptr, size);

    Here, the first parameter “ptr” is the pointer to a memory block previously allocated with malloc, calloc or realloc to be reallocated. If this is NULL, a new block is allocated and a pointer to it is returned by the function.

    The second parameter “size” is the new size for the memory block, in bytes. If it is “0” and ptr points to an existing block of memory, the memory block pointed by ptr is deallocated and a NULL pointer is returned.

    Example

    The following example demonstrates how you can use the realloc() function in a C program −

    #include <stdio.h>#include <stdlib.h>#include <string.h>intmain(){char*name;
       name =(char*)calloc(strlen("TutorialsPoint"),sizeof(char));strcpy(name,"TutorialsPoint");
    
       name =(char*)realloc(name,strlen(" India Private Limited"));strcat(name," India Private Limited");if(name ==NULL){fprintf(stderr,"Error - unable to allocate required memory\n");}else{printf("Name = %s\n", name);}}

    Output

    When the above code is compiled and executed, it produces the following output −

    Name = TutorialsPoint India Private Limited
    

    The free() Function

    The free() function in C is used to dynamically de-allocate the memory allocated using functions such as malloc() and calloc(), since it is not freed on their own.

    In C programming, any reference to unused memory creates a garbage deposition, which may lead to problems like program crashing. Hence, it is wise to use the free() function to perform a manual cleaning operation of the allocated memory.

    Here is the prototype to use the free() function −

    voidfree(void*ptr);

    Where ptr is the pointer to the block of memory previously allocated.

    Example

    The following example demonstrates how you can use the free() function in a C program −

    #include <stdio.h>#include <stdlib.h>#include <string.h>intmain(){char*name;
       name =(char*)calloc(strlen("TutorialsPoint"),sizeof(char));strcpy(name,"TutorialsPoint");if(name  ==NULL){fprintf(stderr,"Error - unable to allocate required memory\n");}else{printf("Name = %s\n", name);free(name);}}

    Output

    At the end of the code, the memory allocated to the char * pointer is de-allocated.

    Name = TutorialsPoint India Private Limited
    
  • Memory Layout

    The memory layout of a C program refers to how the program’s memory is organized during its execution. Understanding the memory layout helps developers manage memory more effectively, debug programs, and avoid common memory-related errors.

    The memory is typically divided into the following distinct memory segments −

    • Text segment
    • Initialized data segment
    • Uninitialized data segment
    • Heap
    • Stack

    Efficiently managing these memory segments in RAM, which is faster but limited in capacity as compared to the secondary storage, is crucial for preventing segmentation faults and optimizing C program execution.

    The following illustration shows how the memory layout is organized, and also depicts how the RAM loads a C program into its different memory segments −

    Memory Layout in C

    Let us discuss each of these memory segments in detail.

    Text Segment

    The text segment is also known as the code segment, which generates a binary file after compiling the program. This binary file is then used to execute the program by loading it into the RAM. This binary file contains instructions that get stored in the text segment of the memory.

    • The text segment is usually read-only and stored in the lower part of the memory to prevent accidental modification of the code while the program is running.
    • The size of the text segment determines the number of instructions and the complexity of the program.

    Initialized Data Segment

    The initialized data segment is a type of data segment that stores the global and static variables created by the programmer. This segment is placed just above the text segment of the program.

    The initialized data segment contains global and static variables that have been explicitly initialized by the programmer. For example,

    // Global variableint a =10;// Static variablestaticint b =20;

    This memory segment has read-write permission because the value of a variable can change during program execution.

    Example: Initialized Data Segment

    The following C program shows how the initialized data segment works −

    #include<stdio.h>int globalVar1 =50;char* greet ="Hello World";constint globalVar2 =30;intmain(){// static variable stored in initialized data segmentstaticint n =10;// ...printf("Global variables are stored in Initialize Data Segment");return0;}

    In this code, the variables globalVar1 and the pointer greet are declared outside the scope of the main() function, and therefore they are stored in the read-write section of the initialized data segment. However, the global variable globalVar2 is declared with the keyword const, and hence it is stored in the readonly section of the initialized data segment. Static variables like a are also stored in this part of the memory.

    When you run this code, it will produce the following output −

    Global variables are stored in Initialize Data Segment
    

    Uninitialized Data Segment

    The uninitialized data segment, also known as the BSS (Block Started by Symbol) segment, is a part of a C program’s memory layout. When a program is loaded into the memory, space for the BSS segment is allocated by the operating system. Before the execution of the C program begins, the kernel automatically initializes all variables in the BSS segment: arithmetic data types are set to 0, and pointers are set to a null pointer.

    The BSS segment contains all the global and static variables that are not explicitly initialized by the programmer (or initialized with 0). Since the values of these variables can be modified during program execution, the BSS segment has read-write permission.

    Example: Uninitialized Data Segment

    Let’s understand the role of uninitialized data segment through the following C program −

    #include <stdio.h>// Uninitialized global variable stored in the bss segmentint globalVaraible;intmain(){// Uninitialized static variable stored in bssstaticint staticVariable;printf("Global Variable = %d\n", globalVaraible);printf("Static Variable = %d\n", staticVariable);return0;}

    When you run this code, it will produce the following output −

    Global Variable = 0
    Static Variable = 0
    

    In this C program, both the static and global variables are uninitialized, so they are stored in the BSS segment of the memory layout. Before the program execution begins, the kernel initializes these variables with the value 0.

    Heap Segment

    The heap area begins at the end of the BSS segment and grows upward toward higher memory addresses. It is the memory segment used for dynamic memory allocation during program execution. Whenever additional memory is required, functions like malloc() and calloc() allocate space from the heap, causing it to grow upward.

    • The heap is managed by functions such as malloc(), calloc(), and free(), which internally may use system calls like brk and sbrk to adjust its size.
    • Since the heap is a shared region, it is also used by all shared libraries and dynamically loaded modules within a process.

    Example: Heap Segment

    In this C program, we have created a variable of data type char, which allocates 1 byte of memory (the size of a char in C) at the time of program execution. Since this variable is created dynamically, it is allocated in the heap segment of the memory.

    #include <stdio.h>#include <stdlib.h>intmain(){// Allocate memory for a single charchar*var =(char*)malloc(sizeof(char));*var ='A';// Print the value and the size of the allocated memoryprintf("Value of dynamically allocated char: %c\n",*var);printf("Size of dynamically allocated char: %zu bytes\n",sizeof(*var));// Free the dynamically allocated memoryfree(var);return0;}

    Run the code and check its output −

    Value of dynamically allocated char: A
    Size of dynamically allocated char: 1 bytes
    

    Stack Segment

    The stack segment follows a LIFO (Last In, First Out) structure and usually grows downward toward lower memory addresses (though the exact behavior depends on the computer architecture). It grows in the direction opposite to the heap.

    The stack is used to manage function calls and local variables. Each time a function is called, a stack frame is created, which stores the function’s local variables, parameters, and return address. When the function finishes, its stack frame is removed, following the LIFO principle.

    Example: Stack Segment

    The following example shows how the variables are stored in the stack memory segment −

    #include <stdio.h>voiddisplay(int x){int y =20;printf("Parameter x = %d\n", x);printf("Local variable y = %d\n", y);}intmain(){int mainVar =10;// function call creates new stack framedisplay(mainVar);return0;}

    Run the code and check its output −

    Parameter x = 10
    Local variable y = 20
    

    When the main function starts, its stack frame is created storing mainVar and the return address.
    When the display function is called, a new stack frame is pushed storing x and y, and removed once the function ends (LIFO order).

    Command-line Arguments

    When a C program is executed, any command-line arguments passed to it are also stored in the memory. These arguments are placed in the special memory segment, typically above the stack in the process memory layout.

    The command-line arguments are passed to the main() function in the form of −

    intmain(int argc,char*argv[])

    Here,

    • argc (argument count): Stores the total number of argument passed, includes the program name.
    • argv (argument vector): It is an array of character pointer (strings), where each element point to a command-line arguments.

    Example: argc and argv

    Let’s understand both arguments (argc and argv) through a C program:

    #include <stdio.h>intmain(int argc,char*argv[]){printf("Total arguments: %d\n", argc);for(int i =0; i < argc; i++){printf("Argument %d: %s\n", i, argv[i]);}return0;}

    Following is the output of the above code −

    Total arguments: 1
    Argument 0: /tmp/HqDVg7xJye/main.o
    

    Example: Program to get the Size of Memory Segment

    In this example, we create a simple C program layout and use the command below to get the size of each memory segment. To run this, you need a Linux environment. On Windows, you can download and install MinGW to use GCC and related commands.

    #include<stdio.h>intmain(){return0;}

    Use the following command to get the size −

    gcc file_name.c -o file_name
    size file_name
    

    Output: Following is the size −

    ~$ gcc program.c -o program
    ~$ size program
       text    data     bss     dec     hex filename
       1418     544      8    1970     7b0 program
    

    Example: Inserting an Uninitialized Global Variable

    Inserting an uninitialized global variable increases the size of the Data segment −

    #include <stdio.h>int global;intmain(){return0;}

    Run the code and check its output −

    ~$ gcc program.c -o program
    ~$ size program
       text    data     bss     dec     hex filename
       1418     548      8    1970     7b0 program
    

    Example: Inserting an Uninitialized Static Variable

    If you insert an uninitialized static variable, it increases the occupied space in the BSS segment.

    #include <stdio.h>int globalVar =10;intmain(){staticint staticVar;return0;}

    Run the code and check its output −

    ~$ gcc program.c -o program
    ~$ size program
       text    data     bss     dec     hex filename
       1418     548      12    1970     7b0 program
    

    Example: Inserting a Static Variable with Initialized Value

    If you insert a static variable with an initialized value, it will be stored in the data segment.

    #include <stdio.h>int globalVar =10;intmain(){staticint staticVar;staticint a =5;return0;}

    Run the code and check its output −

    ~$ gcc program.c -o program
    ~$ size program
       text    data     bss     dec     hex filename
       1418     552      8    1970     7b0 program
    

    Example: Inserting an Uninitialized Global Variable

    As we saw in the above programs, if we insert a global variable without initialization, it will be stored in the BSS segment.

    #include <stdio.h>int globalVar =10;int x;intmain(){staticint staticVar;staticint a =5;return0;}

    Run the code and check its output −

    ~$ gcc program.c -o program
    ~$ size program
       text    data     bss     dec     hex filename
       1418     552      16    1970     7b0 program
    

    Conclusion

    The memory layout of a C program is divided into distinct segments: text segment, data segment, BSS, heap, and stack. Each segment has a specific role in program execution. The text segment stores code, while the data and BSS segments handle global and static variables. The heap manages dynamic memory, and the stack is used for function calls and local variables.