Scan to FollowLearn Embedded Together, learn and grow together

When it comes to global variables, the most common discussion is about reducing their usage or the various drawbacks associated with them.
Today, we will discuss a different perspective. For embedded software, the use of global variables has many advantages.
This article will explore the advantages of global variables in embedded software and provide methods to mitigate their potential risks, helping everyone find a balance between efficiency and maintainability.
Advantages of Global Variables
Efficient Access, Reduced Overhead
- Direct Memory Access: Global variables are stored at fixed memory addresses (such as
<span>.data</span>or<span>.bss</span>sections), eliminating the need for passing through function parameters, thus reducing the overhead of stack operations. - Suitable for Low-End MCUs: In 8/16-bit microcontrollers (like 8051, PIC), stack space is limited, and global variables can avoid performance losses caused by frequent stack operations.
Facilitates Data Sharing
- Cross-Module Access: Multiple functions or modules (such as driver layer, application layer) can share the same data without the need for passing parameters through multiple layers.
- Communication Between Interrupts and Main Program: In Interrupt Service Routines (ISRs), global variables are the primary means of passing data to the main program (e.g.,
<span>volatile bool g_data_ready</span>).
Controlled Memory Management
- Static Allocation, No Fragmentation Risk: Global variables are allocated fixed memory at compile time, making them more stable compared to dynamic memory allocation (
<span>malloc</span>/<span>free</span>), thus avoiding heap memory fragmentation issues. - Suitable for Real-Time Systems: In RTOS or bare-metal systems, the access time for global variables is deterministic and will not cause unpredictable delays due to dynamic allocation.
Convenient for Debugging and Monitoring
- Debugger Friendly: Using debugging tools like JTAG/SWD, global variables can be directly observed for real-time changes, facilitating fault diagnosis.
- Persistent State Management: The lifecycle of global variables spans the entire program execution period, making them suitable for storing system states (like error codes, runtime statistics).
Usage in Interrupt Contexts
- Communication Between ISR and Main Program: In ISRs, global variables are a common way to pass data to the main program (e.g.,
<span>volatile uint8_t flag</span>). - No Call Chain Dependency: ISRs typically cannot pass parameters, making global variables the only means of data sharing.
Some Applicable Scenarios
- Hardware Register Mapping (e.g.,
<span>volatile uint32_t *g_reg = (uint32_t*)0x40000000;</span>). - System Status Flags (e.g.,
<span>volatile bool g_system_ready</span>). - Time Counters (e.g.,
<span>uint32_t g_ticks</span>updated in SysTick interrupt).
Problems Caused by Global Variables
Despite the many advantages of global variables, misuse can lead to the following issues:
| Issue | Risk |
|---|---|
| High Coupling | Excessive inter-module dependency on global variables makes code difficult to reuse and maintain. |
| Naming Conflicts | Global namespace pollution may lead to variable name duplication. |
| Reentrancy Issues | In multi-tasking/interrupt environments, data may be inadvertently modified (race conditions). |
| Difficult to Track Modifications | Global variables can be modified anywhere, increasing debugging difficulty. |
How to Avoid Issues with Global Variables
Limit Scope
-
Use the static Keyword: If a global variable is only used within a single
<span>.c</span>file, it should be declared as<span>static</span>to prevent access from external files:static uint32_t s_sensor_data; // Visible only in the current file -
Modular Encapsulation: Access global variables through function interfaces instead of exposing them directly:
// module.c static int g_temperature; void set_temperature(int temp) { g_temperature = temp; } int get_temperature(void) { return g_temperature; }
Avoid Race Conditions
-
Disable Interrupt Protection (suitable for bare-metal systems):
volatile uint32_t g_counter; void ISR_Handler(void) { __disable_irq(); // Disable interrupts g_counter++; __enable_irq(); // Enable interrupts } -
Use Atomic Operations (e.g., C11
<span>stdatomic.h</span>or MCU-specific instructions):#include <stdatomic.h> atomic_int g_atomic_flag;
Use Naming Conventions
-
Prefix Identifiers: Use prefixes like
<span>g_</span>,<span>s_</span>to distinguish global variables from local variables:volatile uint32_t g_system_ticks; // Global variable static uint8_t s_debug_mode; // Static global variable
Alternative Solutions
-
Use Local Static Variables: If a variable only needs to maintain state between function calls, use
<span>static</span>local variables:int get_next_id(void) { static int s_id = 0; return s_id++; } -
Pass Struct Pointers: In RTOS, pass data through message queues or task parameters instead of relying on global variables:
// FreeRTOS example xQueueSend(data_queue, &sensor_data, portMAX_DELAY);
In Conclusion
In embedded software development, global variables are a common data storage method. Due to their direct access and efficient sharing characteristics, they are widely used in resource-constrained embedded systems.
However, if misused, global variables can lead to high code coupling, maintenance difficulties, and even serious issues like race conditions.
When used appropriately, global variables can balance performance and maintainability in embedded systems, allowing for the writing of efficient and stable code.

Follow 【Learn Embedded Together】 to become better together.。
If you find this article helpful, click “Share”, “Like”, or “Recommend”!