C code (violence) | The only good system is a sound system

4 min read Original article ↗
  • The shorter the code, the better. Code is read more often than is written.
  • If you can avoid writing code, avoid it.
  • Keep it as simple as possible, but not too much. Again, read vs write.
  • Read the code. In case of a retch, take a few deep breaths. Continue.
  • If there are no comments, it either means the code IS simple, or the code is impossible to understand unless you read a spec. See the top of a file to check if that’s the case.
  • Do not put a copy of your license on top of every file. This is awkward.
  • Use /* */-style comments instead of C++ style. Use // to comment out parts of code for a quick test.
  • Put a link to a spec on top if the code implements it. For god’s sake, compiler can’t read and doesn’t care about your spec copy-pasted into the source code. If your code really needs a copy-pasted spec, it sucks.
  • Prefer “magic” numbers with in-place comments (over a stupid enum somewhere) if your code deals with parsing of some complex format. It will be easier to debug such code while looking at the data it tries to parse.
  • Try writing self-contained code. It should be easy to use and test it separately.
  • Function name and ALL its arguments must start from a new line, ALWAYS just one line. It should be possible to find its definition with grep ^function *.c. Return type should be on the line before.
  • Function prototype ALWAYS takes a SINGLE line, so grep function *.h outputs the whole prototype. If it’s too long, you have a different problem.
  • While it might look really stupid, prepend each function prototype in the header with extern keyword if it’s gonna be used in a new code, i.e. if it’s “public” in some way. It’s easy to see what global variables and functions the header is exporting then, by simply invoking grep ^extern file.h. Private/internal function prototype can still be written without extern, so that it won’t show up in the output.
  • Function should return -1 in case of error, 0 in case of success. Not -2, or -3 unless you declare it in an enum to handle specific errors differently (try to avoid this need).
  • Do not expect functions to return -1 in case of error. Either use == 0, != 0 or < 0.
  • Always compare to nil/NULL explicitly.
  • Learn C operator precedence. Do not overuse parentheses.
  • No manual inlining, compiler should do it.
  • No #ifdefs/#endifs, code should be self-contained. Consider a separate file with initialization function instead, or a platform-specific implementation, depending on the case.
  • No #include in headers.
  • No #ifdef/#endif in headers. Include the headers once. See the one above.
  • Separate typedef from actual struct and enum, put it at the top of a file. It’s easy to both grep it and read it then. See grep ^typedef file.h.
  • Use enum instead of #define if possible.
  • No 80-column limit, although try to make it as short as easily readable.
  • Initialize a variable near the place it’s gonna be used, not at its declaration.
  • Do not use memcpy unless you absolutely sure. Just use memmove.
  • Never ever use strcpy, even if you KNOW it fits.
  • Never ever use sprintf, even if you KNOW it fits.
  • Never ever use alloca.
  • Do not expect the stack to grow.
  • Do not expect data to fit in a stack-allocated buffer. Use malloc/free if unsure.
  • If you have defined main function and there is setlocale on the target OS, call setlocale(LC_ALL, "C") there.
  • Expect a crash on any unaligned access.
  • Do not (over)use const.
  • Learn valgrind. Use it more often.
  • Before printing/logging a debug message consider its value. Only important information should be seen. Completely empty output is the best of all.
  • Prefer stderr over any kind of logging.
  • Do not make assumptions.
  • xkcd is always wrong.

Last update: June 06, 2021 08:58AM