@@ -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]
740871uint32_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