|
| 1 | +# Auto-Cleanup (RAII in C) |
| 2 | + |
| 3 | +One of the biggest pain points in standard C is manual memory management. If a |
| 4 | +library dynamically allocates memory to capture a string or a file, the |
| 5 | +developer is strictly responsible for calling `free()` when they are done. |
| 6 | + |
| 7 | +If you forget, your program leaks memory. If you call it twice, your program |
| 8 | +crashes. |
| 9 | + |
| 10 | +Languages like C++ and Rust solved this decades ago using **RAII** (Resource |
| 11 | +Acquisition Is Initialization) and destructors. When a variable goes out of |
| 12 | +scope (reaches the closing brace `}` of its block), the compiler automatically |
| 13 | +inserts the cleanup code. |
| 14 | + |
| 15 | +With NextStd, you get that exact same magic in C. |
| 16 | + |
| 17 | +## The `ns_autocmd` Macro |
| 18 | + |
| 19 | +NextStd leverages a powerful, widely-supported GCC/Clang compiler extension |
| 20 | +called `__attribute__((cleanup))`. We wrap this extension in a simple, ergonomic |
| 21 | +macro called `ns_autocmd`. |
| 22 | + |
| 23 | +When you prefix your `ns_cmd_output` struct with `ns_autocmd`, you are attaching |
| 24 | +a hidden destructor to the variable. The moment that variable falls out of |
| 25 | +scope, the compiler automatically intercepts it and safely frees any internal |
| 26 | +`ns_string` heap allocations. |
| 27 | + |
| 28 | +```c |
| 29 | +#include <nextstd/ns.h> |
| 30 | +#include <nextstd/ns_cmd.h> |
| 31 | + |
| 32 | +void run_status_check() { |
| 33 | + // 1. Declare the struct with the auto-cleanup macro |
| 34 | + ns_autocmd ns_cmd_output out = {0}; |
| 35 | + |
| 36 | + // 2. Run the command |
| 37 | + ns_cmd_run("uptime", &out); |
| 38 | + |
| 39 | + // 3. Print the result |
| 40 | + ns_println("System Uptime: {}", out.stdout_data); |
| 41 | + |
| 42 | + // 4. End of scope. The compiler automatically calls |
| 43 | + // ns_cmd_output_free(&out) right here! No memory leaks! |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +## ⚠️ The Golden Rule: Zero-Initialization |
| 48 | + |
| 49 | +There is one critical rule when using `ns_autocmd`: |
| 50 | +**You must always zero-initialize your struct (`= {0};`).** |
| 51 | + |
| 52 | +```c |
| 53 | +// ❌ DANGEROUS: Contains random stack garbage memory |
| 54 | +ns_autocmd ns_cmd_output bad_out; |
| 55 | + |
| 56 | +// ✅ SAFE: Memory is explicitly zeroed out |
| 57 | +ns_autocmd ns_cmd_output good_out = {0}; |
| 58 | +``` |
| 59 | +
|
| 60 | +**Why is this required?** In C, uninitialized stack variables contain random |
| 61 | +"garbage" data left over in RAM. |
| 62 | +
|
| 63 | +If `ns_cmd_run` were to fail *before* it could successfully populate your struct |
| 64 | +(for example, if the system couldn't spawn a shell), the destructor would |
| 65 | +eventually run and try to `free()` that random garbage memory. This results in a |
| 66 | +guaranteed segmentation fault. |
| 67 | +
|
| 68 | +By zero-initializing the struct (`= {0};`), you guarantee that if the command |
| 69 | +fails early, the destructor simply sees `NULL` pointers and lengths of `0`, and |
| 70 | +safely does nothing. |
| 71 | +
|
| 72 | +## Manual Cleanup |
| 73 | +
|
| 74 | +If you are compiling on a highly restrictive embedded compiler that does not |
| 75 | +support GCC/Clang extensions, or if you simply prefer manual memory management, |
| 76 | +you can still declare the struct normally and use the manual free function: |
| 77 | +
|
| 78 | +```c |
| 79 | +ns_cmd_output out = {0}; |
| 80 | +ns_cmd_run("ls", &out); |
| 81 | +ns_println("Output: {}", out.stdout_data); |
| 82 | +
|
| 83 | +// Manually free the internal strings |
| 84 | +ns_cmd_output_free(&out); |
| 85 | +``` |
0 commit comments