About Program Memory: A Few Insights

Foreword: Recently, I have been studying pointers, which has led me to have a deeper interaction with memory. I realized that I had a blank slate regarding this area. Thus, I took the opportunity to learn a bit and wanted to share my reflections.


Introduction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main()
{
char str[] = {"asdasd"};
char *p = (char *)"asdasd";
*(p + 2) = "e"; // 1
str[2] = "e"; // 2
while (*p)
{
putchar(*p);
p++;
}
}

The running result indicates: line 1 will cause an error, but line 2 will not.

Consider why this is the case?

Hint: They are stored in different areas, which means they are in different memory segments!

I hope this article can help you find the answer.

What is Memory Segmentation?

Here, we will discuss memory segmentation in the context of C language.

The diagram below illustrates the memory segmentation in C:

Stack Segment

Overview of the Stack Segment

  • The stack segment is automatically allocated and freed by the compiler, and it is managed by the operating system, requiring no manual management.
  • The content in the stack segment only exists within the scope of the function. Once the function ends, this content will also be automatically destroyed.
  • The stack segment grows downwards in memory address from high to low, with its maximum size determined at compile time. It is fast but lacks flexibility and has limited space.
  • The stack segment operates on a Last In, First Out (LIFO) principle, meaning the last item added is the first to be removed.

Content Stored

  • Temporarily created local variables and local variables defined with const are stored in the stack segment.
  • When functions are called and return, their input parameters and return values are also stored in the stack segment.

Heap Segment

Overview of the Heap Segment

  • The heap segment is allocated and freed by the programmer.
  • The heap segment grows upwards in memory address from low to high, with its size determined by system memory or virtual memory limits. It is slower but more flexible, allowing for larger available space.

Function Calls

  • Memory allocation in the heap is accomplished using functions like malloc.
1
void *malloc(size_t);

Parameter size_t specifies the number of bytes to allocate.
Return value is a pointer of type void*, which points to the beginning address of the allocated space.
(The void* pointer can be cast to any other type of pointer.)

  • Use the free function for memory deallocation, or it will cause memory leaks.
1
void free(void * /*ptr*/);

Parameter is the starting address of the allocated memory.

Global Static Segment

Overview of the Global Static Segment

  • This segment is typically used for variables whose storage size can be determined during compilation. It is used for global variables and static variables that are visible throughout the entire program’s runtime.
  • The global segment consists of the .bss and .data segments, which are readable and writable.
  • The memory space is managed by the system: it is allocated when the program starts and reclaimed when the program ends, and it exists throughout the program’s execution!

.bss Segment

  • Uninitialized global variables and uninitialized static variables are stored in the .bss segment.
  • Global variables initialized to 0 and static variables initialized to 0 are also stored in the .bss segment.
  • The .bss segment does not occupy space in the executable file; its content is initialized by the operating system.

.data Segment

  • Initialized global variables are stored in the .data segment.
  • Initialized static variables are stored in the .data segment.
  • The .data segment occupies space in the executable file and is initialized by the program.

Supplement on static

  1. The first and most important point is hiding:

    • When compiling multiple files simultaneously, all global variables and functions without the static prefix have global visibility.
    • The static prefix can be applied to both functions and variables, limiting the function’s scope to the file it is defined in.
    • This feature allows defining functions and variables with the same names in different files without worrying about naming conflicts.
  2. The second function is to maintain the variable’s content persistently:

    • Since static variables are stored in the global static segment, they persist throughout the program’s execution and are not released when the function ends.
    • The static keyword controls the variable’s scope, but ultimately, it is used for hiding.
    • This feature allows defining functions and variables with the same names in different functions without worrying about naming conflicts.
  3. The third function is default initialization to 0:

    • Global variables also have this property since they are stored in the static data segment.
    • In the static data segment, all memory bytes are initialized to 0x00, which can sometimes reduce the programmer’s workload.

Constant Segment

  • Strings, numbers, and other constants are stored in the constant segment.
    • Initialized global variables.
    • Initialized static variables.
  • Global variables modified by const are stored in the constant segment.
  • The content of the constant segment cannot be modified during the program’s execution.

Code Segment

  • The executable code of the program is stored in the code segment, and its content cannot be modified (modifying it will result in an error).
  • String constants and constants defined by #define may also be stored in the code segment.

Supplementary Concepts

  • Memory Leak: Refers to allocated memory space that has not been reclaimed after use.
    While one instance of memory leak can be ignored, continual leakage, regardless of how much memory is available, will eventually occupy all memory and lead to the program crashing. (Thus, we should strive to avoid memory leaks in development.)
  • Out of Memory: Refers to a situation where a program cannot find sufficient memory space for its use when requesting memory.
    In simpler terms, it means running out of memory. This often occurs when running large applications or games, where the memory needed far exceeds what is installed on the machine, leading to a restart or program crash.

Returning to the Introduction

When we define an array, it is actually allocated in the global static segment or stack segment.

When we define a string pointer, it is actually allocated in the constant segment.

Thus, the final result is that the second type is non-modifiable!