#include #include // for malloc #include #include #include // In order to use: assert() // 1. Use typedefs typedef struct cat { char* name; } cat; // 2. Use sized types #include uint8_t i; uint16_t j; int64_t k; // > minimum width types int_least8_t x; // > fastest? width types int_fast8_t z; // 3. Struct initialization typedef struct v3 { float x, y, z; } v3; typedef struct named_vec { char* name; v3 v; } named_vec; #define COUNT 100 typedef struct named_vecs { char* name; v3 v[COUNT]; } named_vecs; // 4. Generic C float minf(float a, float b) { if (a < b) return a; else return b; } int mini(int a, int b) { if (a < b) return a; else return b; } #define min(a, b) _Generic((a), float: minf(a, b), int: mini(a, b)) // 5. Consider using awesome macros. #define macro_var(name) name ## __LINE__ #define defer(start, end) \ for ( \ int macro_var(_i_) = (start, 0); \ !macro_var(_i_); \ (macro_var(_i_) += 1), end) \ // 5 - Example: void connect() { puts("connected."); } void disconnect() { puts("disconnected."); } #define connection_scope defer(connect(), disconnect()) // 5 - Another Example: #define scope(end) defer((void) 0, end) // 6 - PASS BY VALUE and avoid Out Parameters - Trust the Compiler Optimizations v3 v3_add(v3 a, v3 b) { // Actually leads to better performance! v3 result = { a.x + b.x, a.y + b.y, a.z + b.z }; return result; } // 6 - Example: Leads to clean APIs typedef union hmm_vec2 { struct { float X, Y; }; struct { float U, V; }; struct { float Left, Right; }; struct { float Width, Height; }; float Elements[2]; } hmm_vec2; // 7 - Use Result like Error Handling! typedef struct file_contents_t { char* data; int size; bool valid; } file_contents_t; typedef struct name_t { char* name; bool valid; } name_t; file_contents_t read_file_contents(name_t name){ file_contents_t result = { .valid = false }; if (!name.valid) { return result; } // ... return result; } // Using this Optional like pattern allows us to write functions that can be nicely chained together // typedef struct image_t { // uint8_t* data; // struct { uint16_t x, y; uint8_t channels; } size; // bool valid; // } image_t; // // image_t crop_to_cat(image_t img) { return (image_t){ .valid = false }; }; // image_t add_bow_tie(image_t img) { return (image_t){ .valid = false }; }; // image_t make_eyes_spark(image_t img) { return (image_t){ .valid = false }; }; // image_t make_smaller(image_t img) { return (image_t){ .valid = false }; }; // image_t add_rainbow(image_t img) { return (image_t){ .valid = false }; }; // // image_t get_cute_cat(image_t img) { // img = crop_to_cat(img); // img = add_bow_tie(img); // img = make_eyes_spark(img); // img = make_smaller(img); // img = add_rainbow(img); // return img; // } // Variant: use error code instead typedef enum { OKAY = 0, BAD_SIZE = 1, EMPTY_IMAGE = 2, } image_error_code; typedef struct image_t { uint8_t* data; struct { uint16_t x, y; uint8_t channels; } size; image_error_code error_code; } image_t; image_t crop_to_cat(image_t img) { return (image_t){ .error_code = EMPTY_IMAGE}; }; image_t add_bow_tie(image_t img) { return (image_t){ .error_code = EMPTY_IMAGE}; }; image_t make_eyes_spark(image_t img) { return (image_t){ .error_code = EMPTY_IMAGE}; }; image_t make_smaller(image_t img) { return (image_t){ .error_code = EMPTY_IMAGE}; }; image_t add_rainbow(image_t img) { return (image_t){ .error_code = EMPTY_IMAGE}; }; image_t get_cute_cat(image_t img) { img = crop_to_cat(img); img = add_bow_tie(img); img = make_eyes_spark(img); img = make_smaller(img); img = add_rainbow(img); return img; } // 8 - Avoid Allocations if possible. Request allocators or buffers from the user // typedef struct allocator_t { // void* user_data; // void* (*proc)(struct allocator_t* this_allocator, uintmax_t amount_to_alloc, void* ptr_to_free); // } allocator_t; typedef struct allocator_t { void* user_data; void* (*alloc)(void* user_data, uintmax_t size); void* (*realloc)(void* user_data, void* ptr, uintmax_t size); void (*free)(void* user_data, void* ptr); } allocator_t; // - Centralize your Allocations! // - USE BUFFERS WITH MAXIMUM SIZES WHERE POSSIBLE. // - Consider handles instead of pointers (Example: ECS) // = Differentiate between temporary and long lived allocators // allocator_t temp_allocator = make_allocator(arena); // while(game_is_runnning) // { // // ... // dynarr(string) strings = get_strings(temp_allocator); // // ... // free_temp_allocator(temp_allocator); // } static void* general_purpose_alloc(void* user_data, uintmax_t size) { (void)user_data; // Unused in this implementation return malloc(size); } static void* general_purpose_realloc(void* user_data, void* ptr, uintmax_t size) { (void)user_data; // Unused in this implementation return realloc(ptr, size); } static void general_purpose_free(void* user_data, void* ptr) { (void)user_data; // Unused in this implementation free(ptr); } // Create a general purpose allocator allocator_t create_general_purpose_allocator() { return (allocator_t) { .user_data = NULL, .alloc = general_purpose_alloc, .realloc = general_purpose_realloc, .free = general_purpose_free }; } // 9 - Avoid libc - its slow, very old, terrible API - Some APIs are fine: stdint, memmove, memcpy, memset, math - Replace others, Especially the string handling stuff amd printf // void print_cat(cat*); // logger_register_printer("cat", print_cat); // 10 - Use better strings typedef struct str { char* data; uintmax_t size; } str; typedef struct str_buf { char* data; uintmax_t size; uintmax_t capacity; allocator_t allocator; } str_buf; str_buf str_buf_make(uintmax_t capacity, allocator_t allocator) { return (str_buf) { .data = (char*)allocator.alloc(allocator.user_data, capacity), .capacity = capacity, .allocator = allocator }; } void str_buf_append(str_buf buf, str s) { if (buf.size + s.size > buf.capacity) { uintmax_t new_capacity = buf.capacity * 2 + s.size; buf.data = buf.allocator.realloc(buf.allocator.user_data, buf.data, new_capacity); buf.capacity = new_capacity; } memcpy(buf.data + buf.size, s.data, s.size); buf.size += s.size; } void str_buf_insert(str_buf buf, str s, uintmax_t pos) { if (pos > buf.size) return; // Invalid position if (buf.size + s.size > buf.capacity) { uintmax_t new_capacity = buf.capacity * 2 + s.size; buf.data = buf.allocator.realloc(buf.allocator.user_data, buf.data, new_capacity); buf.capacity = new_capacity; } memmove(buf.data + pos + s.size, buf.data + pos, buf.size - pos); memcpy(buf.data + pos, s.data, s.size); buf.size += s.size; } void str_buf_remove(str_buf buf, uintmax_t start, uintmax_t end) { if (start >= buf.size || end > buf.size || start >= end) return; // Invalid position uintmax_t remove_length = end - start; memmove(buf.data + start, buf.data + end, buf.size - end); buf.size -= remove_length; } str str_buf_to_str(str_buf buf) { return (str) { .data = buf.data, .size = buf.size }; } void str_buf_free(str_buf buf) { buf.allocator.free(buf.allocator.user_data, buf.data); buf.data = NULL; buf.size = 0; buf.capacity = 0; } void str_print(str s) { fwrite(s.data, 1, s.size, stdout); } void str_println(str s) { str_print(s); putchar('\n'); } void str_buf_print(str_buf buf) { str s = str_buf_to_str(buf); str_print(s); } void str_buf_println(str_buf buf) { str_buf_print(buf); putchar('\n'); } // 11. Use Bitfields: In order to model memory mapped interfaces as well as as compact enums struct ControlRegister { unsigned int enable: 1; unsigned int mode: 2; volatile unsigned int interrupt: 1; unsigned int reserved: 4; }; // Represents an 8-bit register typedef union { struct { // Anonymous struct within union for better "type punning" uint8_t read: 1; uint8_t write: 1; uint8_t execute: 1; uint8_t hidden: 1; uint8_t system: 1; uint8_t reserved: 3; // unused for future expansion }; uint8_t all; } FilePermissions; // Use inline functions for flag operations static inline void set_permission(FilePermissions *perm, FilePermissions flag) { perm->all |= flag.all; } static inline void clear_permission(FilePermissions *perm, FilePermissions flag) { perm->all &= ~flag.all; } static inline bool has_permission(FilePermissions perm, FilePermissions flag) { return (perm.all & flag.all) == flag.all; } // Use designated initializers for creating flag constants static const FilePermissions FLAG_READ = { .read = 1 }; static const FilePermissions FLAG_WRITE = { .write = 1 }; static const FilePermissions FLAG_EXECUTE = { .execute = 1 }; static const FilePermissions FLAG_HIDDEN = { .execute = 1 }; static const FilePermissions FLAG_SYSTEM = { .system = 1 }; int main() { cat a = { .name = "satyam" }; // 3-NOTE: ZII: Zero is Initialization v3 v = { .z = 1.0f }; // undefined members initialized to zero implicitly // v = { .x = 100.0f }; // Error! v = (v3){ .x = 100.0f }; // Good! assert(v.x == 100.0f); assert(v.y == 0.0); assert(v.z == 0.0); named_vec v1 = { .name = "vector 1", .v = { .x = 1.0f, .y = 2.0f } }; named_vecs vecs1 = { .name = "vector 2", .v[0] = { .x = 3.0f, .y = 1.0f }, .v[10].z = 10.f }; named_vecs vecs2 = { .name = "vector 3", .v = { [0] = { .x = 1.0f, .z = 1.1f }, [10] = { .y = 10.f }, [11].x = 11.f, [99].z = 10.f } }; static_assert(2 + 2 == 4, "Run this at compile time!"); // Requires a header: int min = min(10, 11); assert(min == 10); defer(connect(), disconnect()){ for (int i = 0; i<= 3; i++) { puts("ping"); } } connection_scope { for (int i = 0; i<= 3; i++) { puts("ping"); } } scope(disconnect()){ puts("hello"); } v3 sum = v3_add(v1.v, vecs1.v[0]); name_t filename = { .name = "file.txt", .valid = true }; file_contents_t fc = read_file_contents(filename); if(!fc.valid) { puts("File is invalid"); // return -1; } // image_t some_image = { // .data = (uint8_t[]){ 11, 12, 21, 22 }, // .size = { .x = 2, .y = 2, .channels = 1 }, // .valid = true // }; image_t some_image = { .data = (uint8_t[]){ 11, 12, 21, 22 }, .size = { .x = 2, .y = 2, .channels = 1 }, .error_code = OKAY }; image_t img = get_cute_cat(some_image); // log("Cat: {cat}", a); // 10. Using our allocator and our string api allocator_t gpa = create_general_purpose_allocator(); str_buf buffer = str_buf_make(16, gpa); str hello = { .data = "Hello, ", .size = 7 }; str world = { .data = "world!", .size = 6}; str_buf_append(buffer, hello); str_buf_append(buffer, world); str_buf_println(buffer); str result = str_buf_to_str(buffer); str_println(result); str_buf_free(buffer); // 11. Example of Bitfields for Memory Mapped Registers struct ControlRegister cr = { .enable = 1, .mode = 1 << 1 }; printf("[ enable (%d) | mode (%d) | interrupt (%d) | reserved (%d) ]\n", cr.enable, cr.mode, cr.interrupt, cr.reserved); // 11. Use for modeling combinatorial flags FilePermissions perms = (FilePermissions){ .read = 1, .execute = 1 }; printf("initial permissions: %02X\n", perms.all); set_permission(&perms, FLAG_WRITE); printf("After adding write permissions: %02X\n", perms.all); if (has_permission(perms, FLAG_READ) && has_permission(perms, FLAG_EXECUTE)) { puts("File is both readable and executable"); } puts("Program Ended Safely"); return 0; }