site-josuah

/usr/josuah
Log | Files | Refs

commit 3130cb95171b22372ada867ccb22b94d45a2a54b
parent e06756f402ed9bc461f39b5c858e5e2fb5bf2437
Author: Josuah Demangeon <me@josuah.net>
Date:   Sat, 25 Jul 2020 14:47:00 +0200

add article about coding style

Diffstat:
Awiki/c-coding-style/index.md | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 316 insertions(+), 0 deletions(-)

diff --git a/wiki/c-coding-style/index.md b/wiki/c-coding-style/index.md @@ -0,0 +1,316 @@ +Bits of C programming style +=========================== +that helps to isolate unrelated parts of your programs. + +Naming convention and project hierarchy +--------------------------------------- +* ./Makefile (and config.mk, content.mk, Makefile.linux, Makefile.bsd...) +* ./README, ./README.md +* ./progname.c +* ./progname.1 +* ./doc/rfc3564.txt, ./doc/progname.1 +* ./src/base64.c, ./src/random.c, ./src/conf.c + +And then, following the file name in ./src/: + +* base64_decode(), base64_encode(), ... +* random_bytes(), random_u64(), random_u32(), random_mem(), ... +* conf_read_file(), conf_parse(), conf_get(), conf_dump(), ... + +assert(), assert(), assert() +---------------------------- +* a little bit closer to memory safe languages in some cases. +* no performance overhead in production. +* assert or unit tests: program internals. +* assert or unit tests: fuzzing. +* assert and unit tests: larger libraries, code bases +* for libraries: helper program + +Error handling +-------------- + +### Error handling if() style + +``` + mem = malloc(3); + if (mem == NULL) + return NULL; +-vs- + if ((mem = malloc(3)) == NULL) + return NULL; +``` + +### Error handling with goto + +``` + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + + mem = malloc(3); + if (mem == NULL) { + close(fd); + return -1; + } + + if (do_something_1() < 0) { + close(fd); + free(mem); + return -1; + } + + if (do_something_2() < 0) { + close(fd); + free(mem); + return -1; + } + + return 0; + +-vs- + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + + mem = malloc(3); + if (mem == NULL) + goto err_close; + + if (do_something_1() < 0) + goto err_close_free; + + if (do_something_2() < 0) { + goto err_close_free; + + return 0; +err_close: + close(fd); +err_close_free: + free(mem); + return -1; + +-vs- + fd = -1; + mem = NULL; + + fd = open(path, O_RDONLY); + if (fd < 0) + goto err; + + mem = malloc(3); + if (mem == NULL) + goto err; + + if (do_something_1() < 0) + goto err; + + if (do_something_2() < 0) { + goto err; + + return 0; +err: + close(fd); /* does nothing if fd == -1 */ + free(mem); /* does nothing if mem == NULL */ + return -1; +``` + +Known as "the only right use of goto". + +### Error handling with enums + switch + *_errstr + +* Because printing error from within a library messes with the program I/O + (considered a bad practice everywhere) +* Because 0 for success and -1 for error (or 1 for success and 0 for error) + does not tells us much... +* try { ... } catch (errortype) { ... } in plain C for free! + +``` + int + conf_read_file(char const *path, struct conf *cf) + { + int fd, err = 0; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -CONF_ERR_SYSTEM; + + err = conf_parse(fd, cf); + if (err < 0) + goto end; + end: + close(fd); + return err; + } +... + int + conf_errstr(int i) + { + enum conf_errno err = (i > 0) ? i : -i; + + switch (err) { + case CONF_ERR_SYSTEM: + return "system error"; + case CONF_ERR_SYNTAX: + return "syntax error"; + } + assert(!"all errno should have been handled before"); + return "unknown error"; /* make compiler happy */ + } +... + int + main(int argc, char **argv) + { + struct conf cf = {0}; + int err; + + err = conf_get(path, &conf); + switch (-err) { + case 0: + break; + case -CONF_ERR_SYSTEM: + fprintf(stderr, "%s: %s: %s\n", + argv[0], conf_strerror(err), strerror(errno)); + return -1; + default: + fprintf(stderr, "%s: %s\n", + argv[0], conf_strerror(err)); + return -1; + } + + return 0; + } +``` + +(Ab)use of types +---------------- + +### struct is the key + +What if more parameter to give? Change all the functions? + +``` + do_something(buf1, len1, prop1, buf2, len2, prop2, buf3, len3, prop3); +-vs- + do_something(struct1, struct2, struct3); +``` + +Struct gives an outline of the data structures that the program uses: + + "Choose the data structures and the program will write itself" + +### enum is the sight + +In switch statements and in code: + +``` + switch (get_state()) { + case 2: + do_it_again(); + break; + case 1: + next_step(); + break; + } +-vs- + switch (get_state()) { + case PARTIAL_DATA: + do_it_again(); + break; + case DONE: + next_step(); + break; + } + /* compiler warning for missing TOO_MUCH_DATA */ +``` + +Initialize fields: + +``` + char *state_to_description[] = { + NULL, + "all done, goodbye", + "partial data read, doing it again", + NULL, + NULL, + "too much input read, erroring out", + }; +-vs- + char *state_to_description[] = { + [DONE] = "all done, goodbye", + [PARTIAL_DATA] = "partial data read, doing it again", + [TOO_MUCH_DATA] = "too much input read, erroring out", + }; +``` + +Memory management +----------------- + +### Same struct type, various size: + +``` + struct obj { + char *name, *description; . length given by + size_t len; |- sizeof(struct obj) + struct obj *next; ' + char buf[]; :- variable length at + }; the end of the struct + + struct obj * + obj_parse_new(char const *input, struct obj **first) + { + struct obj *new; + size_t len; + + len = strlen(input); + new = calloc(1, sizeof *new + len); + if (new == NULL) + return NULL; + + memcpy(new->buf, input, len); + new->len = len; + + if (obj_parse(new) < 0) + goto err; + + new->next = *first; + *first = new; + return new; + err: + free(new); + return NULL; + } +``` + +### Immutable pointers and linked lists + +Not an immutable pointer: it changes the pointer as the memory grows: + +``` + struct obj * + obj_grow(struct obj *obj) + { + mem = realloc(obj, obj->len + 10) + if (mem == NULL) + return NULL; + obj = mem; + obj->len += 10; + } +``` + +This is what we want for linked lists: it does not change the pointer as the +memory grows. + +``` + int + obj_grow(struct obj *obj) + { + mem = realloc(obj->buf, obj->len + 10) + if (mem == NULL) + return -1; + obj->buf = mem; + obj->len += 10; + } +``` + +Variable-size struct member as shown before: only when size does not change in +advance.