****** 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