I’ve spent quite some time integrating a lot of C code from different people to turn it into production-ready software. It is surprisingly interesting, but the first few days are usually quite painful for these reasons:

Logs

Many C developers have done tens of projects but still end up dealing with errors with a dirty fprintf(stderr, ... ); . I use something similar to this. But you should at least insert the __FILE__ and __LINE__ macros. It will save lots of people hours of debugging.

Use the compiler warnings

On gcc, you should always have -Wall -Werror (and -Wextra when possible) enabled.

This will avoid a lot of bugs.

Error code

Do not reinvent your own error codes. Use the the POSIX error codes.

Error handling (and logging)

Check the return codes, log the abnormal ones.

Sample code that can make you lose quite some time:

FILE * f = fopen("logs.txt","w");
if ( ! f )
   goto end;

THIS IS WRONG, we should have something like this:

LL_WARNING("Could not open log file !");

using this kind of macro.

Dirty numbers

You know how to count in bytes, how pointer and offsets work, that’s great. But you should also know that you can always replace this by a sizeof(). That way, we won’t have to be as smart as you to read your code (and fix it).

Great POSIX standard headers and libraries

The standard errors code already cover a lot of cases you could encounter.

You can use booleans including stdbool.h, it’s even standard.

Argument parsing is standard, don’t make your own strange argument parsing code that won’t even (fully) document.

Great non standard libraries

Like json-c to serialize your data, or zeromq to handle network communication.

Bad memory management

Leaks and buffer overflow can happen. And so as many other issues out there. This is why you should use tools like valgrind frequently.

“It worked fine on my computer” is never a good excuse, but on C it’s even more meaningless. The memory allocation scheme can be very different from one OS/architecture to another. Many buffer overflows don’t appear on desktop computers but exploded on embedded ARM devices.

Malloc is not your (only) friend

In most cases, allocating things on the stack is enough. Declaring an array like that is ok in C99:

uint8_t bin[strlen(hex)/2+1];

Use a debugger

Debugger will make you save a lot of time especially when they are well integrated in IDEs like netbeans. You can just immediately know why something crashed or why the programmed stalled for no apparent reasons.

Tiny note on debugging: If you have strange issues and can’t seem to get a correct stacktrace, it’s probably a stack-smashing issue. Compile your code with the -fstack-protector flag, this will report the stack smashing (most probably a buffer overflow) as soon as it occurs.

Optimization

In most of the cases, your compiler will do a great job. Don’t use inline, prefetching or any other crazy optimization unless you really know what you do.

Don’t re-create unreadable network protocols

Use something simple readable like JSON over zeromq. It’s easier to create, debug, monitor. Please don’t create a new binary protocol.

Learn other languages

There are many scenarios where using something else (like python or go) will give you a huge productivity boost, it will probably run slower and produce bigger binaries but in most of the cases it really doesn’t matter. When making network servers, python, go, node.js (java in some ways) make the job a lot easier.

Makefile

Use standard tools like autoconf, automake or create a simple and easy to maintain Makefile like this one.

IDEs

Spend the time to learn how to use a an IDE like netbeans, CLion or even vim.