I think C is a pretty good programming language.
But there’s a lot of “old knowledge” in C – the sort of stuff that you only really learn by reading through actual code bases, I thought I’d leave some tips I’ve found here.
From the standard:
If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration
C99 Standard Draft, Section 6.7.8, Point 21
Looking at point 10 just above that we will see that this means
initialized to NULL/0
. This gives way to the common practive of doing
something like
int f(int k)
{
struct myStruct = { 0 };
// ...
}
Which gives us a nicely zero-initialized variable on the stack (same applies for arrays, etc.).
This ruling also applies if you just wanted to initialize one or two members to non-zero.
Here’s a trick I learnt from sfeed,
you can use an enum to name the members of some data stored in an
array. As a bonus you can add a COUNT
member which will be the size
of your array to read the whole blob into it.
``` enum { PKT_HEADER = 0, PKT_BYTE1, PKT_BYTE2, PKT_BYTE3, PKT_CHECKSUM, PACKET_COUNT };
// … unsigned char data[PACKET_COUNT]; fread(data, sizeof(*data), PACKET_COUNT, fp); printf(“Byte 1: %hhu\n”, data[PKT_BYTE1]); }
```
I could see people at work complaining this is horrible design, but I quite like it :)
Taking a look at the snippet above, you might’ve also noticed that the
size
parameter that I pass to fread(3)
is coming from the size of
whatever our variable points to indirectly.
This means we don’t have to copy-paste the data type into the sizeof and get a codebase that would work if we started using a different-sized data type (obviously in this example it wouldn’t make a tonne of sense for that to happen – but I think it is an improvement here anyway).
The address sanitizer will make debugging segfaults actually viable
(-fsanitize=address
), and should absolutely be enabled during
development.
The leak sanitizer will also catch a lot of memory leaks (direct &
indirect) and will probably encourage you to fix them now instead of later
(which you should). In my version of gcc
, enabling ASAN also turns
on LSAN.
This isn’t really a C-specific piece of advice but I think it’s something
that most people overlook – assertions are like breakpoints baked into
your program to stop you doing stupid shit. This is especially helpful
when the language doesn’t throw
exceptions when you do said stupid shit.
I also really like using them if I’m stubbing out functions – leave an
assert(0 && "TODO: Implement this thing")
, and that way you can safely
forget about that stub until you need it.