Principes des Langages de Programmation
Les pointeurs, les structures et les fonctions

John Samuel
CPE Lyon

Année: 2026-2027
Courriel: john.samuel@cpe.fr
Creative Commons License

2.1. Objectifs

Objectifs

2.2. Microprocesseur

Architecture

L'architecture d'une machine détermine la gestion de la mémoire, la taille des types, l'ordre d'exécution des instructions et la taille des registres — d'où des ajustements nécessaires d'une plate-forme à l'autre.

2.2. Microprocesseur

Architecture

Une architecture n-bit peut adresser au plus 2n octets. En pratique les processeurs 64 bits actuels n'implémentent que 48 ou 57 bits d'adresse virtuelle : la limite théorique n'est jamais atteinte.

2.2. Microprocesseur

Le nombre de bits d'une machine est étroitement lié à la mémoire de plusieurs manières :

2.2. Microprocesseur

Le nombre de bits d'une machine est étroitement lié à la mémoire de plusieurs manières :

2.2. Microprocesseur

Le nombre de bits d'une architecture détermine la quantité de mémoire adressable.

2.3. Mémoire vive

La capacité de la mémoire vive (RAM) d'un ordinateur est de 4 Go, 8 Go, 16 Go, etc., et elle détermine la quantité de données que l'ordinateur peut stocker temporairement pour un traitement rapide.

2.4. Mémoire Virtuelle

la conversion des adresses virtuelles en adresses physiques
La conversion des adresses virtuelles en adresses physiques

2.5. Bit de poids fort et faible

Dans une représentation binaire donnée,
MSB et LSB (8 bit)
MSB et LSB (8 bit)

2.6. Endianness (Boutisme)

Dans une représentation binaire sur 8 bits (octet)

MSB et LSB (8 bit)
MSB et LSB (8 bit)

2.6. Endianness (Boutisme)

Dans une représentation binaire sur 16 bits

MSB et LSB (16 bit)
MSB et LSB (16 bit)

2.6. Endianness (Boutisme)

Endianness (Boutisme)

L'ordre d'organisation des octets en mémoire.

Remarque: Dans ce cours, nous utilisons la convention petit-boutiste.

carte des architectures grand-boutistes et petit-boutistes
Boutisme: grand-boutiste et petit-boutiste

2.7. Pointeurs

char c = 'a';
char *my_char_addr = &c;
une variable char
une variable char

2.7. Pointeurs

Pointeurs, adresses virtuelles et physiques

2.7. Pointeurs

Pointeurs, adresses virtuelles et physiques

2.7. Pointeurs

Pointeurs, adresses virtuelles et physiques

2.7. Pointeurs

short *

short s = 0xa478;
short *my_short_addr = &s;

Ce code initialise une variable short s avec la valeur hexadécimale 0xa478, puis crée un pointeur my_short_addr qui référence l'adresse mémoire de s.

Remarque: my_short_addr = 0xab71.

une variable short
une variable short

2.7. Pointeurs

int *

int i = 0xa47865ff;
int *my_int_addr = &i;

Ce code initialise une variable int i avec la valeur hexadécimale 0xa47865ff, puis crée un pointeur my_int_addr qui référence l'adresse mémoire de i.

Remarque: my_int_addr = 0xab71

une variable int
une variable int

2.7. Pointeurs

long int *

long int li = 0xa47865ff;
long int *my_long_int_addr = &li;

Ce code initialise une variable long int li avec la valeur hexadécimale 0xa47865ff, puis crée un pointeur my_long_int_addr qui référence l'adresse mémoire de li.

Remarque: my_long_int_addr = 0xab71

une variable long int
une variable long int

2.7. Pointeurs: les nombres en flottant

IEEE 754

float (32-bit)

une variable float
une variable float

2.7. Pointeurs: les nombres en flottant

IEEE 754

float (32-bit): Pour stocker un numéro en utilisant la conversion IEEE 754, voici les étapes :

2.7. Pointeurs: les nombres en flottant

IEEE 754

float (32-bit): Pour stocker un numéro en utilisant la conversion IEEE 754, voici les étapes pour stocker le nombre 9.5:

2.7. Pointeurs: les nombres en flottant

IEEE 754

double (64-bit)

Remarque: IEEE 754

une variable double
une variable double

2.7. Pointeurs: les nombres en flottant

IEEE 754

double (64-bit): La conversion d'un nombre en double précision (double) se fait généralement en plusieurs étapes :

2.7. Pointeurs

float *

float f = 2;
float *my_float_addr = &f;

Remarque: my_float_addr = 0xab71

une variable float
une variable float

2.7. Pointeurs

float *

float f = 1;
float *my_float_addr = &f;

Remarque: my_float_addr = 0xab71

une variable float
une variable float

2.7. Pointeurs

double *

double d = 2;
double *my_double_addr = &d;

Remarque: my_double_addr = 0xab71

une variable double
une variable double

2.7. Pointeurs

double *

double d = 1;
double *my_double_addr = &d;

Remarque: my_double_addr = 0xab71

une variable double
une variable double

2.7. Pointeurs

printf: Afficher à l'écran

Type Code de conversion
pointeur %p

  printf("char : %p\n", (void *) my_char_addr);
  printf("short : %p\n", (void *) my_short_addr);
  printf("int : %p\n", (void *) my_int_addr);
  printf("long int : %p\n", (void *) my_long_int_addr);
  printf("float : %p\n", (void *) my_float_addr);
  printf("double : %p\n", (void *) my_double_addr);

2.8. L'opérateur de déréférenciation

L'opérateur de déréférenciation * est utilisé pour accéder à la valeur stockée à l'adresse mémoire pointée par un pointeur.

char c = 'a';
char *my_char_addr = &c;
printf ("%c", *my_char_addr);

L'opérateur de déréférencement (*) est utilisé pour accéder et afficher la valeur de la variable c à travers le pointeur my_char_addr.

2.8. L'opérateur de déréférenciation

Pour distinguer l'opérateur * en tant qu'opérateur de déréférencement, vous pouvez vous appuyer sur le contexte dans lequel il est utilisé.

Déclaration de pointeur : Lorsque vous déclarez un pointeur, l'opérateur * est utilisé pour indiquer qu'il s'agit d'un pointeur. Par exemple :

char *my_char_addr = &c;

Ici, * fait partie de la déclaration, ce n'est pas un déréférencement.

Utilisation du pointeur : pour accéder à la valeur pointée, * est bien l'opérateur de déréférencement :

printf ("%c", *my_char_addr);
char d = *my_char_addr;

* donne ici la valeur pointée par my_char_addr, c'est-à-dire celle de c.

2.8. L'opérateur de déréférenciation

L'opérateur * est utilisé pour manipuler un objet pointé lorsqu'il est utilisé avec un pointeur.

Exemple 1

char c = 'a';
char *my_char_addr = &c;
c = 'b';
*my_char_addr = 'c';
printf ("%c", c); //le caractère pointé par my_char_addr prend la valeur 'c'

L'opérateur * est utilisé avec *my_char_addr pour accéder et modifier la valeur stockée à l'adresse mémoire pointée par my_char_addr. Cela permet de changer la valeur de la variable c en modifiant directement la mémoire à l'emplacement indiqué par le pointeur.

2.8. L'opérateur de déréférenciation

L'opérateur * est utilisé pour manipuler un objet pointé lorsqu'il est utilisé avec un pointeur.

Exemple 2

int i = 0x20;
int *my_int_addr = &i;
*my_int_addr = 1;
*my_int_addr = *my_int_addr + 1; //i = 0x2
i = i + 3; //i = 0x5
printf ("%x", *my_int_addr); // 5

L'opérateur * est utilisé avec *my_int_addr pour accéder et modifier la valeur stockée à l'adresse mémoire pointée par my_int_addr. Cela permet de changer la valeur de la variable i en modifiant directement la mémoire à l'emplacement indiqué par le pointeur.

2.9. Les pointeurs génériques

void *

char a = 'a';
char *cptr = &a;
int i = 1;
cptr = &i;

$ gcc ...
error: assignment to ‘char *’ from incompatible pointer type ‘int *’ [-Wincompatible-pointer-types]
cptr = &i;

2.9. Les pointeurs génériques

void *

Un pointeur générique est un type spécial de pointeur qui peut contenir l'adresse mémoire de n'importe quel type de données, c'est-à-dire qu'il peut pointer vers n'importe quel type de variable. Le terme "générique" signifie que le pointeur n'a pas de type de données spécifique associé.

char a = 'a'; char *cptr = &a;
void *vptr = &a;

int i = 1; vptr = &i;

float f = 1; vptr = &f;

L'avantage principal d'un pointeur générique est sa polyvalence. Vous pouvez l'utiliser pour stocker l'adresse d'une variable de n'importe quel type,

2.10. Conversion de type

Sans perte d'information

La conversion de type (« cast ») change le type d'une variable. Vers un type plus large de même signe, la valeur est conservée : elle est simplement étendue.

short s = 0x7fff;
int i = s; // i vaut 32767

Contre-exemple : short s = 0xffff; ne convertit rien : 65535 ne tient pas dans un short signé (max 32767). Le résultat est défini par l'implémentation — sur x86, s vaut −1.

2.10. Conversion de type

Avec perte d'information

Exemple

int i = 16777217;
float f = i;

Remarque: f vaut 16777216, pas 16777217 : l'entier voisin n'est pas représentable. (int) f == i est faux.

2.10. Conversion de type

Les conversions explicites

Les conversions explicites, également appelées typecasts, sont des opérations dans lesquelles vous spécifiez explicitement le type de données vers lequel vous souhaitez convertir une variable.

short s = 0x7fff;
float f = 30.999f;
int i = (int) s;
int j = (int) f;

Remarque: Une conversion explicite (typecasting): (type)

2.10. Conversion de type

void *

Lorsque vous souhaitez accéder aux données pointées par un pointeur générique, vous devez le convertir vers le type approprié.

char a = 'a';
char b = 'b';
char *cptr = &a;
void *vptr = &b;

cptr = (char *)vptr;

2. Question

Écrivez un programme en utilisant char * et les opérateurs de pointeurs pour voir les octets d'une variable long int.

2.11. Les structures

Tableau

est un ensemble d'éléments homogènes

Mais comment travailler avec un ensemble d'éléments hétérogènes?


2.11. Les structures

Base de données de gestion des étudiants

char prenom[135][30];
char nom[135][30];
char rue[135][30];
char ville[135][30];
unsigned short notes[135];

Question: Quels sont les problèmes?

2.11. Les structures

Base de données de gestion des étudiants

2.11. Les structures

Base de données de gestion des étudiants

2.11. Les structures

Une structure (struct) est une construction de données en programmation permettant de regrouper plusieurs variables de types différents sous un seul nom, facilitant ainsi la manipulation et la gestion de données complexes.

struct etudiant{
 char prenom[30];
 char nom[30];
 char rue[30];
 char ville[30];
 short notes;
};

Avantages

2.11. Les structures

La déclaration et l'utilisation d'une structure

Ce code crée une variable d'une structure etudiant, et l'initialise avec les données spécifiques à un étudiant nommé Dupont Pierre.

struct etudiant dupont = { "Pierre", "Dupont", "Boulevard du 11 novembre 1918", "Villeurbanne", 19};

2.11. Les structures

La déclaration et l'utilisation d'une structure

Ce code vise à définir une variable d'une structure etudiant pour stocker les informations d'un étudiant particulier nommé "Dupont Pierre", avec une adresse et une note.

struct etudiant dupont;

strcpy(dupont.prenom, "Pierre");
strcpy(dupont.nom, "Dupont");
strcpy(dupont.rue, "Boulevard du 11 novembre 1918");
strcpy(dupont.ville, "Villeurbanne");
dupont.notes = 19;

2.11. Les structures

Tableaux de structures

Ce code crée un tableau de structures nommé etudiant_cpe pouvant contenir jusqu'à 135 étudiants, puis il initialise le premier étudiant du tableau avec les données spécifiques à "Dupont Pierre" notamment son prénom, nom, adresse et note.

struct etudiant etudiant_cpe[135];

strcpy(etudiant_cpe[0].prenom, "Pierre");
strcpy(etudiant_cpe[0].nom, "Dupont");
strcpy(etudiant_cpe[0].rue, "Boulevard du 11 novembre 1918");
strcpy(etudiant_cpe[0].ville, "Villeurbanne");
etudiant_cpe[0].notes = 19;

2.11. Les structures

Une structure dans une structure

Ce code définit deux structures, adresse et etudiant, où la structure etudiant contient une structure adresse comme l'un de ses membres. Cela permet de stocker les informations de l'étudiant, y compris son prénom, nom et adresse, de manière organisée et hiérarchique.

struct adresse{
 char rue[30];
 char ville[30];
};
struct etudiant{
 char prenom[30];
 char nom[30];
 struct adresse adresse;
 short notes;
};

2.11. Les structures

Une structure dans une structure

Ce code définit une structure etudiant qui inclut une structure imbriquée adresse. Cela permet de regrouper les informations relatives à un étudiant, telles que son prénom, nom et notes, ainsi que ses données d'adresse, y compris la rue et la ville, de manière structurée et organisée.

struct etudiant{
 char prenom[30];
 char nom[30];
 struct adresse{
  char rue[30];
  char ville[30];
 } adresse;
 short notes;
};

2.11. Les structures

Une structure dans une structure

Ce code crée un tableau de structures etudiant nommé etudiant_cpe pouvant contenir jusqu'à 135 étudiants. Il utilise la structure imbriquée adresse pour stocker les détails de l'adresse de l'étudiant.

struct etudiant etudiant_cpe[135];

strcpy(etudiant_cpe[0].prenom, "Pierre");
strcpy(etudiant_cpe[0].nom, "Dupont");
strcpy(etudiant_cpe[0].adresse.rue, "Boulevard du 11 novembre 1918");
strcpy(etudiant_cpe[0].adresse.ville, "Villeurbanne");
etudiant_cpe[0].notes = 19;

2.12. Les unions

Union

Une union se déclare comme une structure, mais ses membres partagent le même espace mémoire. Sa taille est celle de son membre le plus grand — et non la somme des tailles, comme pour une structure.

union etudiant{
 char prenom[30];
 char nom[30];
 char rue[30];
 char ville[30];
};

Remarque: l'union etudiant peut stocker le prénom, le nom, la rue ou la ville — mais un seul à la fois. Écrire dans un membre écrase les autres.

2.12. Les unions

La déclaration et l'utilisation d'une union

union etudiant dupont;

strcpy(dupont.nom, "Dupont");
printf("%s\n", dupont.prenom);
printf("%s\n", dupont.rue);
printf("%s\n", dupont.ville);

L'affichage

Dupont
Dupont
Dupont

Remarque: les quatre membres commencent à la même adresse. Un seul contenu est écrit, et les trois lectures le retrouvent. C'est exactement ce qui distingue une union d'une structure.

2.12. Les unions

Union étiquetée : l'usage courant

struct content{
 char type[30];
 union{
  char * ccontent;
  int * icontent;
  float * fcontent;
 };
};

2.12. Les unions

/* Fichier: memory3.c
* une union étiquetée */

#include <stdio.h> // en-têtes(headers)
#include <stdlib.h>
#include <string.h>
int main(int argc, char ** argv) {
 struct content c = {0};
 snprintf(c.type, sizeof(c.type), "%s", "char");

 if (strcmp(c.type, "char") == 0) {
   c.ccontent = calloc(10, sizeof(char));
 }
 else if (strcmp(c.type, "int") == 0) {
   c.icontent = calloc(10, sizeof(int));
 }

 free(c.ccontent); // le membre libéré doit être celui qui a été alloué
 return 0;
}

2.12. Les unions

Point3D : deux vues des mêmes octets

L'union point3d donne deux façons de désigner les mêmes 12 octets : un tableau value[3], ou trois champs nommés x, y, z. p.value[0] et p.x désignent le même entier.

union point3d{
 int value[3];
 struct{
  int x;
  int y;
  int z;
 };
};

Remarque: la structure anonyme (C11) permet d'écrire p.x et non p.s.x. C'est ici que la relecture d'un membre non écrit est volontaire et bien définie en C : les deux vues décrivent la même disposition mémoire.

2.12. Les unions

/* Fichier: union.c
* la déclaration et l'utilisation d'une union
*/

#include <stdio.h> // en-têtes(headers)

int main(int argc, char ** argv) {
 union point3d p;
 p.value[0] = 0x10;
 p.value[1] = 0x45;
 p.value[2] = 0x78;

 printf("%x %x %x\n", p.x, p.y, p.z);
 return 0;
}

L'affichage

10 45 78

Remarque: sizeof(union point3d) vaut 12 — la taille du plus grand membre, pas leur somme.

2. Question

Écrivez une structure pour la représentation d'une couleur RGB (rouge, vert, bleu : chaque couleur prend un octet) en utilisant union et struct, de façon à pouvoir accéder soit aux trois composantes séparément, soit aux trois octets d'un coup.

synthèse additive des couleurs rouge, vert et bleu
Source: https://commons.wikimedia.org/wiki/File:AdditiveColorMixiing.svg

2.13. Fonctions

/* Fichier: bonjour2.c
* affiche un message à l'écran en utilisant une variable
* auteur: John Samuel
* Ceci est un commentaire sur plusieurs lignes
*/


#include <stdio.h> // en-têtes(headers)

int main() {
  int annee = 2026; // déclaration d'une variable
  printf("Bonjour le Monde!!! C'est l'année %d", annee);
  return 0;
}

2.13. Fonctions

Les fonctions sont utilisées pour encapsuler des morceaux de code spécifiques dans un programme afin d'effectuer des opérations ou de calculer des résultats de manière modulaire et réutilisable.

Prototype: Le but de ce prototype est de fournir aux autres parties du programme des informations sur la manière d'appeler la fonction "add" et sur les types de données qu'elle attend et renvoie.

int add( int, int);

L'implémentation: Plutôt que de répéter le même code d'addition partout où vous en avez besoin dans votre programme, vous pouvez simplement appeler la fonction "add".

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

2.13. Fonctions

Les fichiers .h servent à déclarer les prototypes de fonctions et les structures de données pour permettre une séparation claire entre l'interface publique et la définition de fonctions dans les fichiers .c.

Prototype (operators.h)

int add( int, int);

L'implémentation (operators.c)

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

2.13. Fonctions

La déclaration d'une fonction consiste en un prototype qui spécifie le type de retour et les types des paramètres entre parenthèses, tandis que l'implémentation de la fonction utilise la même signature avec des noms de variables pour les paramètres et inclut le code à exécuter.

Prototype

type fonction( [type,]*);

L'implémentation

type fonction( [type variable,]*) {
 [return valeur];
}

Remarque: type: void, les types de base, les types composés

2.13. Fonctions

Les fichiers .h sont inclus à l'aide de #include pour permettre l'accès aux déclarations de fonctions, structures et variables définies dans le fichier d'en-tête, ce qui permet d'utiliser ces fonctionnalités dans d'autres parties du programme.

L'utilisation

#include "operators.h" // en-têtes(headers)

int num1 = 20, num2 = 60;
int sum = add( num1, num2);

Remarque: Regardez l'utilisation de " " dans en-têtes

2.13. Fonctions

Prototype (nom.h)

void print( char *, int);

L'implémentation (nom.c)

#include <stdio.h> // en-têtes(headers)

void print( char * nom, int size ) {
 int i;
 for( i = 0; i < size && nom[i] != '\0'; i++) {
   printf( "%c", nom[i]);
 }
}

Remarque: Il n'y a pas de return

2.13. Fonctions

/* Fichier: bonjour3.c
* affiche un message à l'écran en utilisant print
* auteur: John Samuel
*/


#include "nom.h" // en-têtes(headers)

int main() {
 print( "Bonjour le Monde!!!", 19);
 char message[] = "Pierre";
 print("Je suis ", 8);
 print(message, 19);
 return 0;
}

2.13. Fonctions

La compilation

$ gcc -o bonjour bonjour3.c nom.c

L'exécution

$./bonjour
Bonjour le Monde!!!Je suis Pierre

Remarque: un programme ne peut contenir qu'une seule fonction nommée 'main', quel que soit le nombre de fichiers .c liés ensemble.

2.14. L'interface en ligne de commande

Les outils Linux

L'interface en ligne de commande (CLI) est un moyen d'interagir avec un système d'exploitation ou un logiciel en utilisant du texte, généralement en saisissant des commandes dans un terminal. Les commandes sont des instructions données à un système via une interface en ligne de commande, et les arguments sont des informations spécifiques fournies avec ces commandes pour indiquer ce qu'elles doivent faire

2.14. L'interface en ligne de commande

/* Fichier: bonjour4.c
* affiche un message à l'écran en utilisant les arguments de la ligne de commandes.
* auteur: John Samuel
*/


#include "nom.h" // en-têtes(headers)

int main(int argc, char ** argv) {
 print("Bonjour le Monde. Je suis ", 26);
 // sinon argv[4] est hors du tableau
 if (argc < 5) return 1;
 print(argv[1], 20);
 print(argv[2], 20);
 print(argv[3], 20);
 print(argv[4], 20);
 return 0;
}

2.14. L'interface en ligne de commande

La compilation

$ gcc -o bonjour bonjour4.c nom.c

L'exécution

$./bonjour Pierre Dupont Lyon 69001
Bonjour le Monde. Je suis Pierre Dupont Lyon 69001

La fonction main prend deux arguments, int argc et char ** argv, qui permettent de passer des arguments lors de l'exécution du programme.

Le programme peut ainsi accéder et utiliser ces arguments pour effectuer des actions spécifiques en fonction de ces valeurs. Dans l'exemple donné, le programme "bonjour4.c" semble utiliser les arguments pour afficher un message de salutation personnalisé en fonction des noms et de l'adresse passés en arguments lors de son exécution.

2.14. L'interface en ligne de commande

2.14. L'interface en ligne de commande

La compilation

2.14. L'interface en ligne de commande

/* Fichier: bonjour4.c
 * affiche un message à l'écran en utilisant
 * les arguments de la ligne de commandes. */


#include "nom.h" // en-têtes(headers)

int main(int argc, char ** argv) {
 print("Bonjour le Monde. Je suis ", 26);
 if ( argc == 2 ) {
  print(argv[1], 20);
 }
 return 0;
}

Remarque: argv[0] est normalement le nom du fichier exécutable (e.g., bonjour)

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string.c
* manipulation d'une chaîne de caractères.
* auteur: John Samuel
*/


#include <stdio.h> // en-têtes(headers)
#include <string.h>

int main(int argc, char ** argv) {
 char nom[10];
 printf("Bonjour. Votre nom? ");
 scanf("%s", nom);
 printf("La taille: %zu\n", strlen(nom));
 return 0;
}

Objectif : L'objectif de la fonction strlen est de calculer la longueur (le nombre de caractères) d'une chaîne de caractères, c'est-à-dire le nombre de caractères qu'elle contient avant d'atteindre le caractère nul '\0' qui marque la fin de la chaîne.

2.15. La manipulation d'une chaîne de caractères

La compilation

$ gcc -o strlen string.c

1ere Exécution

$./strlen
Bonjour. Votre nom? John
La taille: 4

2.15. La manipulation d'une chaîne de caractères

La compilation

2eme Exécution

Lorsqu'un nom excessivement long est saisi, ce programme provoque une erreur de dépassement de pile (stack smashing).

$./strlen
Bonjour. Votre nom? : ABCDEFGHIJKLMNOPQRSTUVWXYZ
*** stack smashing detected ***: ./strlen terminated

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string.c
* manipulation d'une chaîne de caractères.
* auteur: John Samuel
*/

#include <stdio.h> // en-têtes(headers)
#include <string.h>
int main(int argc, char ** argv) {
 char nom[10];
 printf("Bonjour. Votre nom? ");
 scanf("%9s", nom);
 printf("La taille: %zu\n", strnlen(nom, sizeof(nom)));
 return 0;
}

Objectif : strnlen calcule la longueur d'une chaîne sans jamais lire au-delà de la taille annoncée : il renvoie au plus sizeof(nom), même si le tableau ne contient aucun '\0'.
Attention : strnlen ne corrige pas le dépassement — il protège seulement la lecture. Le débordement de la page précédente venait de l'écriture faite par scanf("%s", nom). C'est pourquoi le format est passé à "%9s" ici : une largeur maximale (taille du tampon moins un) est la seule chose qui empêche réellement l'écriture hors limites.

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string.c
* scanf avec une largeur maximale : "%9s"
* auteur: John Samuel
*/

#include <stdio.h> // en-têtes(headers)
#include <string.h>
int main(int argc, char ** argv) {
 char nom[10];
 char sortie[100];
 printf("Bonjour. Votre nom? ");
 scanf("%9s", nom);
 sprintf(sortie, "La taille: %zu\n", strnlen(nom, sizeof(nom)));
 return 0;
}

Objectif: scanf("%9s", nom) lit une chaîne de caractères d'au plus 9 caractères depuis l'entrée standard et la stocke dans la variable nom, garantissant ainsi qu'aucun dépassement de mémoire ne se produit.

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string.c
* sprintf.
* auteur: John Samuel
*/

#include <stdio.h> // en-têtes(headers)
#include <string.h>
int main(int argc, char ** argv) {
 char nom[10];
 char sortie[100];
 printf("Bonjour. Votre nom? ");
 scanf("%9s", nom);
 sprintf(sortie, "La taille: %zu\n", strnlen(nom, sizeof(nom)));
 return 0;
}

Remarque: L'objectif de ce code est de formater une chaîne de caractères contenant la taille d'une autre chaîne (nom) limitée à un maximum de 10 caractères, puis de stocker ce résultat dans la variable sortie.

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string.c
* snprintf.
*/

#include <stdio.h> // en-têtes(headers)
#include <string.h>
int main(int argc, char ** argv) {
 char nom[10];
 char sortie[100];
 printf("Bonjour. Votre nom? ");
 scanf("%9s", nom);
 snprintf(sortie, sizeof(sortie), "La taille: %zu\n",
   strnlen(nom, sizeof(nom)));
 return 0;
}

Remarque: Utiliser snprintf au lieu de sprintf est une pratique plus sûre pour éviter les dépassements de mémoire potentiels.

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string.c
* atoi
* auteur: John Samuel
*/


#include <stdio.h> // en-têtes(headers)
#include <stdlib.h> // atoi, strtol

int main(int argc, char ** argv) {
 char numstr[10] = "1024";
 int num = atoi(numstr);
 printf("Numéro: %d", num);
 return 0;
}

La fonction atoi en C a pour objectif de convertir une chaîne de caractères représentant un nombre entier en une valeur entière de type int. Elle ignore les espaces initiaux, lit les chiffres jusqu'à ce qu'elle atteigne un caractère non numérique, puis retourne la valeur entière correspondante.

Attention : atoi ne signale aucune erreur : atoi("abc") et atoi("0") renvoient tous deux 0, et un dépassement est un comportement indéfini. Pour une saisie utilisateur, préférez strtol, qui indique où la lecture s'est arrêtée et positionne errno.

2.15. La manipulation d'une chaîne de caractères

/* sscanf */

#include <stdio.h> // en-têtes(headers)
#include <string.h>

int main(int argc, char ** argv) {
 int inum1, inum2;
 float fnum3 = 20;
 char numstr[20] = "10 20 30.33";
 sscanf(numstr, "%d %d %f", &inum1, &inum2, &fnum3);
 printf("Numéros: %d %d %f\n", inum1, inum2, fnum3);
 return 0;
}

La fonction sscanf permet de lire et de convertir des valeurs depuis une chaîne de caractères en fonction d'un format spécifié, stockant ces valeurs dans des variables, ce qui facilite l'extraction de données structurées à partir de chaînes de caractères formatées. La fonction sscanf analyse la chaîne numstr en recherchant des entiers et un nombre à virgule flottante délimités par des espaces, puis stocke ces valeurs dans les variables inum1, inum2, et fnum3.

2.15. La manipulation d'une chaîne de caractères

/* sscanf */

#include <stdio.h> // en-têtes(headers)
#include <string.h>

int main(int argc, char ** argv) {
 int inum1, inum2;
 float fnum3 = 20;
 char numstr[20] = "10;20;30.33";
 sscanf(numstr, "%d;%d;%f", &inum1, &inum2, &fnum3);
 printf("Numéros: %d %d %f\n", inum1, inum2, fnum3);
 return 0;
}

La fonction sscanf analyse la chaîne numstr en recherchant des entiers et un nombre à virgule flottante délimités par des points-virgules, puis stocke ces valeurs dans les variables inum1, inum2, et fnum3.

2.15. La manipulation d'une chaîne de caractères

La compilation

$ gcc -o strnlen string2.c

1ere Exécution

$./strnlen
Bonjour. Votre nom? John
La taille: 4

2.15. La manipulation d'une chaîne de caractères

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string3.c
* manipulation d'une chaîne de caractères.
*/


#include <stdio.h> // en-têtes(headers)
#include <string.h>
int main(int argc, char ** argv) {
 char message[]= {"Bonjour"}, nom[]= {"John"}, string[20] = "";
 strncat(string, message, 19);
 strcat(string, " ");
 strncat(string, nom, 19);
 printf("%s\n", string);
 return 0;
}

2.15. La manipulation d'une chaîne de caractères

/* Fichier: string4.c
* manipulation d'une chaîne de caractères.
* auteur: John Samuel
*/

#include <stdio.h> // en-têtes(headers)
#include <string.h>
int main(int argc, char ** argv) {
 char message[]= {"Bonjour"}, nom[]= {"John"}, string[20] = "";
 strncpy(string, message, 19);
 strcpy(string, " ");
 strncpy(string, nom, 19);
 printf("%s\n", string);
 return 0;
}

2.15. La manipulation d'une chaîne de caractères

La compilation

$ gcc -o strncat string3.c
$ gcc -o strncpy string4.c

1ere Exécution

$./strncat
Bonjour John

2eme Exécution

$./strncpy
John

2.16. Allocation dynamique de mémoire

#include <stdlib.h>

void * malloc( size_t size);
void * calloc( size_t nmemb, size_t size);
void free( void * ptr); // désallocation ou libération de mémoire

2.16. Allocation dynamique de mémoire

/* Fichier: memory.c
* Allocation dynamique de mémoire
* auteur: John Samuel
*/


#include <stdio.h> // en-têtes(headers)
#include <stdlib.h>
#include <string.h>

int main(int argc, char ** argv) {
 char *content = malloc(10 * sizeof(char));
 if (content == NULL) return 1;
 // malloc ne remplit pas : il faut une chaîne valide
 content[0] = '\0';
 strncat(content, "Bonjour", 9);
 free(content); //libération de mémoire
 return 0;
}

2.16. Allocation dynamique de mémoire

/* Fichier: memory.c
* Allocation dynamique de mémoire
* auteur: John Samuel
*/


#include <stdio.h> // en-têtes(headers)
#include <stdlib.h>
#include <string.h>

int main(int argc, char ** argv) {
 char *content = calloc(10, sizeof(char));
 if (content == NULL) return 1;
 // 9 et non 10 : strncat ajoute encore le '\0'
 strncat(content, "Bonjour", 9);
 free(content); // libération de mémoire
 return 0;
}

2. Question

Écrivez un programme qui réserve et libère un espace mémoire pour un tableau d'entiers (int, long int, short ou long long int) et un tableau de nombres en flottant (float, double ou long double) en utilisant malloc, calloc et free.

2.17. Les variables constantes

Une variable constante

L'objectif d'une variable constante est de déclarer une valeur immuable qui ne peut pas être modifiée après son initialisation, afin d'assurer l'intégrité des données et de prévenir les erreurs potentielles.

/* Une variable constante */
#include <stdio.h>

int main() {
  const int annee = 2017; // une variable constante
  annee = 2019; // tentative de modification d'une variable constante
  printf("C'est l'année %d", annee);
  return 0;
}

2.17. Les variables constantes

Erreur pendant la compilation

Les variables constantes sont des valeurs en lecture seule, et toute tentative de modification de leur valeur après leur initialisation entraîne une erreur de compilation pour préserver leur immutabilité.

$ gcc bonjour.c
const.c: In function ‘main’:
const.c:5:8: error: assignment of read-only variable ‘annee’
annee = 2019;

2.18. La portée des variables

Une variable globale

Les variables globales sont visibles et modifiables depuis toutes les fonctions du programme. Il est généralement recommandé de limiter l'utilisation de variables globales et de les utiliser avec précaution pour éviter des effets secondaires indésirables dans un programme complexe.

/* affiche un message à l'écran en utilisant une variable globale
*/


#include <stdio.h>
int annee = 2017; // une variable globale

int main() {
  printf("C'est l'année %d", annee); //affiche 2017
  return 0;
}

2.18. La portée des variables

Une variable globale

/* affiche un message à l'écran en utilisant une variable globale */

#include <stdio.h>
int main() {
  printf("C'est l'année %d", annee);
  return 0;
}
int annee = 2017; // une variable globale

Dans la fonction main(), l'affichage de la valeur de annee est tenté avant sa déclaration et son initialisation, ce qui génère une erreur de compilation. Le compilateur ne sait pas ce qui est tenté d'être affiché avant que la variable annee ne soit définie.

2.18. La portée des variables

Erreur pendant la compilation

$ gcc bonjour.c
bonjour.c: In function ‘main’:
bonjour.c:5:30: error: ‘annee’ undeclared (first use in this function)
5 | printf("C'est l'année %d", annee);
| ^~~~
bonjour.c:5:30: note: each undeclared identifier is reported only once for each function it appears in

L'erreur de compilation indique que la variable annee n'a pas été déclarée avant son utilisation dans la fonction main().

2.18. La portée des variables

Une variable locale

/* affiche un message à l'écran en utilisant une variable locale */
#include <stdio.h>
int annee = 2017; // une variable globale
int main() {
  int annee = 2018; // une variable locale
  printf("C'est l'année %d", annee); //affiche 2018
  return 0;
}

Déclarée en dehors de main(), annee est une variable globale : visible depuis toute fonction du programme. Déclarée à l'intérieur, c'est une variable locale, visible de main() seulement.

C'est la portée des variables : la locale masque la globale de même nom.

2.18. La portée des variables

Une variable locale

/* affiche un message à l'écran en utilisant une variable locale */
#include <stdio.h>
int annee = 2017; // une variable globale

int main() {
  int annee = 2018; // une variable locale
  {
   int annee = 2019; // une variable locale
   printf("C'est l'année %d", annee); //affiche 2019
  }
  printf("C'est l'année %d", annee); //affiche 2018
  return 0;
}

2.19. Le passage de paramètres

1. Passage par valeur

void echange( int a, int b ) {
  int temp = a;
  a = b;
  b = temp;
}
int main() {
  int a = 10, b = 20;
  echange(a, b);
  printf("a: %d, b: %d", a, b); //affiche 10, 20
  return 0;
}

Passage par valeur : la fonction reçoit des copies. Les valeurs de a et b ne sont pas modifiées en dehors de la fonction.

2.19. Le passage de paramètres

2.1 Passage par référence: une variable

void echange( int *a, int *b ) {
  int temp = *a;
  *a = *b;
  *b = temp;
}
int main() {
  int a = 10, b = 20;
  echange(&a, &b);
  printf("a: %d, b: %d", a, b); //affiche 20, 10
  return 0;
}

Passage par référence : la fonction reçoit les adresses. Les valeurs pointées sont réellement échangées, et le changement est visible en dehors de la fonction.

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

Le passage par référence d'un tableau est une manière de transmettre un tableau à une fonction en lui fournissant directement une référence (ou un pointeur) vers le tableau d'origine, plutôt que de faire une copie du tableau.

void affichage( char message[10] ) {
  printf("%s\n", message);
}
int main() {
  char message[10] = "Bonjour";
  affichage(message);
  return 0;
}

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

Toute modification apportée au tableau à l'intérieur de la fonction affectera également le tableau d'origine en dehors de la fonction.

void affichage( char message[] ) {
  message[0] = 'b';
  printf("%s\n", message); // affiche "bonjour"
}
int main() {
  char message[10] = "Bonjour";
  affichage(message);
  printf("%s\n", message); // affiche "bonjour"
  return 0;
}

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

Lorsqu'un tableau est passé en tant qu'argument à une fonction, il est automatiquement converti en un pointeur vers son premier élément. Cela signifie que char message[] est équivalent à char *message dans le contexte de cette fonction.

void affichage( char *message ) {
  printf("%s\n", message);
}
int main() {
  char message[10] = "Bonjour";
  affichage(message);
  return 0;
}

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

void affichage( int tableau[2][2] ) {
  for ( int i = 0; i < 2; i++) {
    for ( int j = 0; j < 2; j++) {
     printf("%d\n",  tableau[i][j]);
    }
  }
}
int main() {
  int prix[2][2] = {{11, 12}, {13, 14}};
  affichage(prix);
  return 0;
}

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

void affichage( int tableau[][] ) {
  for ( int i = 0; i < 2; i++) {
    for ( int j = 0; j < 2; j++) {
     printf("%d\n",  tableau[i][j]);
    }
  }
}
int main() {
  int prix[2][2] = {{11, 12}, {13, 14}};
  affichage(prix);
  return 0;
}

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

Erreur pendant la compilation

Remarque : L'erreur lors de la compilation est due à la déclaration d'une fonction avec un tableau multidimensionnel sans spécifier la taille de la deuxième dimension.

$ gcc tableau.c
tableau.c:3:21: error: array type has incomplete element type ‘int[]’
3 | void affichage( int tableau[][] ) {
| ^~~~~~~
tableau.c:3:21: note: declaration of ‘tableau’ as multidimensional array must have bounds for all dimensions except the first
tableau.c: In function ‘main’:
tableau.c:15:13: error: type of formal parameter 1 is incomplete

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

Pour corriger l'erreur, nous avons précisé la taille de la deuxième dimension.

void affichage( int tableau[][2] ) {
  for ( int i = 0; i < 2; i++) {
    for ( int j = 0; j < 2; j++) {
     printf("%d\n",  tableau[i][j]);
    }
  }
}
int main() {
  int prix[2][2] = {{11, 12},{13, 14}};
  affichage(prix);
  return 0;
}

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

Pour corriger l'erreur, nous avons utilisé un pointeur vers un tableau à deux dimensions.

void affichage( int (*tableau)[2] ) {
  for ( int i = 0; i < 2; i++) {
    for ( int j = 0; j < 2; j++) {
     printf("%d\n",  tableau[i][j]);
    }
  }
}
int main() {
  int prix[2][2] = {{11, 12}, {13, 14}};
  affichage(prix);
  return 0;
}

2.19. Le passage de paramètres

2.2. Passage par référence: un tableau

Un pointeur vers un pointeur d'entiers : Un pointeur qui pointe vers un tableau de pointeurs.

void affichage( int **tableau, int lignes, int colonnes ) {
  for ( int i = 0; i < lignes; i++) {
    for ( int j = 0; j < colonnes; j++) {
     printf("%d\n",  tableau[i][j]);
    }
  }
}
int main() {
  int **prix, lignes, colonnes;   ....
  affichage(prix, lignes, colonnes);
  return 0;
}

2.19. Le passage de paramètres

un tableau à deux indices

2.19. Le passage de paramètres

Passage par référence: un tableau

int main() {
  int **tableau, lignes = 2, colonnes = 10;
  tableau = calloc(lignes, sizeof(int *));
  for ( int i = 0; i < lignes; i++) {
   tableau[i] = calloc(colonnes, sizeof(int));
  }
  for ( int i = 0; i < lignes; i++) {
    for ( int j = 0; j < colonnes; j++) {
       tableau[i][j] = i+j;
    }
  }
...

2.19. Le passage de paramètres

Passage par référence: un tableau

...
  affichage(tableau, lignes, colonnes);
  for ( int i = 0; i < lignes; i++) {
   free(tableau[i]);
  }
  free(tableau);
  return 0;
}

Remarque : Le tableau tableau a été passé par référence à la fonction affichage, de sorte que toute modification apportée à tableau à l'intérieur de cette fonction serait également répercutée sur le tableau d'origine.

2.20. Les structures et les pointeurs

struct couleur{
 unsigned char rouge;
 unsigned char vert;
 unsigned char bleu;
 unsigned int compteur;
};
int main() {
 struct couleur c1;
 struct couleur *scptr = &c1;
 c1.bleu = 0xff;
 scptr->bleu = 0x01;
 printf( "c1.bleu: %hhx\n", c1.bleu); // affiche 1
 (*scptr).bleu = 0x22;
 printf( "c1.bleu: %hhx\n", c1.bleu); // affiche 22
}

scptr->bleu = 0x01 modifie également le membre bleu, mais à travers le pointeur scptr. scptr->bleu et (*scptr).bleu sont équivalents : la deuxième affectation écrit 0x22 au même endroit.

synthèse additive des couleurs
Source: https://commons.wikimedia.org/wiki/File:AdditiveColorMixiing.svg

2.20. Les structures et les pointeurs

 struct couleur *scptr = &c1;

Les notations équivalents

(*scptr).bleu scptr->bleu

L'opérateur -> combine les opérations de déréférencement et d'accès au membre en une seule étape. Cela rend le code plus lisible et réduit les risques d'erreurs de déréférencement. Les deux notations sont interchangeables.

2.20. Les structures et les pointeurs

struct couleur{
 unsigned char rouge;
 unsigned char vert;
 unsigned char bleu;
 unsigned int compteur;
};
void change( struct couleur *c) {
 c->bleu = 0x01;
}
int main() {
 struct couleur c1;
 c1.bleu = 0xff;
 change(&c1);
 printf( "c1.bleu: %hhx\n", c1.bleu); // affiche 1
}

Un membre d'une structure est modifié en utilisant une fonction qui prend un pointeur vers la structure en argument. Cette approche permet de manipuler directement la structure à l'intérieur de la fonction sans avoir besoin de renvoyer la structure modifiée, car les pointeurs permettent de travailler avec la même instance de la structure.

2.20. Les structures et les pointeurs

struct couleur{
 unsigned char rouge;
 unsigned char vert;
 unsigned char bleu;
 unsigned int compteur;
};
void nochange( struct couleur c) {
 c.bleu = 0x03;
}
int main() {
 struct couleur c1;
 c1.bleu = 0xff;
 nochange(c1);
 printf( "c1.bleu: %hhx\n", c1.bleu); // affiche ff
}

La fonction nochange est censée modifier le membre bleu de la structure c pour lui attribuer la valeur 0x03. Cependant, la fonction est déclarée pour prendre la structure c en tant que copie (par valeur) au lieu d'un pointeur. Par conséquent, lorsqu'elle est appelée dans la fonction main, une copie de la structure c1 est passée à la fonction nochange. Toute modification apportée à cette copie n'affecte pas la structure d'origine.

2.20. Les structures et les pointeurs

Une liste de couleurs simplement chaînée

Une liste simplement chaînée est une structure de données linéaire composée de nœuds, où chaque nœud contient une valeur et une référence (pointeur) vers le nœud suivant. Elle est utilisée pour stocker des éléments de manière séquentielle, offrant une manipulation efficace des données en insérant ou supprimant des éléments en temps constant, mais avec un accès moins efficace aux éléments au milieu de la liste.

2.20. Les structures et les pointeurs

Une liste de couleurs simplement chaînée

Chaque nœud de la liste contient également un pointeur vers le nœud suivant de la liste, permettant de stocker et de naviguer à travers une séquence de couleurs. Cette structure est souvent utilisée pour représenter une séquence de couleurs dans des applications graphiques ou de traitement d'images.

struct couleur{
 unsigned char rouge;
 unsigned char vert;
 unsigned char bleu;
 unsigned int compteur;
 struct couleur *next;
};

2.20. Les structures et les pointeurs

Une liste de couleurs simplement chaînée

int main() {
 struct couleur first = {0}, c1 = {0}, c2 = {0}; // sinon bleu est indéterminé
 struct couleur *cptr;
 first.next = &c1;
 c1.next = &c2;
 c2.next = NULL;
 cptr = &first;

 while(cptr != NULL) { //navigation
  printf( "cptr->bleu: %hhx\n", cptr->bleu);
  cptr = cptr->next; //couleur suivante
 }
}

Un pointeur cptr est utilisé pour parcourir la liste, et à chaque étape, la valeur du composant "bleu" de la couleur actuelle est affichée à l'aide de printf. La boucle continue jusqu'à ce que le pointeur atteigne la fin de la liste, ce qui permet de parcourir et d'afficher les composants "bleu" de toutes les couleurs de la liste.

2.20. Les structures et les pointeurs

Une liste d'entiers simplement chaînée

2.20. Les structures et les pointeurs

Une liste d'entiers simplement chaînée

Chaque élément de la liste est représenté par la structure element, qui contient un numéro entier (numero) et un pointeur vers l'élément suivant (suivant). Deux fonctions sont fournies : insertion pour ajouter un nouvel élément à la liste et parcours pour parcourir et afficher les éléments de la liste. Cela permet de construire et de manipuler une liste d'entiers simplement chaînée.

struct element{
 unsigned int numero;
 struct element *suivant;
};

// insertion d'un élement dans une liste
void insertion(struct element*, int);

// parcours de la liste
void parcours(struct element *);

2.20. Les structures et les pointeurs

La saisie d'entiers par l'utilisateur

int main() {
 struct element premier;
 premier.suivant = NULL;
 while(1) {
   int numero;
   char strnum[50];
   if (fgets(strnum, sizeof(strnum),
     stdin) == NULL) break;
   if(strcmp(strnum, "FIN\n") == 0) {
     break;
   }
   sscanf(strnum, "%d\n", &numero);
   insertion(&premier, numero);
 }
 parcours(&premier);
}

2.20. Les structures et les pointeurs

Une liste d'entiers simplement chaînée: insertion

2.20. Les structures et les pointeurs

Insertion d'un élément dans une liste simplement chaînée

L'objectif de cette fonction est d'insérer un nouvel élément dans une liste chaînée en créant un nouvel élément, en assignant une valeur à ce nouvel élément, et en ajustant les pointeurs pour l'insérer correctement dans la liste existante, ce qui permet de modifier la structure de la liste chaînée.

void insertion(struct element *premier, int numero) {
  struct element *nouveau;
  nouveau = malloc(sizeof(*nouveau));
  if (nouveau == NULL) return;
  nouveau->numero = numero;
  nouveau->suivant = premier->suivant;
  premier->suivant = nouveau;
}
 

2.20. Les structures et les pointeurs

Une liste d'entiers simplement chaînée: parcours

2.20. Les structures et les pointeurs

Parcours d'une liste simplement chaînée

void parcours(struct element *premier) {
   struct element *elem = premier->suivant;
   // premier est une sentinelle :
   // son numero n'est jamais initialisé
   while(elem != NULL) {
   printf("%u\n", elem->numero);
   elem = elem->suivant;
  }
 }

2.20. Les structures et les pointeurs

Une liste de couleurs doublement chaînée

Une liste doublement chaînée a pour objectif de permettre la navigation dans une structure de données linéaire de manière bidirectionnelle, offrant un accès à la fois vers l'élément précédent et l'élément suivant, ce qui facilite l'insertion, la suppression et la recherche efficace des éléments.

2.20. Les structures et les pointeurs

Une liste de couleurs doublement chaînée

struct couleur{
 unsigned char rouge;
 unsigned char vert;
 unsigned char bleu;
 unsigned int compteur;
 struct couleur *next;
 struct couleur *prev;
};

L'objectif de cette structure de données est de créer une liste doublement chaînée de couleurs, où chaque élément conserve des informations sur la couleur, un compteur, ainsi que des pointeurs vers l'élément précédent et l'élément suivant. Cela permet une navigation bidirectionnelle efficace et des opérations telles que l'insertion et la suppression d'éléments au sein de la liste chaînée.

2.20. Les structures et les pointeurs

int main() {
 struct couleur first = {0}, c1 = {0}, c2 = {0}, last = {0};
 struct couleur *cptr = &last;
 first.next = &c1;
 first.prev = NULL;
 last.next = NULL;
 last.prev = &c2;
 c1.next = &c2;
 c1.prev = &first;
 c2.next = &last;
 c2.prev = &c1;
 while(cptr != &first) { //navigation
  printf( "cptr->bleu: %hhx\n", cptr->bleu);
  cptr = cptr->prev; //couleur précédente
 }
}

2.20. Les structures et les pointeurs

Une liste d'entiers simplement chaînée

2.20. Les structures et les pointeurs

Insertion d'un élément et parcours d une liste doublement chaînée

 struct element {
   int num;
   struct element *suivant;
   struct element *precedent;
 };
 
 struct liste {
   struct element premier;
   struct element dernier;
 };
 
 void insertion_debut(struct liste *, struct element *);
 void insertion_fin(struct liste *, struct element *);
 void parcourir_debut(struct liste *);
 void parcourir_fin(struct liste *);

2.20. Les structures et les pointeurs

Une liste de couleurs doublement chaînée: insertion

2.20. Les structures et les pointeurs

Une liste d'entiers doublement chaînée

 void insertion_debut(struct liste *liste,
   struct element *nouveau) {
  nouveau->suivant = liste->premier.suivant;
  nouveau->precedent = &liste->premier;
  liste->premier.suivant->precedent = nouveau;
  liste->premier.suivant = nouveau;
 }
 

2.20. Les structures et les pointeurs

Une liste de couleurs doublement chaînée: insertion

2.20. Les structures et les pointeurs

Une liste d'entiers doublement chaînée

 void insertion_fin(struct liste *liste,
    struct element *nouveau) {
  nouveau->suivant = &liste->dernier;
  nouveau->precedent = liste->dernier.precedent;
  liste->dernier.precedent->suivant = nouveau;
  liste->dernier.precedent = nouveau;
 }

2.20. Les structures et les pointeurs

Une liste d'entiers doublement chaînée

 void parcourir_debut(struct liste *liste) {
  struct element *elem = liste->premier.suivant;
  while(elem != &liste->dernier) {
   printf("%d\n", elem->num);
   elem = elem->suivant;
  }
 }
 

2.20. Les structures et les pointeurs

Une liste d'entiers doublement chaînée

 
 void parcourir_fin(struct liste *liste) {
  struct element *elem = liste->dernier.precedent;
  while(elem != &liste->premier) {
   printf("%d\n", elem->num);
   elem = elem->precedent;
  }
 }

2.20. Les structures et les pointeurs

La saisie d'entiers par l'utilisateur

 int main() {
  struct liste liste;
  liste.premier.suivant = &liste.dernier;
  liste.dernier.precedent = &liste.premier;
  liste.premier.precedent = NULL;
  liste.dernier.suivant = NULL;
 

2.20. Les structures et les pointeurs

La saisie d'entiers par l'utilisateur

  while (1) {
   char strnum[50];
   if (fgets(strnum, sizeof(strnum),
      stdin) == NULL) break;
   if(strcmp(strnum, "FIN\n") == 0) {
    break;
   }
 
   // alloué après le test : sinon fuite
   struct element *elem = malloc(sizeof(*elem));
   if (elem == NULL) break;
   if (sscanf(strnum, "%d", &elem->num) != 1)
      { free(elem); continue; }
   insertion_fin(&liste, elem);
  }
 
  parcourir_debut(&liste);
  parcourir_fin(&liste);
 }

2.21. C23, pointeurs et sûreté

Des outils pour expliciter l'intention

Ces ajouts ne suppriment pas les erreurs de mémoire, mais ils aident le compilateur, les outils d'analyse et le programmeur à mieux détecter certaines intentions ou anomalies.

2. Principes des Langages de Programmation : Références

Références

Crédits d'images