Fighting the Norm (and C)

by bhagenlo

If you're like me, and haven't programmed extensively in C before, you might know the struggle.

You know abstractly what to do, somehow manage to do it in C, but then, norminette interferes.

This is for those who are fighting those fights, too.

The first step, of course, is to write good, modular code with well-named functions and data structures.

But often, it isn't enough.

#Oh no: my function's too long

#Shorter loops

int	i;

i = 0;
while (i < 10)
{
        dostuff(i);
        i++;
}

int	i;

i = -1;
while (++i < 10)
        dostuff(i);

I think this is even cleaner than the 'normal' style, as you can see at one glance whether you forgot the incrementation — and not have to look about 10 lines below.

#Hacking together the ternary operator

a = (isneg ? -num : num);

becomes

a = num * (-2 * isneg + 1);

– which is totally legal.

#Going crazy with returns.

It happens regularly that you want to exit from a function inside an if, but you have to do some cleanup beforehand. Unfortunately, this function has a return value (and they often differ from function to function), so we can't just easily handle that situation.

Original code:

while (true)
{
        if (somecondition() == true)
        {
                free_stuff();
                return (1);
        }
        ...
}
return (0);

I, until recently, refactored that to the following:


int	free_stuff1(void)
{
        free_stuff();
        return (1);
}

int	somefun(void)
{
        while (true)
        {
                if (somecondition() == true)
                        return (free_stuff1());
                ...
        }
        return (0);
}

But you can also just do this:

while (true)
{
        if (somecondition() == true)
                return (free_stuff(), 1);
        ...
}
return (0);

This return uses the comma operator, which is a different beast than the comma seperator used almost anywhere else.
It evaluates both expressions, but discards the value of the first — which is exactly what we want here.

#Oh no: I'm declaring too many variables

#Using an array

Well, for that — provided the arguments are of the same length – you can just use an array.

This way,

myfun.c


void	myfun(void)
{
        int	i;
        int	j;
        int	wordcount;
        int	charcount;
        int	word_started;
        int	x;
        int	y;
}

becomes

myfun.c


void	myfun(void)
{
        int	vars[7];

        vars = populate_vars();
}

But, in my opinion, there's a better option.

#Usign structs

You can just declare a struct.

myproject.h


typedef struct s_myfun_struct
{
        int	i;
        int	j;
        int	wordcount;
        int	charcount;
        int	word_started;
        int	x;
        int	y;
}	t_mf;

With that, myfun becomes:

myfun.c


void	myfun(void)
{
        t_mf	mf_struct;

        mf_struct = (t_mf){.i = 0, .j = 0,
        .wordcount = 0, .charcount = 0, .word_started = 0,
        .x = 0, .y = 0};
}

Feel free to omit the member names:

mf_struct = (t_mf){0, 0, 0, 0, 0, 0, 0};

Or, if you really want to initialize every member to zero, you can even use:

mf_struct = (t_mf){};

#Oh no: I am using too many arguments to my function

Another case for structs.

utils.c


void	super_general_fun(int a, int b, char *s1, char *s2, bool is_valid, bool is_upper, int *n)
{
        ...
}

becomes

myproject.h


typedef struct s_super_general_struct
{
        int		a;
        int		b;
        char	*s1;
        char	*s2;
        bool	is_valid;
        bool	is_upper;
        int		*n;
}	t_sg;

utils.c


void	super_general_fun(t_sg s)
{
        ...
}

#Adding some C++ flavor

#Using getter and setter functions

In larger projects like minishell, the need to access different structs from many functions may arise, and constantly passing them by reference to every function can be tedious. Some might think, "Then I'll just use a global struct to access them." However, this is forbidden by the norm.
To still be able to access a struct, you can use a concept from C++ in a slightly C-adjusted manner. Having a static pointer to a variable within a getter function can help with this. Here is a minimal example for an int variable:

default_getter.c

int	*default_getter(void)
{
        static int	status;

        return (&status);
}

default_setter.c

void	default_setter(int new_status)
{
        int	*status;

        status = default_getter();
        *status = new_status;
}

#Pointers

← Writing Exams

Found something to improve? Edit this page on Github!

Explainers →