Related sites: | site map |

# aiju.de Home of aiju

- › about me/
- › b/
- › code/
- › donate
- › electronics/
- › legal
- » misc/
- - › books
  - » c style
  - › electronics
  - › languages
  - › pic32mz usb
  - › puzzles
  - › science troll
  - › sloc
  - › software
  - › unicode
- › plan 9/

# Notes On Programming In C

See also Rob Pike's version.

## Style

(Haters gonna hate)

- Do pick one style and use it consistently. It's very awkward to read code which switches between brace styles all the time.

- If you work on an existing body of code, use the existing style. Do not introduce another, in particular your own, style.

- Do not use type prefixes on your names. Do not use `struct` prefixes on your `struct` fields. Examples how not to do it:

      int iFoo;
      char *pcBar;

      struct FooBar {
          int fb_spam, fb_eggs;
      }

- Use `typedef` for your `struct`s:

      typedef struct FooBar FooBar;

  This is preferable to `typedef struct { ... } FooBar;` because it makes it possible to use `FooBar *` pointers in `FooBar`.

- Do not define cute types.

      typedef int MyVeryOwnInt;

- Consider the following style for functions:

      int
      foobar(char *foo)
      {
      }

  It allows searching for a function using `/^foobar\(/`.

- Do not put braces around single statements, e.g. don't do

      if(foo){
          return 0;
      }

- Use pointers. Seriously, use pointers.

- Do not `#include` `.c` files. Just don't.

- Contrary to popular belief, using `goto` will not get you killed by a raptor. You should use it sparingly, but it definitely has its applications.

- Do not use `const` or `restrict`. `const` does not have any effect during runtime. `restrict` can introduce extra bugs. Both have no real use.

  - Many people seem to like `const` claiming that it "prevents bugs". I'm honestly not sure if I have ever come across a single bug that would have been prevented by const. Even if there is the odd examples for which it is true, const is a cancer that spreads through entire codebases as the only sane way to use it is to use it consistently (or else you require casts which could mask real bugs). Considering that it serves little to no purpose it's, in my opinion, just not worth effort. If you do find yourself stuck with code that insists on it, the magic incantation is `#define const`.

- Casts from and to `void *` are always implicit in C (not in C++). Explicit casts just clutter the code and make you miss out on an opportunity to annoy C++ programmers (in a similar vein try using variable names like `new`, `class` or `template`).

- Look at Windows API, glibc header files, Linux kernel source and GNU program source as examples how NOT to write code.

- Learn the operator hierarchy. Don't put parentheses where they are not needed. This one takes practice.

      if(((((a + b) - c) != d) && (x != (y + z))) || (a > b)) /* is THIS really more readable than */
      if(a + b - c != d && x != y + z || a > b) /* this? */

- Declare pointers as `int *x` not `int* x` because

      int* x, y;
      int *x, y;

  both define `x`, but not `y` as a pointer. The second one makes this more obvious. Also if you do intend two pointers `int *x, *y` looks better than either

      int* x,* y;
      int* x, *y;

## Avoiding bugs

- Do not define functions like `int foobar();`. This is a function with an undefined number of arguments, not a function without arguments as you might expect. Write `int foobar(void);`.

- Declare all your functions in advance. Using functions without prototypes is a source of bugs.

- malloc(3) can fail in various ways. The most straightforward is returning `NULL`, in which case it is a good idea to exit the program. It can also return a seemingly valid address, but your program is killed when you use it.

- Zero all structures (remember that global and static variables are automatically zeroed). Zero all memory allocated with malloc(3). (suggestion: create a function emalloc which checks for `NULL` and zeroes memory before returning it.)

- Check for the following errors and abnormalities, even if they do not appear under your configuration:

  - partial read(2) (not even an error)
  - partial write(2) (an error, unless a signal interrupted the call)
  - If your program does signal handling, all blocking syscalls can return prematurely. Be sure to check and compensate for that.

- Check for buffer overflows.

- Always make sure your strings are NUL-terminated. Beware of functions that might not NUL-terminate your strings, e.g. strncpy(3).

