Common Mistakes in Embedded C Development - Pointers Done Wrong

Table of contents

Article photo by Jorge Ramirez on Unsplash
📘 Introduction
This is Part 2 of our 4-part series on memory mistakes in Embedded C. In Part 1, we discussed stack overflows and how modern C practices can prevent them.
Today, we’ll take on unsafe pointer usage, one of the most common (and dangerous) issues in low-level C, especially in the context of hardware drivers, buffers, and data parsing in embedded systems.
🧠 Unsafe Use of Pointers
🐞 The Problem
Pointers are one of C’s most powerful features, and also one of its most dangerous. Beginners often use them without fully understanding ownership, memory layout, or lifetime. This leads to:
Dereferencing null or invalid pointers
Using freed or uninitialized memory
Memory leaks or crashes
Data corruption from unexpected aliasing
Let’s walk through a realistic example from an embedded IoT application. Suppose you’re reading sensor data and preparing it for a cloud payload handler:
void prepareData(float* temperature) {
*temperature = readSensor(); // OK if 'temperature' is valid
}
void logAndSend() {
float* temp = NULL;
prepareData(temp); // ⛔️ temp is null → crash!
sendToCloud(temp);
}
This code compiles fine, but dereferencing a null pointer inside prepareData() will crash the device at runtime.
Or another classic mistake is returning a pointer to a local stack variable:
char* getDeviceID() {
char id[32]; // ⛔️ local variable
snprintf(id, sizeof(id), "dev-%04X", getChipID());
return id; // INVALID after function returns
}
✅ Solution 1: Use Proper Ownership – Allocate Before Passing
Always make sure that pointers point to valid memory before you pass them to functions.
void prepareData(float* temperature) {
*temperature = readSensor();
}
void logAndSend() {
float temp = 0.0f;
prepareData(&temp); // ✅ address of valid memory
sendToCloud(&temp);
}
✅ Solution 2: Avoid Returning Pointers to Local Variables
Return static memory, or require the caller to provide a buffer:
void getDeviceID(char* out, size_t size) {
snprintf(out, size, "dev-%04X", getChipID());
}
✅ Solution 3: Mark Pointer Ownership with const
If a function only reads from a pointer, mark it as const. This helps:
Prevent bugs from accidental modification
Signal to other developers that ownership doesn’t transfer
void sendToCloud(const float* value) {
mqttSend("sensor/temp", valueToStr(*value));
}
✅ Solution 4: Use Static Analysis and Compiler Warnings
Let your tools catch pointer issues early.
Use strict compiler warnings for C:
gcc -Wall -Wextra -Werror -O2 -std=c11 main.c
With the below code
#include <stdio.h>
int main() {
int *ptr; // Uninitialized pointer
printf("%d\n", *ptr); // 🚨 Undefined behavior
return 0;
}
You will get the following output:
main.c:5:21: error: variable 'ptr' is uninitialized when used here [-Werror,-Wuninitialized]
5 | printf("%d\n", *ptr); // 🚨 Undefined behavior
| ^~~
main.c:4:13: note: initialize the variable 'ptr' to silence this warning
4 | int *ptr; // Uninitialized pointer
| ^
| = NULL
1 error generated.
Use static analysers for deeper inspection:
Coverity (great for CI pipelines)
💡 Takeaway
Pointer safety in embedded C isn’t optional; it’s critical. You don’t get exceptions, memory protection, or guard rails like in modern languages.
To stay safe:
Don’t dereference null or invalid pointers
Don’t return pointers to local variables
Always check pointer inputs (defensive programming)
Use const to clarify ownership
Turn on all warnings and use static analysis tools
At Itransition, we build IoT solutions with all these challenges in mind, ensuring our clients receive reliable, scalable systems with minimal maintenance overhead. Learn more about our approach at https://www.itransition.com/iot.
Subscribe to my newsletter
Read articles from Ilya Katlinski directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
