🏁Header files

This page will describe some of the concepts used in header files

What is a header file ?

A header file is a file with extension .h which contains C function declarations and macro definitions to be shared between several source files.

Note that a header file contains only function declaration and not the definitions of these functions.

void    ft_putchar(char c);
void    ft_putstr(char *str);
size_t    ft_strlen(char *str);

The declaration is only the prototype of the function. When we compile a C source file that includes a header file, these prototypes tell our source file that the functions declared in the header exist, what are their parameters, and what type of data do they return.

The definitions of these functions can be written in as many C source files as you want to.

i.e. the LIBFT project has one header file that contains the declaration of all the functions, but each function is written in a separate file so that the code remains readable and we include the same header file in all of them.

Header protection

There's a lot of chance that you had some problems with your header files when trying to pass the Norm, for something called "HEADER PROT" (or something like that) but what the f*** is that ?

Header protection is the code that wraps your entire header file :

header_example.h
#ifndef HEADER_EXAMPLE_H
# define HEADER_EXAMPLE_H

// your functions prototypes go here
// your structures / typedefs go here as well

#endif

Before telling you exactly what this code do, I have to show you what a source file in which you include a header looks like when you compile it.

Let's keep LIBFT as an example

libft.h
size_t ft_strlen(char *s);
int ft_isdigit(int c);
ft_strlen.c
// This is what the code looks like before compilation

#include "libft.h"

size_t ft_strlen(char *s)
{
    size_t i;
    
    i = 0;
    while (s[i])
        i++;
    return (i);
}
ft_isdigit.c
// This is what the code looks like before compilation

#include "libft.h"

int ft_isdigit(int c)
{
    return (c >= 48 && c <= 57);
}

Above, I wrote 3 files, one header, and two source files that both include the same header file. Above is what the files look like before compilation, below, the same files after compilation.

ft_strlen.c
size_t ft_strlen(char *s);
int ft_isdigit(int c);

size_t ft_strlen(char *s)
{
    size_t i;
    
    i = 0;
    while (s[i])
        i++;
    return (i);
}
ft_isdigit.c
size_t ft_strlen(char *s);
int ft_isdigit(int c);

int ft_isdigit(int c)
{
    return (c >= 48 && c <= 57);
}

As you can see, the whole content of the header file was pasted at the top of each file (where we put the include statement before compilation). But since both our files will be compiled together in the same library, we don't have to include it in both files (or as many files as we included the header in), only including it in one of our source file is enough. That's the whole point of header protection, including headers only once.

If I write the same header with header protection like so :

libft.h
#ifndef LIBFT_H
# define LIBFT_H

size_t ft_strlen(char *s);
int ft_isdigit(int c);

#endif

Our source files after compilation will look like this :

ft_strlen.c
size_t ft_strlen(char *s);
int ft_isdigit(int c);

size_t ft_strlen(char *s)
{
    size_t i;
    
    i = 0;
    while (s[i])
        i++;
    return (i);
}
ft_isdigit.c
int ft_isdigit(int c)
{
    return (c >= 48 && c <= 57);
}

Why was the header included only once this time ?

The header protection has a weird syntax, let's go over it line by line.

#ifndef LIBFT_H

This first line means if not defined LIBFT_H, the preprocessor checks if the macro LIBFT_H is defined or not. If it is not defined, it takes into account everything that is between this statement and the last statement of our header file.

# define LIBFT_H

This is a macro definition, as you would do for any other values, the name of this macro must follow a specific format, check the Norm to find what it should be, but remember that the name of the file is libft.h and the macro for example_header.h is EXAMPLE_HEADER_H, I'm sure you can figure it out.

Why do we define it ? So that next time we include the header file, this macro will be defined, so everything between ifndef and endif will be skipped as if it was never there. This way we only include the content of the header file at the top of one of all the source files.

And finally, the last statement of the header protection:

#endif

This statement ends the if we opened at the top of the header file with ifndef.

TLDR

Header protection is a wrapper around all our header file content to include the content of this header only in one of the source file and not in every file.

The syntax of the header protection is the following:

#ifndef FILENAME_H
# define FILENAME_H

// other includes (stdio.h, stdlib.h, etc)
// your function declarations
// structrures declarations

#endif

Macros

A macro in a header file is what you declare with the #define statement.

You could, for example, define a macro called WIN_H that holds the height of the window you want to create, or BUFFER_SIZE to tell the program how much characters it has to read every time you call the function read() (that's what you have to do for get_next_line).

Here are these examples:

example_header.h
#ifndef EXAMPLE_HEADER_H
# define EXAMPLE_HEADER_H
# define WIN_H 720
# define WIN_W 1280
# define BUFFER_SIZE 42

char *get_next_line(int fd);

#endif

You could have multiple screen resolutions pre-defined and choose one or another by commenting a define statement or not like so:

example_header.h
#ifndef EXAMPLE_HEADER_H
# define EXAMPLE_HEADER_H
//# define 720P
# define 360P
# define BUFFER SIZE 42

# ifdef 720P
#  define WIN_W 1280
#  define WIN_H 720
# endif

# ifdef 360P
#  define WIN_W 640
#  define WIN_H 360
# endif

# include <stdio.h>

char *get_next_line(int fd);

#endif

The above header file will set the WIN_W macro to 640 and the WIN_H macro to 360.

What are macros used for ?

A defined macro can be used in every source files where the header is included.

To use it, it is really simple, if we take the example of a window size we could write this:

main.c
#include "example_header.h"

int main(void)
{
    printf("Window Height: %d\n", WIN_H);
    printf("Window Width : %d\n", WIN_W);
    return (0);
}

What will happen when you compile the above source file is that the preprocessor will replace the macros name with the values you set in the header file.

In this example WIN_H will be replaced by 360 and WIN_W by 640. So the expected result of running this program will be this:

$> ./a.out
$> Window Height: 360
$> Window Width : 640
$>

Last updated