Skip to content

Commit 686efd8

Browse files
Enhance Documentation for C Programming Mastery Section
1 parent 18aa300 commit 686efd8

1 file changed

Lines changed: 317 additions & 0 deletions

File tree

Embedded_C/C_Language_Fundamentals.md

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,137 @@ void heap_example(void) {
693693
}
694694
```
695695

696+
#### **Practical Scenario: Stack vs Heap Comparison**
697+
698+
```c
699+
/*
700+
* Stack vs Heap: When to use each
701+
*
702+
* STACK: small, fixed-size, short-lived data
703+
* HEAP: large, variable-size, or data that outlives the function
704+
*/
705+
706+
// ✅ Stack: small fixed buffer for local processing
707+
void process_sensor(void) {
708+
uint8_t raw[8]; // 8 bytes on stack - fast, automatic
709+
read_sensor(raw, 8);
710+
uint16_t value = (raw[0] << 8) | raw[1];
711+
send_value(value);
712+
} // raw automatically freed here
713+
714+
// ✅ Heap: large buffer or data returned to caller
715+
uint8_t* allocate_frame_buffer(size_t width, size_t height) {
716+
size_t size = width * height * 3; // RGB
717+
uint8_t* fb = malloc(size);
718+
if (fb) memset(fb, 0, size);
719+
return fb; // caller must free
720+
}
721+
```
722+
723+
#### **Common Pitfalls with Code Examples**
724+
725+
**Pitfall 1: Returning Stack Address (Dangling Pointer)**
726+
```c
727+
// ❌ BUG: returning address of stack memory
728+
uint8_t* bad_get_buffer(void) {
729+
uint8_t tmp[64];
730+
fill_buffer(tmp);
731+
return tmp; // UNDEFINED BEHAVIOR - tmp is gone after return
732+
}
733+
734+
// ✅ FIX: use caller-provided buffer or heap
735+
void good_get_buffer(uint8_t* out, size_t len) {
736+
fill_buffer(out); // caller owns the memory
737+
}
738+
739+
uint8_t* good_get_buffer_heap(size_t len) {
740+
uint8_t* buf = malloc(len);
741+
if (buf) fill_buffer(buf);
742+
return buf; // caller must free
743+
}
744+
```
745+
746+
**Pitfall 2: Memory Leak in Error Path**
747+
```c
748+
// ❌ BUG: memory leak if second allocation fails
749+
int bad_init(void) {
750+
ctx->buf1 = malloc(1024);
751+
if (!ctx->buf1) return -1;
752+
753+
ctx->buf2 = malloc(2048);
754+
if (!ctx->buf2) return -1; // LEAK: buf1 not freed!
755+
756+
return 0;
757+
}
758+
759+
// ✅ FIX: clean up on error
760+
int good_init(void) {
761+
ctx->buf1 = malloc(1024);
762+
if (!ctx->buf1) return -1;
763+
764+
ctx->buf2 = malloc(2048);
765+
if (!ctx->buf2) {
766+
free(ctx->buf1); // clean up first allocation
767+
ctx->buf1 = NULL;
768+
return -1;
769+
}
770+
return 0;
771+
}
772+
```
773+
774+
**Pitfall 3: Use-After-Free**
775+
```c
776+
// ❌ BUG: accessing freed memory
777+
void bad_cleanup(msg_t* msg) {
778+
free(msg->payload);
779+
log("Freed %zu bytes", msg->payload_len); // OK so far
780+
781+
// ... later in code ...
782+
if (msg->payload[0] == 0xAA) { } // UAF! payload is freed
783+
}
784+
785+
// ✅ FIX: NULL after free, check before use
786+
void good_cleanup(msg_t* msg) {
787+
free(msg->payload);
788+
msg->payload = NULL; // prevent accidental reuse
789+
msg->payload_len = 0;
790+
}
791+
```
792+
793+
**Pitfall 4: Double Free**
794+
```c
795+
// ❌ BUG: freeing the same memory twice
796+
void bad_reset(void) {
797+
free(global_buf);
798+
// ... other code ...
799+
free(global_buf); // DOUBLE FREE - undefined behavior
800+
}
801+
802+
// ✅ FIX: NULL after free
803+
void good_reset(void) {
804+
free(global_buf);
805+
global_buf = NULL;
806+
// ... other code ...
807+
free(global_buf); // safe: free(NULL) is a no-op
808+
}
809+
```
810+
811+
**Pitfall 5: Stack Overflow (Large Local Arrays)**
812+
```c
813+
// ❌ RISKY: large array on stack (may overflow small embedded stack)
814+
void bad_process_image(void) {
815+
uint8_t frame[320 * 240]; // 76KB on stack!
816+
capture_frame(frame);
817+
}
818+
819+
// ✅ SAFER: use static or heap for large buffers
820+
static uint8_t frame_buffer[320 * 240]; // in .bss, not stack
821+
822+
void good_process_image(void) {
823+
capture_frame(frame_buffer);
824+
}
825+
```
826+
696827
## 🎯 **Pointers**
697828

698829
### **What are Pointers?**
@@ -740,6 +871,192 @@ uint32_t second = *(array_ptr + 1); // array[1]
740871
uint32_t third = array_ptr[2]; // array[2]
741872
```
742873
874+
#### **Practical Embedded Examples**
875+
876+
**Memory-Mapped Register Access**
877+
```c
878+
// Direct hardware register manipulation via pointers
879+
#define GPIO_BASE 0x40020000u
880+
#define GPIO_MODER (*(volatile uint32_t*)(GPIO_BASE + 0x00))
881+
#define GPIO_ODR (*(volatile uint32_t*)(GPIO_BASE + 0x14))
882+
#define GPIO_IDR (*(volatile uint32_t*)(GPIO_BASE + 0x10))
883+
884+
void gpio_set_output(uint8_t pin) {
885+
GPIO_MODER &= ~(3u << (pin * 2)); // clear mode bits
886+
GPIO_MODER |= (1u << (pin * 2)); // set output mode
887+
}
888+
889+
void gpio_write(uint8_t pin, uint8_t val) {
890+
if (val) GPIO_ODR |= (1u << pin);
891+
else GPIO_ODR &= ~(1u << pin);
892+
}
893+
```
894+
895+
**Pointer Arithmetic: Type Matters**
896+
```c
897+
/*
898+
* Key insight: ptr + 1 advances by sizeof(*ptr) bytes
899+
*
900+
* uint8_t* + 1 = +1 byte
901+
* uint16_t* + 1 = +2 bytes
902+
* uint32_t* + 1 = +4 bytes
903+
*/
904+
void demonstrate_pointer_arithmetic(void) {
905+
uint8_t buf[16];
906+
907+
uint8_t* p8 = buf;
908+
uint16_t* p16 = (uint16_t*)buf;
909+
uint32_t* p32 = (uint32_t*)buf;
910+
911+
// All start at same address
912+
// p8 = 0x2000
913+
// p16 = 0x2000
914+
// p32 = 0x2000
915+
916+
p8++; // p8 = 0x2001 (+1 byte)
917+
p16++; // p16 = 0x2002 (+2 bytes)
918+
p32++; // p32 = 0x2004 (+4 bytes)
919+
}
920+
```
921+
922+
**Practical: Parsing a Protocol Packet**
923+
```c
924+
/*
925+
* Parse: [SYNC:1][LEN:2][CMD:1][PAYLOAD:n][CRC:2]
926+
* This is how embedded protocols like UART frames are parsed
927+
*/
928+
typedef struct {
929+
uint8_t cmd;
930+
uint16_t len;
931+
uint8_t* payload;
932+
uint16_t crc;
933+
} packet_t;
934+
935+
bool parse_packet(uint8_t* raw, size_t raw_len, packet_t* pkt) {
936+
uint8_t* p = raw;
937+
uint8_t* end = raw + raw_len;
938+
939+
// Check minimum size
940+
if (raw_len < 6) return false;
941+
942+
// Parse SYNC
943+
if (*p++ != 0xAA) return false;
944+
945+
// Parse LEN (little-endian 16-bit)
946+
pkt->len = p[0] | (p[1] << 8);
947+
p += 2;
948+
949+
// Bounds check before accessing payload
950+
if (p + pkt->len + 3 > end) return false;
951+
952+
// Parse CMD
953+
pkt->cmd = *p++;
954+
955+
// Payload pointer (no copy, just reference)
956+
pkt->payload = p;
957+
p += pkt->len;
958+
959+
// Parse CRC
960+
pkt->crc = p[0] | (p[1] << 8);
961+
962+
return true;
963+
}
964+
```
965+
966+
**Pointer Comparison and Bounds Checking**
967+
```c
968+
/*
969+
* Safe buffer iteration with boundary checks
970+
* Common pattern for circular buffers and DMA
971+
*/
972+
void safe_buffer_copy(uint8_t* dst, const uint8_t* src,
973+
size_t len, size_t dst_size) {
974+
const uint8_t* src_end = src + len;
975+
uint8_t* dst_end = dst + dst_size;
976+
977+
while (src < src_end && dst < dst_end) {
978+
*dst++ = *src++;
979+
}
980+
}
981+
982+
// Ring buffer read with wrap-around
983+
size_t ring_read(ring_t* r, uint8_t* out, size_t max) {
984+
size_t count = 0;
985+
uint8_t* end = r->buf + r->size; // one past last valid
986+
987+
while (count < max && r->head != r->tail) {
988+
*out++ = *r->tail++;
989+
if (r->tail >= end) {
990+
r->tail = r->buf; // wrap to start
991+
}
992+
count++;
993+
}
994+
return count;
995+
}
996+
```
997+
998+
**Multi-Byte Access Patterns (Endianness-Aware)**
999+
```c
1000+
/*
1001+
* Portable multi-byte read/write for protocol buffers
1002+
* Avoids alignment issues and works regardless of CPU endianness
1003+
*/
1004+
1005+
// Read 16-bit little-endian from byte buffer
1006+
static inline uint16_t read_le16(const uint8_t* p) {
1007+
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
1008+
}
1009+
1010+
// Read 32-bit big-endian from byte buffer (network order)
1011+
static inline uint32_t read_be32(const uint8_t* p) {
1012+
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
1013+
((uint32_t)p[2] << 8) | (uint32_t)p[3];
1014+
}
1015+
1016+
// Write 16-bit little-endian to byte buffer
1017+
static inline void write_le16(uint8_t* p, uint16_t v) {
1018+
p[0] = (uint8_t)(v & 0xFF);
1019+
p[1] = (uint8_t)(v >> 8);
1020+
}
1021+
1022+
// Usage: build a packet
1023+
void build_response(uint8_t* buf, uint16_t seq, uint32_t value) {
1024+
buf[0] = 0xAA; // sync
1025+
write_le16(buf + 1, seq); // sequence number
1026+
buf[3] = 0x02; // command
1027+
write_le16(buf + 4, (uint16_t)value); // payload
1028+
}
1029+
```
1030+
1031+
**Void Pointers and Type Casting**
1032+
```c
1033+
/*
1034+
* void* is the "generic" pointer - can point to any type
1035+
* Must cast before dereferencing
1036+
* Common in callbacks, memory allocators, and HAL APIs
1037+
*/
1038+
1039+
// Generic compare callback (like qsort)
1040+
typedef int (*compare_fn)(const void*, const void*);
1041+
1042+
int compare_uint16(const void* a, const void* b) {
1043+
uint16_t va = *(const uint16_t*)a;
1044+
uint16_t vb = *(const uint16_t*)b;
1045+
return (va > vb) - (va < vb);
1046+
}
1047+
1048+
// Generic memory pool
1049+
void* pool_alloc(pool_t* pool, size_t size) {
1050+
if (pool->free + size > pool->end) return NULL;
1051+
void* ptr = pool->free;
1052+
pool->free += size;
1053+
return ptr;
1054+
}
1055+
1056+
// Usage
1057+
sensor_t* s = (sensor_t*)pool_alloc(&pool, sizeof(sensor_t));
1058+
```
1059+
7431060
#### **Function Pointers**
7441061
```c
7451062
// Function pointer type

0 commit comments

Comments
 (0)