the uncommon way
use a single main file that includes all necessary code
the traditional way is to compile parts of the application separately into objects, maintain header files with declarations for each and then link to the object files where needed. this may save time in the development of big projects when most objects have previously been compiled and only a few have changed and need to be recompiled. it is probably of no use to a user that just wants to compile the code once and has to compile all of it and is faced with a neverending log of compiler calls and difficult to understand makefiles
further information: wikipedia article . sqlite uses this style. this style is similar to javascript in html without module systems where all source files and dependencies are included before use at the beginning of an html file
status_t status = {0, ""};
, the other status_*
bindings use that variable#include "sph/status.c"
status_t test() {
status_declare;
if (1 < 2) {
status_set_both_goto("mylib", 456);
}
exit:
return status;
}
int main() {
status_init;
// code ...
status_require(test());
// more code ...
exit:
return status.id;
}
track allocations locally
this example uses a reference implementation from sph-sc-lib . sph-sc-lib also contains a version with multiple named registers and a register to be passed between routines memreg_init(4) creates an address register on the stack for at most four pointers memreg_register is the variable and memreg_index is the current index memreg_add(address) adds a pointer to the register memreg_free frees all pointers added so far
#include "sph/memreg.c"
int main() {
memreg_init(2);
int* data_a = malloc(12 * sizeof(int));
if(!data_a) goto exit; // have to free nothing
memreg_add(data_a);
// more code ...
char* data_b = malloc(20 * sizeof(char));
if(!data_b) goto exit; // have to free "data_a"
memreg_add(data_b);
// ...
if (is_error) goto exit; // have to free "data_a" and "data_b"
// ...
exit:
memreg_free;
return(0);
}
a null pointer can be created with setting a pointer to literal zero. calling free on a null pointer is allowed
the stack is memory space that is reserved for the extend of a routine call, for example to store routine arguments and local variables. it has a pre-calculated, limited or fixed size. heap memory is all other available system memory
when arguments are copied with a routine call it prevents the routine from changing state in the caller scope and no thought has to be given to the question if outside execution changes or depends on the values
it might save declaration overhead, but access of a local is often faster because the compiler can better predict where it is modified and prepare to cache values
0m10.745s 0m10.739s
0m9.931s 0m9.940s
the c preprocessor doesnt support hygienic macros. that means macro functions can introduce newly bound identifiers and use and modify variables from the current scope
all stack allocations are being made at the beginning of a routine anyway and having all declarations at the beginning groups this type of preparation, so it might make sense to have all declarations at the top
here are some alternative type names that could be used
i8, i16, i32, i64, i8-least, i16-least, i32-least, i64-least, i8-fast, i16-fast, i32-fast, i64-fast, ui8, ui16, ui32, ui64, ui8, ui16-least, ui32-least, ui64-least, ui8-fast, ui16-fast, ui32-fast, ui64-fast, f32 float, f64 double, pointer, boolean
incidentally, the rust language actually uses some of them