Initiation au C

Nous allons écrire de petits programmes qui écrivent dans le terminal. Le but est d'apprendre :

  • à compiler un seul fichier C (compiler = transformer le programme C texte en fichier exécutable que la machine peut exécuter)
  • écrire un programme C basique (sans pointeur)

⌛ Plus tard, on fera des programmes avec plusieurs fichiers, et on verra les pointeurs.

Mon premier programme C

Voici mon premier programme en C :

#include <stdio.h>

int main() {
    printf("Bienvenue à l'ENS Lyon/n");
    return 0;
}

Vous pouvez mettre ce contenu dans un fichier :

🖹 programme.c
  • #include <stdio.h> permet d'utiliser des fonctions pour écrire et lire dans la console.

  • int main() est le début de la définition de la fonction principale main. Sa définition commence par le type que renvoie la fonction, ici un entier int. La fonction renvoie 0 quand tout s'est bien passé. Elle renvoie une valeur non nulle pour signaler une erreur.
  • printf permet d'écrire dans la console.
  • return 0; quitte la fonction et renvoie 0.
#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("Bienvenue à l'ENS Lyon/n");
    return EXIT_SUCCESS;
}

Compilation

Le fichier 🖹 programme.c n'est pas exécutable : il s'agit d'un fichier texte que le processeur ne comprend pas. Pour construire un fichier exécutable par la machine, on utilise un compilateur. Ce processus s'appelle la compilation.

Pour compiler, taper :

gcc programme.c

On obtient un nouveau fichier qui est exécutable :

🖹 programme.c
⚙ a.out

et que l'on peut lancer :

./a.out

Bibliothèque standard

Le langage C est fourni avec la bibliothèque standard. Elle offre des fichiers appelés fichiers d'en-tête ou fichier header. Par exemple, le fichier stdio.h offre les fonctionnalités d'entrées/sortie sur la console. Les fichiers d'en-tête sont normalement disponibles dans le dossier usr/include. Ils contiennent des signatures de fonctions.

⌛ On peut aussi créer des fichiers header nous-même, ou alors utiliser ceux d'une bibliothèque non-standard (comme la bibliothèque raylib pour dessiner à l'écran !).

Voici les fichiers d'en-tête de la bibliothèque standard :

Bibliothèque standard C (C23)
│
├── Entrées / Sorties & Utilitaires
│   ├── <stdio.h>      (E/S standard : printf, scanf, fopen…)
│   ├── <stdlib.h>     (malloc, exit, atoi, rand…)
│   ├── <string.h>     (strlen, strcpy, memcpy…)
│   ├── <time.h>       (time, clock, struct tm…)
│   ├── <locale.h>     (setlocale, conventions régionales)
│   └── <uchar.h>      (Unicode UTF-16/UTF-32)
│
├── Chaînes & Caractères
│   ├── <ctype.h>      (isalpha, tolower…)
│   ├── <wchar.h>      (wchar_t, fonctions chaînes larges)
│   └── <wctype.h>     (classification/conversion wchar_t)
│
├── Mathématiques
│   ├── <math.h>       (sin, cos, sqrt…)
│   ├── <complex.h>    (nombres complexes)
│   ├── <tgmath.h>     (maths génériques type-safe)
│   ├── <float.h>      (caractéristiques flottants)
│   └── <fenv.h>       (contrôle environnement flottant)
│
├── Types & Limites
│   ├── <stddef.h>     (size_t, NULL, ptrdiff_t…)
│   ├── <stdbool.h>    (bool, true, false)
│   ├── <stdint.h>     (entiers fixes : int32_t…)
│   ├── <inttypes.h>   (formatage et macros pour stdint)
│   ├── <limits.h>     (INT_MAX, etc.)
│   ├── <stdalign.h>   (alignement mémoire, alignof…)
│   ├── <stdnoreturn.h>(_Noreturn)
│   └── <iso646.h>     (alias syntaxiques : and, or, not…)
│
├── Gestion d’erreurs & Signaux
│   ├── <assert.h>     (assert)
│   ├── <errno.h>      (errno, codes d’erreur)
│   ├── <signal.h>     (SIGINT, SIGSEGV…)
│   └── <setjmp.h>     (setjmp/longjmp)
│
├── Fonctions variadiques
│   └── <stdarg.h>     (va_list, va_start, va_arg…)
│
└── Concurrence (C11+)
    ├── <threads.h>    (threads, mutex, cond vars)
    └── <stdatomic.h>  (atomiques)

Entrées-sorties

stdio.h est le fichier pour de la bibliothèque standard (std) pour les entrées-sorties (io).

Pour utiliser ces fonctions, on écrit :

      #include <stdio.h>

Ecrire sur stdout (sortie standard)

La sortie standard est typiquement la console, mais cela peut être un fichier, ou alors une entrée à un autre programme.

putchar('c') écrit le caractère c dans le terminal.

printf est la fonction pour écrire d'un coup des informations plus compliquées ✎ :

    printf("Bonjour");
    printf("Bonjour\n");

    printf("Voici votre identifiant : %d", id);
    printf("Voici votre nouveau prénom : %s", prenom);
    printf("Voici votre nouveau prénom et votre identifiant : %s %d", prenom, id);

⚠ Ne jamais écrire printf(dataFromTheUser) car l'utilisateur pourrait écrire des %s, %d, voire des %n qui écrivent dans la mémoire. Le comportement peut être inattendu, et c'est faille de sécurité garantie :).

Lire l'entrée stdin (entrée standard)

L'entrée standard est typiquement ce qu'écrit l'utilisateur dans la console, mais cela peut être un fichier, ou alors une sortie d'un autre programme.

La fonction getchar() renvoie le prochain caractère sur l'entrée puis le consomme, c'est-à-dire que la prochaine fois que l'on exécute getchar() on lira le prochain caractère. getchar() renvoie EOF (end of file) si c'est fini.

scanf est la fonction pour lire des informations plus compliquées que l'utilisateur écrit dans la console ou de manière générale dans stdin ⌨ :

    int n;
    scanf("%d", &n);

On peut aussi lire plusieurs variables d'un coup.

    int n, m;
    scanf("%d", &n, &m);

La fonction scanf renvoie le nombre de variables qui ont été affectées correctement.

    int n, m;
    int nbLus = scanf("%d %d", &n, &m);

Spécification de formats

SpécificateurType attenduExempleRésultat affiché
%d ou %iint (entier signé)printf("%d", -42);-42
%uunsigned intprintf("%u", 42);42
%oentier non signé (octal)printf("%o", 42);52
%xentier non signé (hexadécimal min.)printf("%x", 42);2a
%Xentier non signé (hexadécimal maj.)printf("%X", 42);2A
%cchar (caractère)printf("%c", 'A');A
%schaîne (char*)printf("%s", "Hello");Hello
%ffloat ou double (décimal)printf("%f", 3.14);3.140000
%.2ffloat ou double (précision)printf("%.2f", 3.14);3.14
%e / %Efloat ou double (scientifique)printf("%e", 1234.0);1.234000e+03
%g / %Gfloat ou double (auto %f ou %e)printf("%g", 1234.0);1234
%ppointeur (void*)printf("%p", ptr);0x7ffee2bff6c0
%%affiche %printf("50%%");50%

Quelques éléments du langage C

Une déclaration de variable est un morceau de code qui renseigne le type d'une variable. Par exemple :

    int x;

Il y a pleins de types primitifs : int, long, unsigned int, char, bool (avec #include <stdbool.h>).

Une instruction est un morceau du programme qui ne renvoie rien. Par exemple, printf("aïe");

Une expression est un morceau du programme qui renvoie une valeur. Par exemple 1+2. Mais aussi x = 42 qui renvoie... 42.

Incrémentation

Considérons l'instruction (qui est aussi une expression)

    x = 42;
  • Both expressions x++ and ++x increment x.
  • The value of the expression x++ is 42.
  • The value of expression ++x is 43.

L'instruction (qui est aussi une expression) suivante

    x += 3;

ajoute 3 à x.

Boucle for

    for(int i = 0; i < n; i++)
        ...


    for(int i = 0; i < n; i++) {
        ...
    }

En fait, la boucle for en C est puissante. Elle a l'expressivité des boucles while :

    for(expr1; expr2; expr3) {
        ...
    }

est équivalent à

    expr1;
    while(expr2) {
        ...
        expr3;
    }

La fonction main

Un programme C contient toujours une fonction main qui est la fonction appelée quand on exécute le programme :

    #include <stdlib.h>

    int main() {
        ...
        return EXIT_SUCCESS;
    }

On verra plus tard une signature plus compliquée pour main car on peut gérer des arguments donnés au programme.

Utilisation du compilateur

gcc -Wall -pedantic -ainsi -std=c99 programme.c -o programme
OptionsMeaning
-Wallenables all compiler's warning messages
-Wextrastill a bit more warnings
-pedanticenables all compiler's warning messages
-ansiturns off gcc features that are not in the ANSI C
-std=c99ANSI C99
-std=c11ANSI C99
-o programmetells that the output is the file programme

Exercices

Triangle d'étoiles

Ecrire un programme qui prend demande à l'utilisateur/rice un nombre entier n puis affiche un triangle d'étoiles sur n lignes. Par exemple si n vaut 5, on affiche :

*
** 
***
****
*****

Pyramide

Même chose avec une pyramide :

   *
  *** 
 *****
*******

Nombre de caractères dans le texte sur stdin

En utilisant getchar(), écrire un programme nombreligne.c qui lit stdin et sur donne le nombre de lignes dans l'entrée.

Par exemple sur

    ./nombreligne < textExemple.txt

il donne le nombre de lignes dans le fichier texte textExemple.txt.

  • Pareil avec le nombre de lignes
  • Pareil avec le nombre de mots

Occurrences de caractères

Dans cet exercice, vous aurez besoin d'un tableau d'entiers de taille 26 déclaré avec int A[26];. On accède au i-ème élément du tableau A avec A[i]. On rappelle que chaque caractère est un nombre entre 0 et 255, codé sur 8 bits. Si c est un char, on peut écrire c - 'a' pour faire la différence entre le code de c et celui de la lettre 'a'.

Ecrire un programme qui lit stdin et qui affiche un histogramme du nombre des occurrences des lettres (on ignore les autres caractères, les chiffres, etc.) comme :

    a ***
    b ******
    c **
    d *
    e ************
    z ***

Variables

La portée (scope) d'une variable est la portion de code dans laquelle la variable peut être utilisée.

La durée de vie (life-time) d'une variable est la durée d'existence d'une variable.

ScopeLife-time
Global variablesAllAlways
Variables in functionsFunctionFunction
Static variables in functionsFunctionAlways

Les variables statiques peuvent servir pour :

  • faire un compteur et cacher le contenu du compteur en dehors
  • faire une fonction pour le drag drop avec la souris (on stocke/cache la position précédente de la souris)

Mémoire

La mémoire allouée pour un programme est divisée en trois parties :

  • Segment de données. On y trouve les variables globales et les variables statiques.
  • Pile d'appels. On y trouve les variables locales à une fonction. Les tableaux locaux sont stockées sur la pile aussi.
  • Le tas mémoire. C'est pour y mettre des données dont on ne connait pas à l'avance la taille, ou dont la taille peut modifiée au cours du programme (structure de données comme des tableaux redimensionnables, des listes chaînées, etc.).

Typage en C

Le type en C sert à dire combien d'octets prend une variable.

La fonction sizeof renvoie combien d'octets sont utilisé pour stocker un certain type / une certaine variable.

sizeof(truc)utilise ... octets
sizeof(char)1
sizeof(int)4
    int x;
    printf("%d\n", sizeof(x));
    
    int A[5];
    printf("%d\n", sizeof(A));

Conversion implicite

    1 + 2.3f

Casting

    int i = 2;
    int j = 3;
    float quotient = 2/3;

Oh non, quotient vaut 0. Mais on peut caster :

    quotient = ((float) 2) / 3

Le C est de bas niveau...

Voici un site où on peut compiler du code C et voir le résultat en assembleur :

https://godbolt.org/

Exercices

Génération de labyrinthes

Proposer un algorithme aléatoire pour générer un labyrinthe comme

XXXXXXXXXXXXXXX
X X     X     X
X XXX X X X XXX
X X   X X X   X
X X XXXXX XXX X
X X   X     X X
X X X X XXXXX X
X X X X X   X X
X XXX X X X X X
X X   X X X   X
X X XXX X XXX X
X X     X X X X
X XXXXXXX X X X
X         X   X
XXXXXXXXXXXXXXX

Il s'agit de produire un tableau A 2D de taille n x m où n et m sont des entiers impairs avec :

  • A[i, j] = 'X' ou ' '
  • A[0, j], A[i, 0], A[n-1, j], A[i, m-1] contiennent 'X' ;
  • A[i, j] = 'X' lorsque i et j sont pairs
  • A[i, j] = ' ' lorsque i et j sont impairs
  • Le graphe G est un arbre, où G est le graphe non orienté (V, E) où V sont les couples (i, j) dans {1, 3, .., n-2} x {1, 3, ..., m-2} et E contient les arêtes {(i, j), (i', j')} avec :
  • i' == i+2 et j'== j et A[i+1, j] == ' '
  • ou i' == i et j' == j+1 et A[i, j+1] == ' '

Prochaine séance

Nous allons utiliser la bibliothèque raylib pour pouvoir afficher des dessins (rectangles, points, lignes, etc.) à l'écran :).

  • Jeu vidéo comme agar.io
  • Afficher une fractale comme celle de mandelbrot
  • Promenade aléatoire
  • Appartenance d'un point à un polygone ?
  • parcours de Graham

Pour aller plus loin