In embedded software development, the stack is one of the most important memory areas during program execution. Understanding the direction of stack growth not only helps us write safer code but also provides critical clues when debugging memory-related issues.
Stack Growth Direction
What is Stack Growth Direction
The stack growth direction refers to the direction in which the stack pointer moves during function calls. This determines the storage order of local variables, function parameters, and return addresses in memory.
Stack Growth Direction
Downward growth: High address → Low address
Upward growth: Low address → High address
Most processors: ARM, x86, MIPS
Few processors: Some DSPs
Stack pointer decrement: SP = SP - size
Stack pointer increment: SP = SP + size
Stack Growth Direction in Different Architectures
| Processor Architecture | Stack Growth Direction | Typical Application Scenarios |
| ARM Cortex-M | Downward growth | IoT devices, MCU |
| x86/x86-64 | Downward growth | PCs, servers |
| MIPS | Downward growth | Routers, embedded systems |
| RISC-V | Downward growth | Emerging embedded platforms |
| Some DSPs | Upward growth | Digital signal processing |
Detecting Stack Growth Direction
Theoretical Detection Methods
Address Comparison Method
#include <stdio.h>
#include <stdint.h>
typedef struct {
uintptr_t addr;
int depth;
} stack_info_t;
void detect_stack_growth_recursive(stack_info_t* info, int depth) {
int local_var = 0x12345678;
uintptr_t current_addr = (uintptr_t)&local_var;
if (depth == 0) {
info->addr = current_addr;
info->depth = depth;
printf("Initial stack address: 0x%lx\n", current_addr);
} else if (depth == 10) {
printf("Stack address at level %d: 0x%lx\n", depth, current_addr);
printf("Address difference: %ld\n", (long)(current_addr - info->addr));
if (current_addr < info->addr) {
printf("Conclusion: Stack grows downward (High address → Low address)\n");
} else {
printf("Conclusion: Stack grows upward (Low address → High address)\n");
}
return;
}
detect_stack_growth_recursive(info, depth + 1);
}
int main() {
stack_info_t info = {0};
detect_stack_growth_recursive(&info, 0);
return 0;
}
Function Call Chain Analysis
#include <stdio.h>
#include <stdint.h>
#define MAX_DEPTH 20
typedef struct {
uintptr_t addresses[MAX_DEPTH];
int count;
} call_stack_t;
void analyze_call_stack(call_stack_t* stack, int depth) {
int dummy = 0xDEADBEEF;
uintptr_t addr = (uintptr_t)&dummy;
if (depth < MAX_DEPTH) {
stack->addresses[stack->count++] = addr;
}
if (depth < 5) {
analyze_call_stack(stack, depth + 1);
} else {
printf("Call stack analysis result:\n");
for (int i = 0; i < stack->count - 1; i++) {
int64_t diff = (int64_t)(stack->addresses[i+1] - stack->addresses[i]);
printf("Level %d -> %d: Address difference = %+ld\n",
i, i+1, diff);
}
// Determine growth direction
int64_t first_diff = stack->addresses[1] - stack->addresses[0];
if (first_diff < 0) {
printf("Stack grows downward (Address decreases with each call)\n");
} else {
printf("Stack grows upward (Address increases with each call)\n");
}
}
}
Practical Detection Tools
GDB Debugger Detection
# Compile with debug information
gcc -g -O0 stack_direction.c -o stack_direction
# Use GDB to analyze stack layout
gdb stack_direction
(gdb) break main
(gdb) run
(gdb) info registers sp
(gdb) step
(gdb) info registers sp
Inline Assembly Detection
#include <stdio.h>
#include <stdint.h>
uintptr_t get_stack_pointer(void) {
uintptr_t sp;
#if defined(__arm__) || defined(__aarch64__)
asm volatile("mov %0, sp" : "=r" (sp));
#elif defined(__x86_64__)
asm volatile("mov %%rsp, %0" : "=r" (sp));
#elif defined(__i386__)
asm volatile("mov %%esp, %0" : "=r" (sp));
#else
#error "Unsupported architecture"
#endif
return sp;
}
void monitor_stack_usage(void) {
uintptr_t sp1 = get_stack_pointer();
// Allocate some local variables
char buffer[1024];
int dummy = 0x12345678;
uintptr_t sp2 = get_stack_pointer();
printf("Stack pointer change: 0x%lx -> 0x%lx\n", sp1, sp2);
printf("Change amount: %+ld bytes\n", (int64_t)(sp2 - sp1));
if (sp2 < sp1) {
printf("Stack grows downward\n");
} else {
printf("Stack grows upward\n");
}
}
Impact of Stack Growth Direction on Development
Memory Layout Optimization
Memory Layout Design
Downward growing stack
Upward growing stack
High address: Stack area address: Heap area Low address: Code/Data
Low address: Stack area address: Heap area High address: Code/Data
Stack overflow detection: Sentinel set at stack bottom
Stack overflow detection: Sentinel set at stack top
Memory protection: Prevent stack from overwriting heap
Memory protection: Prevent stack from overwriting code
Stack Overflow Detection Mechanism
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define STACK_SIZE 4096
#define GUARD_SIZE 64
#define GUARD_PATTERN 0xDEADBEEF
typedef struct {
uint32_t guard_before[GUARD_SIZE / 4];
uint8_t stack[STACK_SIZE];
uint32_t guard_after[GUARD_SIZE / 4];
} protected_stack_t;
static protected_stack_t g_stack;
// Initialize protected stack
void init_protected_stack(void) {
// Fill with sentinel pattern
for (int i = 0; i < GUARD_SIZE / 4; i++) {
g_stack.guard_before[i] = GUARD_PATTERN;
g_stack.guard_after[i] = GUARD_PATTERN;
}
printf("Protected stack initialization complete\n");
printf("Stack size: %d bytes\n", STACK_SIZE);
printf("Guard size: %d bytes\n", GUARD_SIZE);
}
// Check for stack overflow
int check_stack_overflow(void) {
for (int i = 0; i < GUARD_SIZE / 4; i++) {
if (g_stack.guard_before[i] != GUARD_PATTERN) {
printf("Stack underflow detected! Location: guard_before[%d]\n", i);
return -1;
}
if (g_stack.guard_after[i] != GUARD_PATTERN) {
printf("Stack overflow detected! Location: guard_after[%d]\n", i);
return 1;
}
}
return 0; // Normal
}
// Simulate stack overflow
void simulate_stack_overflow(void) {
char buffer[STACK_SIZE + 100]; // Intentionally exceed stack size
memset(buffer, 0xAA, sizeof(buffer));
printf("Simulated stack overflow complete\n");
if (check_stack_overflow() != 0) {
printf("Stack overflow detection successful!\n");
}
}
Recursive Depth Control
#include <stdio.h>
#include <stdint.h>
// Recursive depth control based on stack growth direction
typedef struct {
uintptr_t initial_sp;
uintptr_t current_sp;
size_t max_stack_usage;
int max_depth;
} recursion_monitor_t;
static recursion_monitor_t g_monitor = {0};
void init_recursion_monitor(void) {
#if defined(__arm__) || defined(__aarch64__)
asm volatile("mov %0, sp" : "=r" (g_monitor.initial_sp));
#elif defined(__x86_64__)
asm volatile("mov %%rsp, %0" : "=r" (g_monitor.initial_sp));
#endif
g_monitor.current_sp = g_monitor.initial_sp;
g_monitor.max_stack_usage = 0;
g_monitor.max_depth = 0;
printf("Recursion monitor initialization complete\n");
printf("Initial stack pointer: 0x%lx\n", g_monitor.initial_sp);
}
int check_recursion_depth(int current_depth) {
// Get current stack pointer
#if defined(__arm__) || defined(__aarch64__)
asm volatile("mov %0, sp" : "=r" (g_monitor.current_sp));
#elif defined(__x86_64__)
asm volatile("mov %%rsp, %0" : "=r" (g_monitor.current_sp));
#endif
// Calculate stack usage
size_t stack_usage;
if (g_monitor.current_sp < g_monitor.initial_sp) {
// Downward growth
stack_usage = g_monitor.initial_sp - g_monitor.current_sp;
} else {
// Upward growth
stack_usage = g_monitor.current_sp - g_monitor.initial_sp;
}
if (stack_usage > g_monitor.max_stack_usage) {
g_monitor.max_stack_usage = stack_usage;
g_monitor.max_depth = current_depth;
}
// Check if close to stack limit
if (stack_usage > 1024 * 1024) { // 1MB limit
printf("Warning: Stack usage too high (%zu bytes)\n", stack_usage);
return -1;
}
return 0;
}
// Safe recursive function example
int safe_factorial(int n, int depth) {
if (check_recursion_depth(depth) < 0) {
printf("Recursion depth exceeded, stopping execution\n");
return -1;
}
if (n <= 1) return 1;
return n * safe_factorial(n - 1, depth + 1);
}
———— END ————
We invite you to visit the Embedded MCU/MPU Ecosystem Zone

Implementation solutions for embedded memory monitoring systems

Do you need to learn registers for MCU development?