Best practices in C

C might be one of the most demanding programming language in terms of rigor.

In order for our code to run smoothly with no risk of seeing that pesky segmentation fault we will have to follow some simple rules that will ensure everything is under controle.

I will share in this article some simple and well known rules which should always be used when coding in C.

Verify your syscalls return codes

This is perhaps the most annoying aspect of C but also the most important one.

In C, everytime you call a system function, it has the potential to fail due to various factors. The best example is malloc, everytime you call this function, it’s return value should be verified. Here’s a simple example:

#include <stdlib.h>
#include <unistd.h>

int			main(int ac, char *av[])
{
	char	*str;

	if ((str = malloc(sizeof(char) * 4)) == NULL)
		return (1);
	str[0] = 'a';
	str[1] = 'b';
	str[2] = 'c';
	str[3] = '\n';
	write(STDOUT_FILENO, str, 4);
	free(str);
	return (0);
}

As you probably noticed in the provided code,  we check the return value of malloc in order to see if it was successful and returns appropriately.

Note that we could have checked the return value of write too, even though it is not mandatory for our program to run properly. It is up to you to decide if you should exit or not when you encounter this situation, always ask yourself if having an error here would break your program.

Protect your headers

This is a very important thing to do, it will prevent errors due to double or recursive inclusion, here’s an example of unprotected code:

#include "b.h"

int	func_a();
int	func_b();
#include "a.h"
#include "b.h"

int main()
{
	return (0);
}

Trying to compile this code would result in b.h being included twice. Which would cause an error. In order to avoid this we have to protect our headers as follows:

#ifndef _A_H_
# define _A_H_

# include "b.h"

int	func_a();

#endif /* _A_H_ */
#ifndef _B_H_
# define _B_H_

int	func_b();

#endif /* _B_H_ */

This is a very simple thing to do and it will save you lot of mind buggling errors.

If you want to learn a bit more about how this works, take a look at conditional compiling.

Try to simulate objects

C isn’t an object oriented language but that doesn’t mean we can’t design our code according to OOP.

When you use structs to create objects, try to always implement a constructor and a destructor. As for the naming of these functions, it is up to your liking but a common way to do it is using “name of struct”_”name of func”.

Here’s an example of a very simple object simulated in C:

#ifndef _CAT_H_
# define _CAT_H_

typedef struct  s_cat
{
    char        *name;
    int         age;
}               t_cat;

t_cat           *cat_create(char *, int);
void            cat_show(t_cat *);
void            cat_destroy(t_cat *);

#endif /* _CAT_H_ */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cat.h"

int         main()
{
    t_cat   *cat;

    if ((cat = cat_create("meow", 10)) == NULL)
        return (1);
    cat_show(cat);
    cat_destroy(cat);
    return (0);
}

t_cat           *cat_create(char *name, int age)
{
    t_cat       *cat;

    if ((cat = malloc(sizeof(t_cat))) == NULL)
        return (NULL);
    if ((cat->name = strdup(name)) == NULL)
    {
        free(cat);
        return (NULL);
    }
    cat->age = age;
    return (cat);
}

void            cat_show(t_cat *cat)
{
    printf("Name: %s, age: %d\n", cat->name, cat->age);
}

void            cat_destroy(t_cat *cat)
{
    free(cat->name);
    free(cat);
}

(Notice how every syscall is verified ?)

Using this way of designing your structs will limit the amount of possible bugs (if you remember to check your syscalls of course) and will make your code much more readable and understandable.

Don’t be afraid to create more functions

Something I noticed by helping fellow students is that a lot of them are having bugs because of the way their functions are designed. Most of the time they will have their functions do multiple things at once, this is a rather bad idea because not only will it make the code less readable but it will also limit reusability.

Your functions should execute simple tasks and most importantly only have one purpose. For example, if you have a function making  a string uppercase and then reverting it. Create a functions which makes the string uppercase and another one which will revert it. Of course it would be a good idea to create a third one which would call encapsulate both of these function in order to achieve your goal.

Conclusion

Of course there are plenty more important design patterns and best practices, but i decided to focus on some simple elementary ones that will hopefully allow you to improve and write better code.

So now, go code something, and most importantly, have fun doing it!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s