Back at my home university in Berlin, as well as here in Oslo and i assume also at other universities, students learn mainly Java. They may have a single C course about the language, but that is all. Not a big problem, because all other courses are featuring Java as language as well. Until at the end of their studies, sometimes in the master degree studies, they encounter a course that is focused on high performance highly optimized C code, with a lecturer that expects everyone to be well familiar with C.
So i am providing some tips for coding in C. They are not directly targeted for beginners, more for people coming from a higher language, that have to deal with C all of a sudden. But they may be still helpful for programming beginners.
Disclaimer: Other people may have other opinions about my points. I am not (yet) a C Expert, but this is my current “good advice” opinion. I am open for a discussion, so that i may learn and improve as well.
1. Use objects
You can still reassemble something like objects in C. Simply have a header and C file for your object type. In the header define a struct for the object type, that will hold the data of the object. All object methods are defined in its C file and must have a pointer of the object type as parameter. Thereby you can access the data of the object instance. Only methods declared in the header file will be visible to other files that include the header file, so you even have private and public methods. However the object data is all public (although there are also ways to make some variables private but that requires a little bit more afford).
Foo.h
typedef struct _FOO
{
int Bar;
} FOO, * PFOO;
void DoSomethingWithFoo(PFOO this, int Value);
Foo.c
#include "Foo.h"
static void DoPrivateThingsWithFoo(PFOO this);
void DoSomethingWithFoo(PFOO this, int Value)
{
DoPrivateThingsWithFoo(this);
this->Bar = Value;
}
static void DoPrivateThingsWithFoo(PFOO this)
{
//Do private
}
2. Use Initialize and Finalize/Clean-Up functions for your objects
If you use objects, every object should have at least an Init-Method and if it deals with dynamic memory, contains another object, or must perform any other form of clean up, should have a Finalize/Clean-Up method too. These are basically the objects Constructor and Destructor, although you have to call them yourself (But that is C, you are in charge of everything). The Init method is important to initialize your variables, so they won’t contain garbage. Remember C does not initialize your variables to default values.
3. Dynamic Memory should have a parent object or should never survive its creating scope
When creating dynamic memory through malloc
or calloc
, that memory should be either freed in the same scope, or should have a parent object, that is in charge of freeing it. First you won’t create memory leaks so easily this way, because memory is either freed right away, or you have an object, that will free it for you when it is destroyed. Secondly this may help you keeping track of you dynamic memory and not getting confused by passing pointers through your program.
In addition you should not pass dynamic memory up your call stack, instead pass it downwards. Otherwise the risk to loose it is quite high. One exception would be an “Allocate” function, that will have to do some calculations for the allocation, but then the calling function is the scope that has to free it or has to assign it to a parent object.
4. Set invalid pointers to NULL, especially after freeing
More about pointers. Pointers that currently do not point to anything valid should always be set to NULL. This way you can check if they are valid and prevent other errors, like segfaults and double freeing. This goes especially for freeing dynamic memory. Set the pointer right after it to NULL, so you are not able to access it afterwards.
5. Use the <stdint.h>
header for integer types
The <stdint.h>
header defines several integer types with fixed lengths. The problem with the basic types are that their size depends on the compiler and the system. So other system and/or other compiler and your long is now 4 bytes instead of 8 bytes. Especially when dealing with 32 Bit vs 64 Bit this may be a problem.
So in order to have fixed size integers, the <stdint.h>
header defines the following types:
int8_t, int16_t, int32_t, int64_t
uint8_t, uint16_t, uint32_t, uint64_t
The number is the size of the type in bits, and that is regardless of compiler or system.
6. Use the <stdbool.h>
header for the bool type
C does not come with a native boolean type. The <stdbool.h>
header defines a bool
type, as well as the keywords true
and false
. The advantage of using this header instead of abusing a unsigned char is, that this bool
type is somehow partly native and does take care of integer overflow.
7. Use the size_t
and ptrdiff_t
types for anything regarding memory and pointers.
size_t
is an unsigned integral type that is used for sizes of objects and memory. Do NOT ever use int to store the size of a type, object or memory. The two important functions that use this type are sizeof, which will return the size of the type bytes, and malloc, which wants to know how many bytes shall be allocated.
ptrdiff_t,
as its name suggests, is used when you want to store the difference of two pointers. Here again, do NOT ever use int to store a pointer difference, though most compilers are going to throw a warning.
Both types are guaranteed to have the appropriate size to store those information.
8. Pointers are increased by their type size
So this is a little more advanced but still somehow important. It is totally valid to increment and decrement pointers. This is especially useful to iterate over an array. But be aware that an increment/decrement of a pointer takes its type size into account. This means incrementing a pointer will not move it by one byte. Instead it will move it by X bytes, where X is the size of the type it is pointing to. So it will point to the next object next to the previous one. This is the reason why pointer arithmetic on void
pointers are not valid. The type size is not known, therefore the compiler does not know how far it should move the pointer.
Hope this will eventually help someone. If you have something to add or complain, feel free to leave a comment.