Embedded Development Pitfall Guide: Are You Using Global Variables Correctly?
Global variables are convenient in embedded projects but can cause memory waste, tight module coupling, hard‑to‑track bugs, and portability issues, especially in RTOS multitasking; this article explains the underlying memory layout, common traps, and practical techniques to use globals safely.
What Is a Global Variable?
In embedded C a global variable is defined outside any function, lives for the entire program lifetime, and resides in the global/static storage area. Uninitialized globals are automatically set to 0 or NULL.
#include <stdio.h>
// Define a global variable
int globalVar = 100;
void function1() {
printf("function1 accesses: %d
", globalVar);
globalVar++;
}
void function2() {
printf("function2 accesses: %d
", globalVar);
}
int main() {
printf("main accesses: %d
", globalVar);
function1();
function2();
return 0;
}All functions can read or modify globalVar. By contrast, a local variable is declared inside a function or block, lives only until the block ends, and is typically stored on the stack with an indeterminate initial value.
void function1() {
int localVar = 20;
printf("localVar: %d
", localVar);
printf("globalVar: %d
", globalVar);
globalVar++;
}Why Global Variables Feel Convenient
In a temperature‑humidity monitoring project based on an STM32 MCU, developers defined globalTemperature and globalHumidity so that sensor, display, and communication modules could read/write the same data directly:
void readSensorData() {
float temp = getTemperatureFromSensor();
float humi = getHumidityFromSensor();
globalTemperature = temp;
globalHumidity = humi;
}
void displayData() {
lcdDisplay(globalTemperature, globalHumidity);
}
void sendData() {
wirelessSend(globalTemperature, globalHumidity);
}This made early testing fast, but as new features (e.g., energy‑saving control) were added, many functions began reading and writing the same globals, making the program state hard to trace and leading to bugs such as incorrect display values.
Hidden Pitfalls of Overusing Globals
Module coupling skyrockets – multiple modules share the same variables, blurring boundaries and turning independent blocks into a tightly‑coupled monolith.
Debugging becomes a nightmare – when a value changes unexpectedly, it is difficult to pinpoint which function altered it, especially under pre‑emptive multitasking or interrupt contexts.
Memory management gets messy – globals occupy RAM for the whole runtime; excessive globals shrink the limited memory pool, can cause leaks when pointers are stored globally, and increase fragmentation.
Portability and reuse suffer – code that depends on specific hardware‑related globals is hard to move to another platform or reuse in a different project.
How to Use Globals Safely
(1) Minimize usage – Prefer local variables or function parameters whenever possible. For example, compute an average inside a function using temporaries instead of storing intermediate results globally.
(2) Limit scope with static – Declare file‑local globals as static to hide them from other translation units and avoid name clashes.
// uart.c
static int staticBaudRate = 9600;
static int staticDataBits = 8;
static int staticStopBits = 1;(3) Encapsulate access – Provide getter/setter functions so other modules must call a controlled interface.
int globalLightStatus = 0;
int getLightStatus() { return globalLightStatus; }
void setLightStatus(int s) { globalLightStatus = s; }(4) Declare immutable globals with const – Prevent accidental modification.
const float VOLTAGE_CONVERSION_FACTOR = 0.01f;(5) Synchronize in multithreaded environments – Protect shared globals with a mutex (FreeRTOS example).
#include "FreeRTOS.h"
#include "semphr.h"
int globalSharedData = 0;
SemaphoreHandle_t mutex;
void thread1(void *pv) {
while (1) {
if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) {
globalSharedData++;
xSemaphoreGive(mutex);
}
}
}
void thread2(void *pv) {
while (1) {
if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) {
globalSharedData--;
xSemaphoreGive(mutex);
}
}
}
int main() {
mutex = xSemaphoreCreateMutex();
xTaskCreate(thread1, "Thread1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);
xTaskCreate(thread2, "Thread2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);
vTaskStartScheduler();
return 0;
}(6) Organize many globals with a struct – Group related configuration variables into a single structure for clearer initialization and access.
typedef struct {
int systemMode;
int deviceAddress;
int baudRate;
int dataBits;
int stopBits;
} SystemConfig;
SystemConfig globalSystemConfig = {
.systemMode = 0,
.deviceAddress = 1,
.baudRate = 9600,
.dataBits = 8,
.stopBits = 1
};Conclusion
Global variables can simplify data sharing in small embedded projects, but overusing them leads to high coupling, hard‑to‑track bugs, memory waste, and poor portability. By limiting their number, restricting scope, encapsulating access, marking immutable data with const, and applying proper synchronization, developers can keep embedded code clean, maintainable, and scalable.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