- Never ever pass user input to `printf`'s first argument or the format argument of any other function.

- Be aware that the fopen(3) family of functions, printf(3) in particular, are buffered.

- Set pointers to `NULL` after calling free(3) on them (unless of course the pointers themselves will be deleted as well).

- Use `volatile` for machine registers and variables shared between threads.

- Watch out for integer overflow.

- Use the CSP model of threading whenever possible. Using Go instead might be a good idea for thread heavy code.

## Complexity

- Don't support anything but UTF-8.

- Do not localize. English is the STANDARD language.

## Portability

- Try to avoid using C99 (any newer standard is so full of heresy I refuse to consider them C). In particular:

  - Do not use `//` comments.
  - Declare all local variables right at the beginning of a function.

  (Note that Microsoft Visual C does not support these features!) Some features, on the other hand, are so widely supported and useful they are worth using, e.g. `snprintf`.

- Do not perform arithmetic with `void *`. Some compilers don't like that. (Cast to `char *` first)

- Do not assume that types like `int` have a particular size. Use `stdint.h`'s `int32_t` and friends if it matters.

- Never ever assume a certain endianness. Very much avoid writing endianness dependent code. Example to read an uint32_t into memory: (it is stored in little endian)

      uint32_t i;
      unsigned char buf[4];
      read(fd, buf, 4); /* you should check for errors, EOF etc. in real code */
      i = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;

  There are very few instances where the performance benefit of just reading into `i` directly matters (It definitively won't matter if you're reading from a hard disk).

- Never ever assume that a `struct` has a certain layout in memory. In particular do not use read(2), write(2) or similar functions directly on `struct`s.

- Do not cast pointers to integers. Just don't. If you do, make sure it's `uintptr_t`.

- Avoid using floating point. It can cause portability trouble. A lot of floating point behaviour is undefined. If you do use it, you're well advised to use `double`.

- Do not use non-blocking I/O. A nice source of portability issues and race conditions if you do.

- Do not use expressions with side-effects in complicated ways. Do not assume that the compiler evaluates things between sequence points in a particular order.

      a[i++] = i; /* undefined behaviour! */

- Do not write to string literals:

      char *a;
      a = "hello, world";
      a[0] = 'H'; /* can and will error out */

  There is a trick if you want a modifiable string:

      char a[] = "hello, world";

- Do not use `register`. It most likely does nothing anyway.

- Never ever use `__attribute__` when using `gcc`. You're doing it wrong.

## Performance

- Be aware that string handling in C is unusual. strlen(2) in particular is a O(n) operation. Use pointers.

## Preprocessor

- Do not use `#define` for integer constants. Use `enum`:

      enum {
          FOO = 42,
          BAR = 23,
      };

- Avoid using macros. Really. If you have macros longer than one line, you're probably doing it wrong.

- Never ever use `#if`. You're doing it wrong.

- Avoid using `#ifdef`. It very often is avoidable and only leads to convoluted code like this:

      #ifdef WINDOWS
      a = CreateFile(...);
      #endif
      #ifdef LINUX
      a = open(...);
      #endif
      #ifdef MACOSX
      a = open(...);
      #endif

  Better solutions involve concentrating all platform specific code into a single file of which you can have multiple versions. Or in cases like this just using the portable (if ugly) fopen(3) family of functions in the first place.

  Just look at cpython's source code if you need more examples why not to use it.

- Avoid `#include` in header files. This saves you from the usual `#ifndef DICKS` / `#define DICKS` / ... / `#endif` dance.

- Avoid splitting headers in many small files. For most projects, I work with a file `fns.h` containing function prototypes and `dat.h` containing everything else.

## Really Stupid Shit

- Never ever use bitfields (the `int a:4;` thing; if you never heard of it: good for you). And I mean never ever ever ever ever under no circumstances WHATSOEVER. (They are unportable, ugly and useless; just use bit operations).

- Avoid inline assembly like the plague. There are very few instances where it can be justified. If you need to use assembly for performance or to access machine features, isolate it into a function which can be in a separate `.asm` file You should have and maintain a C version of all "for-performance" assembly code.