Skip to content

Instantly share code, notes, and snippets.

@sutyum
Last active August 12, 2024 10:38
Show Gist options
  • Save sutyum/b3dcc8a67765e4474ead9cc1bbd02a58 to your computer and use it in GitHub Desktop.
Save sutyum/b3dcc8a67765e4474ead9cc1bbd02a58 to your computer and use it in GitHub Desktop.
Personal style guide for C
#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