The C language allows program variables to have their addresses determined at the time of definition. Through scope and keywords such as extern, static, register, and volatile, it achieves fine management of data; this is one of the foundations for efficient data management in C.
Storage Methods of Variables
In C, the storage methods of variables are as follows, depending on the allocation method.
- Allocated from the static storage area. Memory is allocated at compile time and exists throughout the program’s execution, mainly including global variables and static declarations of global or local variables.
- Created on the stack. During function execution, non-static variables are mostly created on the stack (they may also be allocated to registers, which will be explained later). These storage units are automatically released when the function execution ends. Stack memory allocation operations are built into the processor’s instruction set, making it very efficient, but the allocated memory capacity is limited.
- Allocated from the heap, also known as dynamic memory allocation. During program execution, memory can be requested using functions like malloc/realloc/calloc, and the programmer is responsible for freeing it with free when done. The lifetime of dynamic memory is determined by the programmer, making it very flexible; however, it also encounters the most issues, such as memory leaks, buffer overflows, and dangling pointers. Additionally, even if these issues are avoided, in microcontroller environments, due to limited memory, fragmentation can lead to processing failures, so caution is advised.
Of course, there are also some variables that do not belong to the above areas, such as variables declared with const, which are stored in read-only memory and are often allocated to the FLASH area in microcontrollers.
Here is an example from C language to illustrate this, as shown below.

The scope of C language not only describes the area where identifiers are accessible but also specifies the storage area of variables.
- Variables ex_val and st_val in the global scope (outside functions) are allocated to the static storage area, where the static keyword mainly restricts whether the variable can be accessed by other files.
- Variables local_val, static_local_val, and p in the block scope are allocated to different areas based on their types. local_val is a local variable allocated on the stack; while static_local_val is allocated to the static storage area, where the static keyword indicates that the variable is static and can only be initialized once in the function, retaining its value after exiting the function. p is a pointer to memory allocated by malloc, allocated on the heap, and needs to be freed after use to avoid memory leaks.
Regarding variable initialization, global and static variables are generally initialized to 0 by the compiler at compile time. Local variables and dynamically allocated memory are assigned to previously used stack and heap addresses, with internal values being indeterminate. If not initialized and directly used as a condition without being set, it can lead to sporadic errors.
The issue of variable initialization involves software platforms and compiler default handling, which may vary across different environments and compiler options; therefore, it is not recommended to handle it based on platform or compiler experience, but rather to initialize variables upon declaration to avoid such issues.
volatile and register Keywords
The C language provides several keywords to control the storage methods of variables. This mainly includes register, volatile, etc. For local variables in functions, if not explicitly specified, they may be optimized to register access instead of being stored on the stack. This depends on the variable’s size and the compiler’s optimization strategy.
- If a variable occupies a larger space than the number of internal registers, it can only be allocated on the stack.
- Short local variables (like int, char, etc.) are generally optimized to register access only at optimization levels of -O2 and above.
For local variables, optimizing to register access is significantly faster than stack variables. This is because stack variables require memory access, involving cache mechanisms and memory addressing; while registers in the kernel can be accessed directly, making access much faster. However, optimization has its downsides; during debugging, local variables optimized to register access may not be directly viewable through the debugging window.
Regarding the addresses of variables, this involves more precise control over variable storage, including the register and volatile keywords.
The register keyword can request the compiler to allocate local variables to registers, thereby increasing access speed; however, it cannot guarantee that the variable will be allocated to a register, as the number of internal registers is limited (for example, in the Cortex-M architecture, the internal numeric registers correspond to r0-r12). For larger structure variables, arrays, etc., the compiler will still choose stack storage.
The volatile keyword does the opposite, requiring the compiler to access the actual memory address and not optimize to internal register access; here, the registers refer to data registers like r0-r12 in the Cortex-M architecture, not functional registers like UART configuration registers.
The volatile keyword can be used in the following scenarios.
- Hardware registers of parallel devices (e.g., module function registers).
- Non-automatic variables accessed in an interrupt service routine.
- Variables shared by several tasks in multi-threaded applications.
Volatile only guarantees that the latest value of the variable is accessed each time, preventing optimization of address access; it does not guarantee atomicity, and in multi-threaded environments, it cannot replace locking mechanisms. For environments requiring synchronization, the C11 _Atomic keyword can be used to achieve atomic operations. Mastery of volatile usage is crucial in embedded systems and is one of the basic requirements for embedded C practitioners.
Examples of volatile, register, and __Atomic keywords are shown below.
