Fundamentals 10 min read

Understanding C Memory Layout on STM32: Stack, Heap, and Data Segments Explained

This article explains C language memory partitioning—including stack, heap, global/static, constant, and code sections—illustrates their characteristics and growth directions, then applies the concepts to STM32F103 by showing memory map details and providing a complete Keil‑based C program that prints the addresses of variables in each region.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Understanding C Memory Layout on STM32: Stack, Heap, and Data Segments Explained

Memory Layout of C Programs on STM32F103

This document describes the five logical memory regions defined by the C language, how they are implemented on an STM32F103 microcontroller, and provides a Keil‑compatible example that prints the runtime addresses of representative objects from each region.

1. Stack Region

The stack is allocated and reclaimed automatically by the compiler and the runtime; no explicit free is required.

Objects placed on the stack exist only for the duration of the function call that created them.

On most ARM Cortex‑M devices the stack grows from higher to lower addresses. Its maximum size is fixed at link time (defined in the linker script or project settings).

Access follows a Last‑In‑First‑Out (LIFO) discipline.

2. Heap Region

The heap is managed manually by the application using the standard C allocation functions malloc and free.

Heap memory grows from lower to higher addresses and is limited by the total RAM size configured for the target. void *malloc(size_t size) returns a pointer to a block of at least size bytes, or NULL on failure.

Every successful malloc must be paired with a corresponding free to avoid memory leaks.

3. Global / Static Region

Variables whose storage size is known at compile time and that must persist for the entire program execution are placed here. The region is split into two sub‑segments:

.bss : uninitialized globals and static variables (including those explicitly initialized to zero). The linker does not allocate space for .bss in the executable; the loader zero‑initializes it at startup.

.data : initialized globals and static variables. The initial values are stored in the executable image and copied to RAM during startup.

4. Constant Region

String literals, numeric literals and const ‑qualified global variables reside in a read‑only area.

These objects cannot be modified at runtime; attempts to write to them typically cause a fault.

5. Code Region

Executable instructions are stored in a read‑only region. On Cortex‑M devices this region is part of the flash (ROM) memory.

Some compilers also place immutable string literals and #define constants in the same area.

6. STM32F103 Memory Map (Keil V5 default configuration)

ROM (Flash) – base address 0x08000000, size 0x10000 (64 KB). Contains the code region and the constant region.

RAM – base address 0x20000000, size 0x5000 (20 KB). Holds the global/static region, the heap, and the stack.

Memory layout diagram
Memory layout diagram
STM32F103 memory map
STM32F103 memory map

7. Verification Program

The following Keil‑compatible C program declares variables in each memory region, allocates two heap blocks, and prints the addresses of all objects. The output lets developers confirm the actual placement of each object on the target device.

#include <stdio.h>   /* printf */
#include <stdlib.h>  /* malloc, free */
#include <string.h>  /* optional string utilities */
#include "main.h"
#include "delay.h"
#include "uart3.h"
#include "led.h"

/* Global / static variables */
int q1;                     /* .bss – uninitialized global */
static int q2;              /* .bss – uninitialized static */
const int q3;               /* .bss – uninitialized const (treated as read‑only) */
int m1 = 1;                 /* .data – initialized global */
static int m2 = 2;          /* .data – initialized static */
const int m3 = 3;           /* .rodata – initialized const */

int main(void) {
    SystemCoreClockUpdate();
    LED_GPIO_Config();
    Uart3_init();

    while (1) {
        /* ---- Stack variables ---- */
        int mq1;                     /* uninitialized local */
        int *mq2;                    /* uninitialized local pointer */
        int mq3 = 3;                 /* initialized local */
        char qq[10] = "hello";      /* initialized local array */
        const int mq4;               /* uninitialized local const */
        const int mq5 = 3;           /* initialized local const */

        /* ---- Heap allocation ---- */
        int *p1 = (int *)malloc(4);
        int *p2 = (int *)malloc(4);

        /* ---- Static locals (still in .bss/.data) ---- */
        static int mp1;              /* uninitialized static local */
        static int mp2 = 2;          /* initialized static local */

        /* ---- Constant region pointers (string literals) ---- */
        const char *vv = "I LOVE YOU";   /* resides in .rodata */
        const char *mq = "5201314";       /* resides in .rodata */

        /* ---- Print addresses ---- */
        printf("
--- Stack addresses ---
");
        printf("mq1  (uninit)          : %p
", (void *)&mq1);
        printf("mq2  (uninit ptr)      : %p
", (void *)&mq2);
        printf("mq3  (init)            : %p
", (void *)&mq3);
        printf("qq   (init array)      : %p
", (void *)qq);
        printf("mq4  (uninit const)    : %p
", (void *)&mq4);
        printf("mq5  (init const)      : %p
", (void *)&mq5);

        printf("
--- Heap addresses ---
");
        printf("p1 (int*)             : %p
", (void *)p1);
        printf("p2 (int*)             : %p
", (void *)p2);

        printf("
--- Global / static addresses ---
");
        printf("q1  (uninit)          : %p
", (void *)&q1);
        printf("q2  (uninit static)   : %p
", (void *)&q2);
        printf("q3  (uninit const)    : %p
", (void *)&q3);
        printf("m1  (init)            : %p
", (void *)&m1);
        printf("m2  (init static)     : %p
", (void *)&m2);
        printf("mp1 (uninit static)   : %p
", (void *)&mp1);
        printf("mp2 (init static)     : %p
", (void *)&mp2);

        printf("
--- Constant region addresses ---
");
        printf("m3  (init const)       : %p
", (void *)&m3);
        printf("vv  (string literal)   : %p
", (void *)vv);
        printf("mq  (string literal)   : %p
", (void *)mq);

        printf("
--- Code region address ---
");
        printf("main function entry    : %p
", (void *)main);

        led485_flicker();
        delay_ms(1000);
        free(p1);
        free(p2);
    }
    return 0;
}

When the program runs on the STM32F103, the printed addresses reveal the actual placement of each variable relative to the ROM and RAM base addresses, confirming the theoretical memory partition described above.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CStackmemory layoutembeddedHeapSTM32Data Segment
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.