Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Variable Declaration in Embedded C: Definition, Initialization and Garbage Values
What a variable is, the difference between declaration and definition, how to initialize variables and arrays, why local variables contain garbage while globals start at zero, and how the start-up code prepares SRAM.
What You Will Learn
Key Terms
1. What Is a Variable?
A variable is a named location in memory that holds a value of a particular type. When you write uint8_t temperature = 25; the compiler reserves one byte of SRAM, remembers that the name temperature refers to it, and arranges for the value 25 to be stored there. Everything about variable declaration in embedded C follows from six properties that every variable has:
| Property | Question it answers | Set by |
|---|---|---|
| Name | What do I call it? | The identifier you choose |
| Type | How many bytes, signed or not, integer or float? | The type in the declaration |
| Value | What is stored now? | Initialization and assignments |
| Address | Where in memory is it? | The linker and compiler (SRAM, stack, or flash for const) |
| Scope | Where in the source can I use the name? | Where the declaration is placed (Lesson 4.2) |
| Lifetime | When does the storage exist? | Storage class: auto, static, extern (Lesson 4.3) |
2. Rules for Naming Variables (Identifiers)
Good variable declaration in embedded C starts with a good name. The name of a variable is an identifier. C has firm rules, and embedded teams add conventions on top of them.
| Rule | Valid | Invalid |
|---|---|---|
| Letters, digits and underscore only | adc_value, temp2 | adc-value, temp$, my var |
| Must not start with a digit | _flag, value1 | 1value |
| Case-sensitive | Count and count are different variables | – |
| Must not be a keyword | count | int, if, return, volatile |
| Avoid reserved patterns | tx_len | __tx, _Tx (reserved for the implementation) |
| At least 31 characters are significant | uart_rx_buffer_overflow_count | – |
Naming conventions used in firmware
| Convention | Example | Meaning |
|---|---|---|
| Lower-case with underscores | motor_speed_rpm | Ordinary variables |
| Unit as suffix | timeout_ms, vbat_mv, temp_c_x10 | Prevents unit mistakes |
g_ prefix | g_system_ticks | Global variable: shows widest scope |
s_ prefix | s_rx_index | File-local (static) variable |
| UPPER_CASE | MAX_RETRIES | Constants and macros |
p_ prefix or _ptr suffix | p_buffer | Pointer |
b / is_ prefix | is_ready | Boolean flag |
uint16_t t; forces every reader to guess. uint16_t sensor_poll_period_ms; documents itself. Good names replace many comments.3. Declaration Versus Definition
Variable declaration in embedded C involves two different actions, and these two words are often confused, yet the difference is essential as soon as a project has more than one .c file.
- Tells the compiler the name and type exist
- Reserves no memory
- May appear many times
- Lives in headers (
.h)
- Creates the variable and allocates memory
- Must appear exactly once in the whole program
- May include an initial value
- Lives in one source file (
.c)
| Statement | Declaration? | Definition? | Memory allocated? |
|---|---|---|---|
extern int x; | Yes | No | No |
int x; (at file or block scope) | Yes | Yes | Yes |
int x = 5; | Yes | Yes | Yes |
extern int x = 5; | Yes | Yes (with a warning) | Yes |
int add(int, int); (prototype) | Yes | No | No |
int add(int a, int b) { … } | Yes | Yes | Code placed in flash |
The classic multi-file structure puts the definition in one .c file and the declaration in a header that every other file includes. The compiler then checks that all files agree on the type, and the linker connects them to the single storage location:
.c that included it created its own copy. “undefined reference to g_tick_count”: you declared it with extern but never defined it anywhere.4. Initialization: Giving a Variable Its First Value
Every variable declaration in embedded C should also say what the first value is. Initialization means giving a variable a value at the moment it is defined. It is different from assignment, which changes the value later. For example int x = 10; initializes; x = 20; assigns.
| Form | Example | Result |
|---|---|---|
| Single | int x = 10; | x = 10 |
| Several in one statement | int a = 1, b, c = 3; | a = 1, b uninitialized, c = 3 |
| Array, full list | uint8_t a[3] = {1, 2, 3}; | 1, 2, 3 |
| Array, partial list | uint8_t a[5] = {1, 2}; | 1, 2, 0, 0, 0 (remaining are zero) |
| Zero everything | uint8_t buf[64] = {0}; | All 64 elements are 0 |
| Size from list | int p[] = {10, 20, 30}; | Array of 3 elements |
| String | char s[] = "ARM"; | ‘A’, ‘R’, ‘M’, 0 |
| Designated (C99) | int a[5] = {[2] = 7, [4] = 9}; | 0, 0, 7, 0, 9 |
| Structure | Point p = {.x = 3, .y = 4}; | Members set by name, others zero |
int a = 1, b, c = 3; initializes a and c only. b holds garbage. Many coding standards (MISRA, CERT) therefore require one variable per declaration.5. Uninitialized Variables and Garbage Values
A variable declaration in embedded C that omits the initial value has consequences. A local variable that you define without an initial value is not set to zero. Its storage is part of the stack, which still contains whatever earlier function calls left behind. Reading it yields an indeterminate value, commonly called a garbage value, and in C reading it is undefined behaviour.
The output is not reproducible: it depends on what ran before, on compiler flags and on the platform. On a microcontroller the stack is not cleared at reset, so garbage values come from earlier code or from random power-up contents of SRAM. Bugs caused by them are among the hardest to find because they may appear only occasionally.
uint8_t retries; if (retries > 3) { … } The test depends on garbage, so the failure handling runs randomly. Initialize: uint8_t retries = 0;-Wall -Wextra -Wuninitialized and optimisation enabled (-O1 or higher); GCC only detects many uninitialized uses when it can analyse the data flow.6. Global and Static Variables Are Automatically Zero
The C standard guarantees that objects with static storage duration (globals and variables declared static) are initialized to zero if you give no initializer: integers become 0, floats 0.0 and pointers NULL. Local (automatic) variables get no such treatment.
| Variable kind | Without initializer | Where it lives |
|---|---|---|
Global (int g;) | 0 (guaranteed) | .bss in SRAM |
static local or file-scope | 0 (guaranteed) | .bss in SRAM |
Local automatic (int x;) | garbage | Stack in SRAM, or a CPU register |
Global with value (int g = 5;) | 5 | .data in SRAM; initial copy in flash |
main() explicitly clears the .bss region and copies the initial values of .data from flash into SRAM (next section). If a project uses a custom start-up file or skips it, globals will not be zero.7. How the Startup Code Initializes Variables on the STM32F407
To understand variable declaration in embedded C fully you must know what happens before main(). When the Cortex-M4 comes out of reset it does not jump straight to main(). It first runs Reset_Handler from the start-up file (startup_stm32f407xx.s), which prepares the variables:
This is why the order of the diagram matters: a global with an initial value costs both flash (the initial copy) and SRAM (the working copy), while a zero-initialized global costs SRAM only. Lesson 4.3 and Module 11 explore these sections in detail.
8. Constants and Initialization Rules for Global Variables
For variables with static storage duration the initializer must be a constant expression, known at compile time, because the value is placed into the program image. For local variables the initializer can be any expression, evaluated each time the block is entered.
| Declaration | Legal? | Reason |
|---|---|---|
int g = 3 * 8; (global) | Yes | Constant expression |
int g = sizeof(int) * 4; (global) | Yes | sizeof is a constant |
int a = 5; int b = a; (both global) | No | a is a variable, not a constant expression in C |
int f = read_adc(); (global) | No | A function call cannot run before main() |
int local = read_adc(); (inside a function) | Yes | Evaluated at run time on entry |
const int LIMIT = 100; (global) | Yes | Placed in flash (Module 13) |
Note that const only promises that your code will not modify the variable; the initialization rules above still apply. You will meet const in depth in Module 13.
9. Common Mistakes With Variable Declaration and Initialization
int sum; for (…) sum += x[i]; starts from garbage. Write int sum = 0;.int g_flag; in config.h creates one copy per source file and leads to multiple-definition errors. Declare it with extern in the header, define it once in a .c file.uint8_t buf[32]; inside a function contains garbage. Use = {0} or memset.extern int x; in one file and uint8_t x; in another compiles in each file but corrupts memory at run time. Always include the same header in the defining file.int a, b, c = 0; initializes only c.10. Best Practices for Variable Declaration in Embedded C
- Initialize every variable where you define it; declare it as late as possible, in the smallest scope.
- One variable per declaration; use fixed-width types.
- Put
externdeclarations in headers and definitions in exactly one.cfile; include the header in the defining file so the compiler checks consistency. - Use descriptive names with units; follow one prefix convention for globals and statics.
- Never rely on garbage; do not rely on zero either if the code might be reused without the standard start-up file: initialize explicitly when it matters.
- Prefer fewer globals: each one is shared state that any function (and any interrupt) can change.
- Enable
-Wall -Wextra -Wshadow -Wuninitializedand treat warnings as errors in firmware builds. - Group related variables into a
structand initialize with designated initializers.
Key Takeaways — Lesson 4.1
- A variable has a name, type, value, address, scope and lifetime.
- Identifiers use letters, digits and underscores, cannot start with a digit and are case-sensitive; use descriptive names with units.
- A declaration announces a name and type (
extern int x;); a definition allocates memory (int x;) and must exist exactly once. - Initialize at definition:
int x = 10;. Partial array initializers zero the remaining elements. - Uninitialized locals contain garbage; reading them is undefined behaviour.
- Globals and statics are zero by default because the start-up code clears
.bss; initialized globals are copied from flash to.data. - Initializers for globals must be constant expressions.
- Good variable declaration in embedded C: initialize everything, minimise scope, one definition, many declarations.
Frequently Asked Questions
Ready to practise?
Next: declaration versus definition across files, a garbage-value demonstration, global zero-initialization and addresses with the & operator.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments