Skip to content

Commit cd6cf15

Browse files
Fine tune topic content
1 parent e5e62fc commit cd6cf15

File tree

4 files changed

+2982
-218
lines changed

4 files changed

+2982
-218
lines changed

Embedded_C/C_Language_Fundamentals.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,22 @@ C Memory Layout:
165165

166166
### **Compilation Process**
167167

168+
C on embedded targets compiles into multiple object files (translation units) that the linker places into memory regions defined by a linker script. Understanding this flow helps you read the map file and control where data/code lands.
169+
170+
### Concept: Translation units, linkage, and the linker script
171+
- A source + its headers → a translation unit → one object file.
172+
- The linker merges objects and libraries, resolving external symbols.
173+
- The linker script maps sections (\`.text\`, \`.rodata\`, \`.data\`, \`.bss\`) into Flash/RAM.
174+
175+
### Try it
176+
1. Build with `-Wl,-Map=out.map` and open the map file. Locate a `static const` table vs a non-const global.
177+
2. Change a symbol from `static` to non-`static` and observe its visibility (external vs internal linkage).
178+
179+
### Takeaways
180+
- The map file is your truth for footprint and placement.
181+
- `static` at file scope gives internal linkage; keep symbols local by default.
182+
- Use sections/attributes only when you must control placement; prefer defaults first.
183+
168184
C programs go through several stages before execution:
169185

170186
```
@@ -200,6 +216,39 @@ C uses a **static type system** with **weak typing**:
200216

201217
## 🔢 **Variables and Data Types**
202218

219+
### Concept: Where does the object live and when does it die?
220+
221+
Rather than memorizing types, think in terms of storage duration and lifetime: who owns the object, where is it placed in memory, and when is it initialized/destroyed. On MCUs, these choices affect RAM usage, startup cost, determinism, and safety.
222+
223+
### Why it matters in embedded
224+
- Static objects may be zero‑initialized by the startup code and can live in Flash (if `const`), reducing RAM.
225+
- Automatic (stack) objects are fast and deterministic to allocate but are uninitialized by default.
226+
- Dynamic (heap) objects increase flexibility but can hurt predictability and fragment memory.
227+
228+
### Minimal example
229+
```c
230+
int g1; // Zero-initialized (\`.bss\`)
231+
static int g2 = 42; // Pre-initialized (\`.data\`)
232+
233+
void f(void) {
234+
int a; // Uninitialized (stack, indeterminate)
235+
static int b; // Zero-initialized, retains value across calls
236+
static const int lut[] = {1,2,3}; // Often placed in Flash/ROM
237+
(void)a; (void)b; (void)lut;
238+
}
239+
```
240+
241+
### Try it
242+
1. Print addresses of `g1`, `g2`, a local variable, and `b`. Inspect the linker map to see section placement (\`.text\`, \`.data\`, \`.bss\`, stack).
243+
2. Make `lut` large and observe Flash vs RAM usage in the map file when `const` is present vs removed.
244+
245+
### Takeaways
246+
- Only static storage duration is guaranteed zero‑init. Stack locals are indeterminate until assigned.
247+
- `static` inside a function behaves like a global with function scope.
248+
- `const` data may reside in non‑volatile memory; don’t cast away `const` to write to it.
249+
250+
> Platform note: On Cortex‑M, large zero‑initialized objects increase \`.bss\` and extend startup clear time; large initialized objects increase \`.data\` copy time from Flash to RAM.
251+
203252
### **What are Variables?**
204253
205254
Variables are named storage locations in memory that can hold data. In C, variables must be declared before use, specifying their type and optionally initializing them with a value.
@@ -300,6 +349,39 @@ typedef enum {
300349

301350
## 🔧 **Functions**
302351

352+
### Concept: Keep work small, pure when possible, and observable
353+
354+
In embedded, function design drives predictability and testability. Prefer small, single-purpose functions with explicit inputs/outputs. Avoid hidden dependencies (globals) except for well-defined hardware interfaces behind an abstraction.
355+
356+
### Why it matters in embedded
357+
- Smaller functions improve stack usage estimation and inlining opportunities.
358+
- Pure functions are easier to unit test off-target.
359+
- Explicit interfaces reduce coupling to hardware and timing.
360+
361+
### Minimal example: refactor side effects
362+
```c
363+
// Before: mixes IO, computation, and policy
364+
void control_loop(void) {
365+
int raw = adc_read();
366+
float temp = convert_to_celsius(raw);
367+
if (temp > 30.0f) fan_on(); else fan_off();
368+
}
369+
370+
// After: separate IO from policy
371+
float read_temperature_c(void) { return convert_to_celsius(adc_read()); }
372+
bool fan_required(float temp_c) { return temp_c > 30.0f; }
373+
void apply_fan(bool on) { if (on) fan_on(); else fan_off(); }
374+
```
375+
376+
### Try it
377+
1. Write a unit test for `fan_required` off-target (no hardware) to validate thresholds and hysteresis.
378+
2. Inspect call sites to ensure high-frequency paths remain small enough to inline.
379+
380+
### Takeaways
381+
- Separate policy from mechanism for testability and reuse.
382+
- Minimize global state; pass data via parameters and return values.
383+
- Consider `static inline` for very small helpers on hot paths.
384+
303385
### **What are Functions?**
304386
305387
Functions are reusable blocks of code that perform specific tasks. They are the primary mechanism for code organization and reuse in C programming.
@@ -383,6 +465,37 @@ bool validate_sensor_data(uint16_t value, uint16_t min, uint16_t max) {
383465
384466
## 🔄 **Control Structures**
385467
468+
### Concept: Prefer early returns and shallow nesting
469+
470+
Deeply nested branches increase cyclomatic complexity and code size on MCUs. Early returns with guard clauses keep critical paths obvious and reduce stack pressure in error paths.
471+
472+
### Minimal example
473+
```c
474+
// Nested
475+
bool handle_packet(const pkt_t* p) {
476+
if (p) {
477+
if (valid_crc(p)) {
478+
if (!seq_replay(p)) { process(p); return true; }
479+
}
480+
}
481+
return false;
482+
}
483+
484+
// Guarded
485+
bool handle_packet(const pkt_t* p) {
486+
if (!p) return false;
487+
if (!valid_crc(p)) return false;
488+
if (seq_replay(p)) return false;
489+
process(p);
490+
return true;
491+
}
492+
```
493+
494+
### Takeaways
495+
- Shallow nesting improves readability and timing analysis.
496+
- Use switch for dense dispatch; avoid fall-through unless deliberate and documented.
497+
- In ISR-adjacent code, keep branches short and avoid loops without clear bounds.
498+
386499
### **What are Control Structures?**
387500

388501
Control structures determine the flow of program execution. They allow programs to make decisions, repeat operations, and organize code execution.
@@ -646,6 +759,42 @@ process_data(42, data_handler);
646759

647760
## 📊 **Arrays and Strings**
648761

762+
### Mental model: Arrays are blocks; pointers are addresses with intent
763+
764+
An array name in an expression decays to a pointer to its first element. The array itself has a fixed size and lives where it was defined (stack, \`.bss\`, \`.data\`). A pointer is just an address that can point anywhere and can be reseated.
765+
766+
### Why it matters in embedded
767+
- Knowing when decay happens prevents bugs with `sizeof` and parameter passing.
768+
- Arrays placed in Flash as `static const` look like ordinary arrays but are read‑only and may require `volatile` when mapped to hardware.
769+
770+
### Minimal example: decay and sizeof
771+
```c
772+
static uint8_t table[16];
773+
774+
size_t size_in_caller = sizeof table; // 16
775+
776+
void use_array(uint8_t *p) {
777+
size_t size_in_callee = sizeof p; // size of pointer, not array
778+
(void)size_in_callee;
779+
}
780+
781+
void demo(void) {
782+
use_array(table); // array decays to uint8_t*
783+
}
784+
```
785+
786+
### Try it
787+
1. Print `sizeof table` in the defining scope and inside a callee parameter.
788+
2. Change the parameter to `uint8_t a[16]` and observe it’s still a pointer in the callee.
789+
3. Create `static const uint16_t lut[] = { ... }` and verify via the map file whether it resides in Flash/ROM.
790+
791+
### Takeaways
792+
- Arrays are not pointers; they decay to pointers at most expression boundaries.
793+
- `sizeof(param)` inside a function where `param` is declared as `type param[]` yields the pointer size.
794+
- Prefer passing `(ptr, length)` pairs, or wrap in a `struct` to preserve size information.
795+
796+
> Cross‑links: See `Type_Qualifiers.md` for `const/volatile` on memory‑mapped regions, and `Structure_Alignment.md` for layout implications.
797+
649798
### **What are Arrays?**
650799
651800
Arrays are collections of elements of the same type stored in contiguous memory locations. They provide efficient access to multiple related data items.
@@ -813,6 +962,8 @@ typedef union {
813962

814963
## 🔧 **Preprocessor Directives**
815964

965+
> Guideline: Keep macros minimal and local. Prefer `static inline` functions for type safety, debuggability, and better compiler analysis unless you truly need token pasting/stringification or compile‑time branching.
966+
816967
### **What are Preprocessor Directives?**
817968

818969
Preprocessor directives are instructions to the C preprocessor that are processed before compilation. They provide text substitution, conditional compilation, and file inclusion capabilities.

0 commit comments

Comments
 (0)