GERBELOTBARILLON.COM

Parce qu'il faut toujours un commencement...

0. Introduction


Il existe un très grand nombre de langages de programmation mais il y en a peu qui ont une histoire et des potentialités telles que le langage C. Il a servi de socle au développement de quasiment tous les autres langages ou systèmes informatiques existants et dispose de qualités énormes et d'inconvénients nombreux. Malgré tout, c'est un des langages les plus homogènes mélangeant rapidité, efficacité et simplicité. Il ne dispose pas du paradigme objet (ce qui n'est pas un défaut, bien au contraire) mais est un langage impératif très proche du système qui le fait fonctionner, ce qui lui permet d'utiliser absolument toutes les ressources du système pour obtenir les plus grandes performances possibles.

Historiquement apparu en 1972 pour servir de base à la réécriture du système UNIX, le langage C est un dérivé du langage B, lui-même dérivé du BCPL. Ken Thompson et Dennis Ritchie ont contribué chacun à cette évolution technologique de C par rapport à son prédécesseur, suffisamment pour que l'on puisse l'appeler C (et non B2 ou B-bis). Le troisième membre du groupe, Brian Kernighan, venu plus tard, permettra de démocratiser grandement ce langage en écrivant une véritable bible du langage en 1978. On appellera d'ailleurs le C "K&R".

Le langage C est LE langage de programmation système par excellence, même si aujourd'hui il se trouve un peu poussé sur la touche suite à l'apparition de langages dits plus modernes tels que Rust (Fondation Mozilla, syntaxe compliquée) ou Go (par Google). Cela reste un langage de coeur, adoré par les puristes et, grâce à l'environnement Linux/GCC, toujours d'actualité tout de même.

Pour faire du C vous avez besoin d'un compilateur et d'un éditeur de texte. Pas plus. Vous pouvez utiliser un environnement de développement complet de type Code::Blocks, Visual Studio ou autre mais personnellement, un IDE pour du C est trop contraignant. Choisissez un éditeur de texte du genre notepad++, sublime, visual studio code, bracketts, notepad3 ou emacs pour saisir vos lignes de code C. N'oubliez-pas de faire afficher les extensions des fichiers pour que vos éditeurs de texte enregistrent avec l'extension *.c et pas *.c.txt

Avec l'éditeur de texte vous avez besoin du compilateur C qui vous donnera par la suite le fichier exécutable par votre système. Le plus simple est de prendre le compilateur GCC disponible de deux façons :

Lors de l'installation, n'oubliez pas de vérifier que votre PATH pointe bien vers le dossier bin de GCC pour trouver les exécutables gcc, gdb, ...

1. Premier programme


Un programme en langage C est un fichier texte comportant des instructions spécifiques permettant d'obtenir un fichier compréhensible par le système d'exploitation qui l'exécute. Ce fichier va être transformé en programme exécutable en suivant plusieurs étapes qui vont être la compilation et l'édition de liens.

Un programme en langage C peut être écrit avec n'importe quel éditeur de texte du moment que celui-ci ne rajoute pas de caractères étranges dans le fichier. Il existe un nombre important d'éditeurs sur tous types de systèmes existants car la quasi totalité des systèmes d'informations disposent d'un moyen pour créer un programme en langage C. C'est ce que l'on appelle l'universalité du langage C. Il n'y a tout de même pas de miracle en ce bas monde. Cela est simplement rendu possible par l'existence de programmes intemporels tels que le compilateur C GCC.

Considérons le programme suivant qui ne va faire qu'afficher le fameux texte 'hello world!' sur la console. Nous le nommerons hello.c

#include <stdio.h>

int main(int argc, char **argv) {
    printf("Hello World!\n");
    return 0;
}

Analyse du programme

Un programme en langage C est composé de un ou plusieurs fichiers source, possédant une extension .c. Ce fichier contient une série d'instructions et de fonctions. La fonction principale est la fonction nommée main() qui est obligatoirement existante et constitue le point d'entrée du programme. Une fonction est délimitée par un bloc d'instructions encadrées par des accolades ouvrante et fermante. La définition de la fonction - son prototype - contient un type renvoyé (ici c'est un type int qui sera renvoyé)

Le corps de la fonction contient simplement un appel à la fonction print() déclarée dans une bibliothèque nommée stdio.h et incluse en début de programme. Les fichiers include contiennent généralement des déclarations de fonctions externes, des structures de données et autres éléments pouvant être partagés entre plusieurs fichiers source du programme. C'est le cas de la bibliothèque stdio.h qui définit un ensemble de prototypes de fonctions d'entrée/sortie standard.

En fin de programme, la fonction return() termine l'exécution en renvoyant un code de fin au shell appelant. Ici nous renvoyons le valeur 0 pour signaler, par convention, qu'il n'y a pas d'erreur.

Compilation du programme

Compiler un programme va consister en la transformation des lignes de codes écrites dans les fichiers source et include en code assembleur, puis en langage machine. Tous les différents fichiers résultants de ces étapes vont être liés entre-eux pour générer le programme exécutable que vous pouvez ensuite lancer par un double-clic sur une icône ou bien depuis une commande shell / powershell / ms-dos (rayer les mentions inutiles ^_^).

De par la structure des systèmes d'exploitation et des plates-formes matérielles, toutes différentes, il est obligatoire de recompiler un programme source pour le faire fonctionner sur la machine qui n'était pas sa destination initiale. Par exemple, si vous écrivez le programme donné en exemple sur un environnement Windows, le fichier binaire final obtenu ne pourra être utilisé que sur une plateforme Windows. Pour le faire fonctionner sur Mac, Linux, Amiga (Atari aussi, il ne faut pas faire de jaloux), il vous faudra repasser par les étapes de compilation pour obtenir un programme compatible avec l'architecture de la machine cible.

Les étapes de la compilation

Le compilateur GCC est disponible sur la quasi totalité des systèmes existants et c'est donc lui que nous utiliserons pour nos programmes C. Lorsque l'on lance la compilation depuis un shell, simplement en saisissant la commande gcc hello.c, toutes les étapes sont réalisées de manière transparente et l'utilisateur obtient à la fin un fichier nommé a.exe sous Windows ou bien a.out sur les systèmes à base d'Unix. Vous pouvez le lancer et vous obtiendrez un magnifique Hello World sur votre affichage.

La commande gcc dispose d'un grand nombre de paramètres mais les plus usuels sont les suivants :

Options de GCC
-c Force la création du fichier objet uniquement (pas d'édition des liens)
> gcc -c hello.c va générer le fichier objet hello.o.
Utile lorsque votre programme principal doit rassembler plusieurs fichiers objets à fournir à l'éditeur de liens (le linkeur).
-E Lance le préprocesseur uniquement
> gcc -E hello.c retourne le résultat sur la sortie standard.
Permet éventuellement de s'assurer que les conditions ou macros définies dans le fichier source seront correctement traitées. Voici un exemple de résultat avec le fichier hello.c :

# 960 "C:/Mingw/x86_64-w64-mingw32/include/stdio.h" 2 3

# 1 "C:/Mingw/x86_64-w64-mingw32/include/_mingw_print_pop.h" 1 3
# 962 "C:/Mingw/x86_64-w64-mingw32/include/stdio.h" 2 3
# 2 "hello.c" 2

int main(int argc, char **argv)
{
printf("Hello world.\n");
return 0;
}
Sur le résultat j'ai volontairement occulté l'affichage des 960 ligne du fichier include stdio.h Pour rapidement décrypter les informations, on voit que nous avons le numéro de la ligne traitée au début (960 lignes dans le fichier stdio.h) puis à partir de la ligne 962 commence l'affichage du fichier source hello.c.
-S Permet de créer le fichier assembleur qui sera pris en charge par le linkeur (l'éditeur de liens).
> gcc -S hello.c va générer le fichier hello.s. Si vous faites un > more hello.s vous pourrez voir de jolies lignes d'assembleur qui vont varier selon la plateforme sur laquelle la commande est exécutée.
-v Active simplement le mode verbose qui va demander à gcc d'afficher toutes les informations des différentes étapes de la compilation.
> gcc -v hello.c
Attendez-vous à avoir une masse d'informations importante à traiter...
-o <exe filename> Autre option très importante de GCC puisque cela va demander la création d'un exécutable final portant le nom souhaité.
> gcc hello.c -o hello.exe va générer le fichier hello.exe au lieu du fichier a.exe de la commande simple gcc hello.c.
-02/-03
-Wall
Différents niveaux d'optimisation du code produit par le compilateur. La commande Wall va être très exigeante sur le strict respect des règles d'écriture du langage C.
-I Indique dans quel dossier aller spécifiquement chercher un fichier d'inclusion
-L Indique à l'éditeur de liens où trouver les bibliothèques requises pour l'assemblage final des objets en code exécutable.
Nous verrons plus loin dans les explications comment générer un fichier Makefile qui prendra en charge la compilation avec plusieurs fichiers sources. Cela rendra cette génération de fichier exécutable plus souple, plus rapide et surtout saura ne recompiler que ce qui a été modifié.

2. Choisir un éditeur de code


Le langage C reste très permissif quant à la structuration de son code, du moment que les fins d'instrauctions sont bien identifées par un ';' et les blocs de code par '{' et '}', le reste est laissé à votre libre choix. Vous pouvez vous orienter vers une multitude d'éditeurs de code, d'IDE, voire même taper du code avec le bloc notes. Actuellement ma préférence reste sur un éditeur de texte qui ne pèse pas trop dans le système, qui est open source et qui est multi plateformes : le vénérable emacs.

Cet éditeur est disponible partout, sur quasiment tout support, est très versatile, totalement configurable et vous permettra d'éditer tout type de document. Il reste simple même si, pour le maîtriser complètement, il faudra avoir une mémoire d'éléphant ou une bonne cheatsheet car les raccourcis sont très nombreux et pas forcément faciles à se rappeler.

Emacs fonctionne avec des buffers (pour le code) et des mini-buffers (pour les commandes). La documentation est assez bien faite pour comprendre comment passer de l'un à l'autre et je ne me substituerai pas à la documentation à ce sujet. Mémo : RTFM...

Je vous laisse quelques liens à consulter :

Syntaxe du langage C


Contrairement à d'autres langages, le C n'impose aucune structuration de son code pour être valide. Il faut juste respecter les délimiations de fin d'instruction par ';' et les accolades pour les blocs de code. Le reste est laissé à votre appréciation.

Ceci étant dit, cela n'en reste pas moins un langage écrit et donc un peu de structuration, de mise en forme, sera nécessaire pour le rendre lisible, maintenable et extensible. Il est toujours plus facile de lire un texte avec des paragraphes que si ce même texte était saisi sans séparation. Il en est de même pour un langage. Pour que cela soit compréhensible il faut pouvoir le lire. Je fais une apparté sur la terminologie obfuscated code pour ceux qui veulent avoir mal au crâne en essayant de comprendre ce que fait le programme que vous avez sous les yeux... Cela se passe ici -> https://www.ioccc.org/ sous vous voulez en savoir plus.

Si l'on reprend le fil de la discussion, vous voilà devant un éditeur et vous souhaitez éditer un programme en langage C. Si vous êtes avec Emacs, commencez par activer le mode C (Alt-x c-mode). Si la coloration syntaxique ne s'active pas, faites-le avec Alt-x global-font-lock-mode.

Les identificateurs

Les identificateurs de variables peuvent être composés des lettres [a-z, A-Z, 0-9, _] mais ne doivent pas commencer par un chiffre. Comme pour la plupart des langages, C fait la distinction entre majuscule et minuscule. Ainsi les variables hello et Hello sont différentes.

Les instructions

Il existe des instructions simples sur une ligne, terminées par un point virgule ';' et d'autres qui tiennent sur plusieurs lignes, encadrées par des accolades ouvrante et fermante.

print("instruction simple sur une seule ligne.\n");
            
{
    print("ligne 1\n");
    print("ligne 2\n");
    print("bloc de code.\n");
}

Commentaires

Un programme n'étant pas fait forcément pour soi ou pouvant être repris plusieurs années après avoir été écrit, il est important de commenter son code correctement. Le langage C permet de déclarer des commentaires sur une ligne en utilisant deux caractères slash '//' ou sur un block en encadrant ce block par /* ... */.


// Commentaire sur une ligne

/*
 Je suis un commentaire qui s'étire
 sur plusieurs lignes */

Les énumérations

L'utilisation de la syntaxe d'énumération permet de déclarer des constantes plus simplement qu'avec une série de directives de précompilateur #define. Nous avons une syntaxe simple enum nom_de_l_enumeration {constante1 [, constante2, ...]}.

// exemple de déclaration de constantes par préprocesseur
#define MOIS 12
#define JOURS 7

// Exemple de déclaration de constantes par énumération
enum jours {LUN = 1, MAR, MER, JEU, VEN, SAM, DIM};

printf("Nombre de Jours : %d\n", JOURS);
printf("Mardi est le jour numéro %d\n", MAR);

enum jours j = DIM;
printf("j = %d\n", j);
Par défaut, l'énumération fait commencer l'affectation des constantes à la valeur 0, mais en affectant une valeur entière (comme dans l'exemple de LUN = 1), cela affecte une valeur précise à une constante. Celles qui suivent sans déclaration explicite de valeur sont simplement incrémentées de 1 à chaque position.

Les types

Le langage C dispose de types de base : entiers, flottants et caractères. Ces différents types peuvent être signés ou non signés. Le signe est caractérisé par un bit de poids fort mis à 0 pour positif et à 1 pour négatif. Chaque type va être en mesure d'accepter des valeurs dans un intervalle donné. Comme cela peut être différent entre les systèmes, le fichier d'inclusion limits.h contient la définition de ces types et leur intervalle respectif. Cependant vous pouvez utiliser la fonction sizeof() du langage C pour trouver cet intervalle, sizeof() renvoyant un nombre d'octets pour un type donné.


#include <stdio.h>

int main(int argc, char *argv[])
{
  printf("char : %d\n", sizeof(char));
  printf("short : %d\n", sizeof(short));
  printf("int : %d\n", sizeof(int));
  printf("long : %d\n", sizeof(long));
  printf("long int : %ld\n", sizeof(long int));
  printf("long long int : %ld\n", sizeof(long long int));
  printf("void * : %d\n", sizeof(void *));
  printf("intptr_t : %ld\n", sizeof(intptr_t));

  printf("float : %d\n", sizeof(float));
  printf("double : %ld\n", sizeof(double));
  printf("long double : %ld\n", sizeof(long double));
}
Sur le système Windows avec lequel ce programme a été exécuté, on obtient les valeurs suivantes en retour :
char : 1
short : 2
int : 4
long : 4
long int : 4
long long int : 8
void * : 8
intptr_t : 8
float : 4
double : 8
long double : 16
Valeurs entières
Chaque valeur est exprimée en nombre d'octets et donc un short est par exemple sur 16 bits et un entier (valeur par défaut) est sur 32 bits. Pour des valeurs beaucoup plus grandes il existe les long long int qui occupent 8 octets. Toutes ces types entiers peuvent être non signés (uniquement positifs). La table ci-après reprend ces caractéristiques.
Type Notation Intervalle signé Intervalle non signé
Entier 8 bits char -128 ... 127 0 ... 255
Entier 16 bits short -32 768 ... 32 767 0 ... 65 535
Entier 32 bits int (ou long) -2 147 483 648 ... 2 147 483 647 0 ... 4 294 967 295
Entier 64 bits long long int -9 223 372 036 854 775 808 ... 9 223 372 036 854 775 807 0 ... 18 446 744 073 709 551 615
Valeurs flottantes
En mathématiques, ces valeurs sont celles qui appartiennent à l'ensemble des Réels. Elles sont exprimées selon une mantisse et un exposant. Ainsi un float est une simple précision, un double équivaut à un réel en double précision et un long double une valeur réelle en quadruple précision. Tout comme pour les entiers, les flottants encadrent des intervalles variables selon les plateformes. Le fichier d'inclusion floats.h détermine ces valeurs minimales et maximales.
Type Mantisse Exposant Min Max
float 24 10^-7 10^-38 3 * 10^38
double 53 2 * 10^-16 2 * 10^-308 10^308
long double 64 10^-19 10^-4931 10^4932
Les caractères
En C les caractères sont représentés par des valeurs ASCII de 0 à 255. La table standard définit les éléments entre 32 et 126 comme étant ceux imprimables. Par exemple la valeur ASCII 65 correspond à la lettre 'A'. Et la valeur 48 correspond au caractère '0'.

Les tableaux

Les tableaux sont un moyen pour stocker un grand nombre d'éléments du même type dans un espace contigü. L'accès aux éléments s'effectue par un index débutant à 0.

int tab[3]; // déclare un tableau de 3 entiers
printf("%d\n", tab[0]); // Affiche la première valeur, indice 0
printf("%d\n", tab[2]); // Affiche la dernière valeur, indice nombre d'éléments - 1

Un tableau peut disposer de plusieurs dimensions que l'on déclare simplement en ajoutant des indices entre crochets :

int tab[3][3]; // Déclare un tableau de 3 lignes et 3 colonnes
printf("%d\n", tab[0][0]); // Affiche le premier élément, ligne 0, colonne 0

Un tableau peut s'initialiser en une seule affectation. Par exemple :

int x[5] = { 1, 2, 3, 4, 5 };
. Si le tableau est composé par des cellules contenant des structures il est également possible de mixer l'ensemble.
struct point {
    int x;
    int y;
};

struct triangle {
    struct point sommet[3];
};

/* Déclaration et affectation en même temps */
struct triangle trg = {
    {1, 1},
    {2, 2},
    {3, 3}
};

Les structures

Une structure est une agrégation de plusieurs types dans un même bloc variable. Cela permet d'obtenir un ensemble hétérogène de valeurs dans un bloc pouvant ensuite être, par exemple, utilisé dans un tableau. Pour déclarer une structure on utilise le mot-clé struct avec un identifiant pour référencer la structure. Dans l'exemple ci-après nous déclarons une structure personne permettant de manipuler un caractère et un entier. L'accès aux éléments de la structure s'effectue par une notation pointée ('.') reprenant le nom de la variable structurée + '.' + nom du champ.

#include <stdio.h>

struct personne {
  char initiale;
  int age;
};

int main(int argc, char **argv)
{
  struct personne johndoe;

  /* Initialisation des champs les uns à la suite des autres */
  johndoe.initiale = 'J';
  johndoe.age = 42;

  /* Autre méthode : initialisation en bloc */
  struct personne janedoe = { 'J', '40' };

  /* Dernière méthode : en fixant les noms des champs avec leur valeur */
  janedoe = { .initiale = 'J', .age = 40 };

  printf("Initiale : %c\n", johndoe.initiale);
  printf("Age: %d\n", johndoe.age);

  return 0;
}
En fin d'exécution, nous avons ces valeurs imprimées sur la sortie standard :
Initiale : J
Age: 42

Les pointeurs

Nous aurons l'occasion de revenir sur cette notion mais sachez qu'un pointeur est un moyen d'accéder à un espace mémoire par son adresse. C'est généralement LA partie compliquée (ou considérée comme telle) dans la programmation en langage C. Mais c'est tellement utilisé partout que c'est un passage obligé.

La manipulation des pointeurs est une source de problèmes récurrents car cela touche directement la gestion de la mémoire de votre programme. Un plantage est vite arrivé, le C étant assez permissif ^_^.

Un pointeur est une variable dont la valeur contient une adresse mémoire. Voilà l'essentiel de ce qu'il faut retenir...

Pour déclarer un pointeur, nous utilisons '*' devant le nom de la variable. Cela va affecter une valeur aléatoire dans cette variable (adresse mémoire) et vous ne devez pas l'utiliser directement sous peine de plantage.

int *pointeur; // déclare une variable de type pointeur non initialisée
Il est généralement fortement conseillé d'initialiser à NULL ce type de variable pour éviter toute mauvaise utilisation.
int *pointeur = NULL; // NULL est un mot-clé du language équivalent à 0
Dorénavant vous disposez d'une variable dont le rôle sera de contenir l'adresse d'une autre variable. Pour affecter une adresse de variable, il faut utiliser la notation '&'
int age = 42; // valeur entière
int *pointeur = &age; // affecte l'adresse de la variable age au pointeur valeur ('&')
printf("valeur pointée : %d\n", *pointeur); // affiche la valeur de la variable pointée ('*')
printf("addresse de la variable : %d\n", pointeur); // affiche l'adresse de la variable pointée

En conclusion ce qu'il faut retenir absolument :
Pour une variable simple (si notre variable se nomme age) :

Pour une variable de type pointeur (si notre variable se nomme pointeur) :

Les fonctions

Une fonction est un bloc de code qui pourra être appelé plusieurs fois et qui évitera d'écrire plusieurs fois les mêmes lignes un peu partout dans le programme. Une fonction dispose d'un type de retour et de paramètres, le tout inscrit dans un bloc délimité par des accolades { et }.

Le prototype d'une fonction est le suivant : <type de retour> nom_de_la_fonction([paramètres, ...])

Une fonction doit renvoyer une valeur du type déclaré dans son prototype, sauf si le type de retour est void qui fait alors passer une fonction pour une procédure (pour ceux qui font la distinction, drogués au Turbo Pascal par exemple ^_^). Cette valeur de retour est d'un type unique et peut être un type atomique (entier, flottant, chaine de caractères...) ou bien un type composé (les structures). C'est l'appel à la commande return() qui effectuera ce retour d'information au code appelant.

int addition(int a, int b)
{
    return a + b;
}

    printf("L'addition de 40 + 2 donne %d\n", addition(40, 2));

Il existe deux types de passage de paramètres à une fonction :

Par défaut, seuls les tableaux sont nativement transmis en paramètre par référence. Tous les autres types sont passés par valeur.

void f(int x) {
    x = 100;
    printf("Dans f(), x vaut %d\n", x); /* Dans f(), x vaut 100 */
}

int main() {
    int x = 42;
    f(x); /* passage du paramètre par valeur donc copie de la valeur et f() ne modifie pas la valeur de x */
    printf("Dans main(), x vaut %d\n", x); /* Dans main(), x vaut 42 */
    return 0;
}
    

Portée des variables

Nous parlerons de variables globales si elles sont disponibles dans la globalité du programme. Nous appellerons des variables locales si elles sont disponibles dans un bloc d'instructions. Rappel : un bloc est délimité par un ensemble d'accolades { et } (une fonction par exemple).


        #include 

int a = 1, b = 42; // Variables déclarées globalement

int modif_a(int a, int b)
{
  a = a - 1; // Variables locales
  return a;
}

int main(int argc, char **argv)
{
  printf ("a vaut %d\n", a); // a vaut 1

  int s = modif_a(a);
  printf("a vaut maintenant %d\n", a); // a vaut toujours 1
  return 0;
}

Programmation modulaire

Le langage C vous autorise à produire un code réparti entre plusieurs fichiers sources. Cela permettra de séparer certaines fonctions données à réaliser par d'autres services que le vôtre pour les utiliser par la suite par exemple. Tout se passe par l'intermédiaire des commandes du préprocesseur (celles qui commencent par '#') et des prototypes de fonctions.

Dans l'exemple qui va suivre, nous allons déclarer des fonctions dans un fichier operations.c et créer un fichier d'inclusion operations.h qui contiendra les prototypes des fonctions du fichier source.

// Fichier de déclaration des prototypes de fonctions : operations.h

// Utilisation des commandes du préprocesseur pour ne pas inclure plusieurs fois le même fichier en cas d'appels successifs
// Les commandes #ifndef, #define et #endif permettent la compilation conditionnelle de blocs de programmes
#ifndef OPERATIONS_H
#define OPERATIONS_H

// Déclaration des prototypes utilisés dans le fichier source.
// Le corps de ces fonctions sera défini dans le fichier operations.c
int addition(int a, int b);
int soustraction(int a, int b);

#endif
// Fichier du code source contenant les fonctions : operations.c

// Le fichier inclus est à encadrer par des caractères "" pour la bonne raison que les < et > sont utilisés par le préprocesseur
// pour chercher les fichiers à inclure dans un dossier système spécifique (les includes dans gcc par exemple).
// La notation avec des "" permet de définir un chemin relatif du système de fichiers pour accéder à des fichiers que vous auriez créés.
#include "operations.h"

// Ecriture du corps des fonctions déclarées dans le fichier operations.h
int addition(int a, int b) { return a + b; }
int soustraction(int a, int b) { return a - b; }
Lorsque des fonctions sont déclarées dans des fichiers externes, leur prototype doit être déclaré dans un fichier d'inclusion et encadré par des commandes du préprocesseur pour la compilation conditionnelle. Ces fichiers d'inclusion doivent être importés dans chaque fichier source qui fera appel à ces fonctions (comme c'est le cas pour operations.c et le programme principal main.c)
// Programme principal : main.c

#include <stdio.h>
#include "operations.h"

int main(int argc, char **argv)
{
    printf("addition de 40 et 2 = %d\n", addition(40, 2));
    printf("soustraction de 44 et 2 = %d\n", soustraction(44, 2));

    return 0;
}
Un programme modulaire se compile presque comme un programme composé d'un seul fichier source, hormis que nous utilisons un wildcard pour nommer les fichiers à utiliser : gcc *.c -o programme.exe. Pour plus de modularité nous verrons ultérieurement comment créer des fichiers de compilations : les makefiles.

Enfin, si l'on souhaite utiliser une fonction uniquement dans le fichier source qui la définit, il faut simplement ajouter le mot-clé static devant la déclaration de la fonction.

Lire une donnée au clavier

Pour demander une information à l'utilisateur, la fonction

int scanf(const char *format, ...)
sera utilisée. C'est la fonction inverse de printf() qui permet l'affichage sur la console. Le format de la chaine est le même que pour printf() donc et permet de lire des types basiques :
ID Type Description Type d'argument
c Lecture d'un seul caractère dans la saisie, même si plusieurs sont disponibles dans le flux. char *
d Entier décimal, nombre optionnellement précédé d'un signe + ou -. int *
e, E, f, g, G Nombre à virgule flottante : nombre contenant un point décimal, avec ou non un symbole + ou - devant et potentiellement suivi par un symbole e ou E suivi lui-même d'un nombre décimal. Par exemple -10e42 ou 42.67. float *
o Nombre octal (base de numération 8) int *
s Chaîne de caractères, lue jusqu'à trouver un blanc (newline ou tab). char *
u Nombre décimal non signé. unsigned int *
x, X Nombre hexad"cimal (base de numération 16). int *
La chaîne de formatage de la saisie peut également contenir des arguments additionnels avec la taille de la chaine attendue par exemple. La fonction scanf() retourne une valeur entière qui donne le nombre d'éléments dans la chaine saisie.
int age;
char prenom[20];
char lettre;

printf("Quel est votre âge ? ");
scanf("%d", &age); // Utilisation de l'adresse de l'entier pour la récupération de la valeur

printf("Quel est votre prénom ? ");
scanf("%s", prenom);

printf("Et l'initiale de votre nom de famille ? ");
scanf("%c", &lettre); // Idem que pour l'entier, nous passons l'adresse de la variable pour modification

printf("Bonjour %s, vous avez %d ans.\n", prenom, age);

Création d'une bibliothèque partagée

gcc -shared -o mydll.dll obj1.o obj2.o [...]
Chaque objet inclus sur le linker doit avoir été compilé avec l'option -fPIC :
gcc -c obj1.c -fPIC