****** aiju.de_Home_of_aiju ****** ****** Notes On Programming In C ****** DISCLAIMER: I don't agree with this anymore but I'm leaving it up for historical interest. 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 structs: 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. o 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: o partial read(2) (not even an error) o partial write(2) (an error, unless a signal interrupted the call) o 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: o Do not use // comments. o 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 structs. * 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. Powered_by_werc