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