Last active
August 12, 2024 10:38
-
-
Save sutyum/b3dcc8a67765e4474ead9cc1bbd02a58 to your computer and use it in GitHub Desktop.
Personal style guide for C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <stdlib.h> // for malloc | |
| #include <memory.h> | |
| #include <stdbool.h> | |
| #include <assert.h> // In order to use: assert() | |
| // 1. Use typedefs | |
| typedef struct cat { char* name; } cat; | |
| // 2. Use sized types | |
| #include <stdint.h> | |
| 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: <assert.h> | |
| 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; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment