Algorithmes et C appliqués aux Systèmes Numériques
Les pointeurs, les structures et les fonctions

John Samuel
CPE Lyon

Année: 2023-2024
Courriel: john(dot)samuel(at)cpe(dot)fr

Creative Commons License

2.1. Objectifs

Objectifs

2.2. Microprocesseur

Architecture

L'architecture d'une machine influence la programmation en C en déterminant les détails de la gestion de la mémoire, la taille des types de données, l'ordre d'exécution des instructions (gestion des threads sur un processeur multicœur), et d'autres caractéristiques matérielles (par exemple, la taille des registres), ce qui peut nécessiter des ajustements spécifiques lors de la rédaction de code pour différentes plateformes matérielles.

2.2. Microprocesseur

Architecture

Une architecture n-bit peut adresser une mémoire de taille 2n.

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 dans une architecture détermine sa capacité à adresser et à gérer la mémoire, ce qui est essentiel pour la performance et la capacité des systèmes informatiques.

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.

la conversion des adresses virtuelles en adresses physiques
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

Les liens entre les pointeurs en C, les adresses virtuelles et les adresses physiques

2.7. Pointeurs

Les liens entre les pointeurs en C, les adresses virtuelles et les adresses physiques

2.7. Pointeurs

Les liens entre les pointeurs en C, les adresses virtuelles et les adresses 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 short 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 short 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 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 int
une variable float

2.7. Pointeurs

float *

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

Remarque: my_float_addr = 0xab71

une variable int
une variable float

2.7. Pointeurs

double *

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

Remarque: my_double_addr = 0xab71

une variable int
une variable double

2.7. Pointeurs

double *

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

Remarque: my_double_addr = 0xab71

une variable int
une variable double

2.7. Pointeurs

printf: Afficher à l'écran

Type Code de conversion
pointeur %p

  printf("char : %p\n", my_char_addr);
  printf("short : %p\n", my_short_addr);
  printf("int : %p\n", my_int_addr);
  printf("long int : %p\n", my_long_int_addr);
  printf("float : %p\n", my_float_addr);
  printf("double : %p\n", 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;

Dans ce contexte, * n'est pas un opérateur de déréférencement, mais plutôt une partie de la déclaration du pointeur.

Utilisation du pointeur : Lorsque vous utilisez un pointeur pour accéder à la valeur pointée, l'opérateur * est utilisé comme opérateur de déréférencement. Par exemple :

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

L'opérateur * est utilisé pour obtenir la valeur de la variable à laquelle my_char_addr pointe, c'est-à-dire la valeur 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 et les tableaux

Pointeurs pour des blocs continus

Un tableau est un ensemble d'éléments homogènes.

char a[8] = {0xff,0x65,0x78,
 0xa4,0x0,0x0,0x0,0x0}
;
char *ptr = &a[0];
une variable int
un tableau de caractères

2.9. Les pointeurs et les tableaux

L'opérateur d'addition sur les pointeurs est utilisé pour déplacer un pointeur vers une autre adresse mémoire en fonction du type de données auquel il pointe.

char a[8] = {0xff,0x65,0x78,
 0xa4,0x0,0x0,0x0,0x0}
;
char *ptr = &a[0];
ptr = ptr + 1;
une variable int
un tableau de caractères

2.9. Les pointeurs et les tableaux

char a[8] = {0xff,0x65,0x78,
 0xa4,0x0,0x0,0x0,0x0}
;
char *ptr = &a[0];
ptr = ptr + 1;
une variable int
un tableau de caractères

2.9. Les pointeurs et les tableaux

L'opérateur d'addition sur les pointeurs est utilisé pour déplacer un pointeur vers une autre adresse mémoire en fonction du type de données auquel il pointe.

short s[4] = {0x65ff,0xa478};
short *sptr = &s[0];

Remarque: Observez bien la position de sptr.

une variable int
un tableau d'entier

2.9. Les pointeurs et les tableaux

Remarque: Observez bien la position de sptr.

short s[4] = {0x65ff,0xa478};
short *sptr = &s[0];
sptr = sptr + 1;
une variable int
un tableau d'entier

2. Questions

Imaginez les trois variables suivantes: int *iptr, float *fptr, double *dptr . Elles ont toutes la même valeur (0x4).

iptr = iptr + 2;
fptr = fptr + 2;
dptr = dptr + 2;

Donnez les valeurs de iptr, fptr, dptr.

2.10. Tableaux: indices

Les indices pour les pointeurs permettent d'accéder aux éléments d'un tableau à l'aide de pointeurs.

short s[4] = {0x65ff,0xa478};
short *sptr = &s[0];

ou

short *sptr = s;

Les indices pour les pointeurs vous permettent de parcourir et de manipuler les éléments d'un tableau en utilisant des pointeurs, ce qui est utile pour travailler avec des tableaux dynamiques ou pour itérer à travers leurs éléments.

2.10. Tableaux: indices

Les indices pour les pointeurs permettent d'accéder aux éléments d'un tableau à l'aide de pointeurs.

short s[4] = {0x65ff,0xa478};
short *sptr = s;
printf( "%hd", *(sptr+3))

ou

printf( "%hd", *(s+3))

ou

printf( "%hd", s[3])

ou

printf( "%hd", sptr[3])

2.11. Les pointeurs: notations

Les notations indicielles et pointeurs

Ces notations permettent de manipuler les éléments d'un tableau en utilisant soit des indices (notation indicielle) soit des pointeurs (notation pointeur).

short s[2] = {0x65ff,0xa478};
Notation indicielle Notation pointeur
&s[0] s
&s[i] s+i
s[0] *s
s[i] *(s+i)

2.12. Les opérateurs et les pointeurs

short *sptr1, *sptr2;
Opérateur Exemple
pointeur + entier sptr1 + 2
pointeur - entier sptr1 - 2
pointeur1 - pointeur2 sptr1 - sptr2

Ces opérateurs sont souvent utilisés en programmation pour la manipulation efficace de données en mémoire, notamment lors de l'accès aux éléments d'un tableau ou lors de la gestion de la mémoire dynamique.

2.13. Les pointeurs génériques

void *

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

$ gcc ...
warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
cptr = &i;

2.13. 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.14. Conversion de type

Sans perte d'information

La conversion de type, également appelée "cast", consiste à changer le type d'une variable d'un type de données à un autre. Cependant, il est important de noter que certaines conversions peuvent entraîner une perte d'information, tandis que d'autres non.

short s = 0xffff;
int i = s;

2.14. Conversion de type

Avec perte d'information

Exemple

int i = 0xffff;
float f = i;

Remarque: N'oubliez pas d'utiliser sizeof.

2.14. 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 = 0xffff;
float f = 30.999;
int i = (int) s;
int j = (int) f;

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

2.14. 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.15. 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.15. 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.15. Les Structures

Base de données de gestion des étudiants

2.15. Les Structures

Base de données de gestion des étudiants

2.15. 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.15. 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 = { "Dupont", "Pierre", "Boulevard du 11 novembre 1918", "Villeurbanne", 19};

2.15. Les structures

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

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

struct etudiant dupont;

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

2.15. 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, "Dupont");
strcpy(etudiant_cpe[0].nom, "Pierre");
strcpy(etudiant_cpe[0].rue, "Boulevard du 11 novembre 1918");
strcpy(etudiant_cpe[0].ville, "Villeurbanne");
etudiant_cpe[0].notes = 19;

2.15. 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.15. 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.15. 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, "Dupont");
strcpy(etudiant_cpe[0].nom, "Pierre");
strcpy(etudiant_cpe[0].adresse.rue, "Boulevard du 11 novembre 1918");
strcpy(etudiant_cpe[0].adresse.ville, "Villeurbanne");
etudiant_cpe[0].notes = 19;

2.16. Typedef

Créer (et Renommer) un type

Le mot-clé typedef est utilisé pour créer et renommer un nouveau type de données, en l'occurrence ici, un type de données structuré appelé etudiant qui contient des informations sur un étudiant, y compris son prénom, nom, adresse et notes.

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

2.16. Typedef

Renommer un type

Le mot-clé typedef peut être utilisé pour renommer des types de données de base, tels que les types intégrés comme int, char, float, etc.

typedef int entier;

Les déclarations

entier est un alias pour le type de données int, ce qui signifie que vous pouvez utiliser integer pour déclarer des variables de type entier.

entier i = 10;

etudiant est un alias pour le type de données struct etudiant. La déclaration etudiant dupont crée une instance.

etudiant dupont;

2.17. Fonctions

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


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

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

2.17. 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.17. 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.17. 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.17. 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.17. 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.17. 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.17. Fonctions

La compilation

$ gcc -o bonjour bonjour3.c nom.c

L'exécution

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

Remarque: un seul fichier C peut avoir une fonction nommée 'main'.

2.18. 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.18. 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);
 print(argv[1], 20);
 print(argv[2], 20);
 print(argv[3], 20);
 print(argv[4], 20);
 return 0;
}

2.18. 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.18. L'interface en ligne de commande

2.18. L'interface en ligne de commande

La compilation

2.18. 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 toujours le nom de la fichier exécutable (e.g., bonjour)

2.19. La saisie d'entiers par l'utilisateur

/* Fichier: addition.c
* affichage de la somme des nombres
* saisis par l'utilisateur */

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

int main(int argc, char ** argv) {
 int num1, num2;
 printf("Tapez numéro 1");
 scanf("%d", &num1);
 printf("Tapez numéro 2");
 scanf("%d", &num2);
 printf("La somme: %d\n", num1 + num2);
 return 0;
}

L'objectif de la fonction scanf est de permettre à un programme de lire et de stocker des données à partir de l'entrée standard (généralement le clavier) ou d'autres flux d'entrée, en fonction d'un format spécifié.

Remarque: Regardez l'opérateur &.

2.19. La saisie d'entiers par l'utilisateur

/* Fichier: addition.c
* affiche la somme de deux numéros
* saisis par l'utilisateur
* auteur: John Samuel
*/


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

int main(int argc, char ** argv) {
 int num1, num2;
 printf("Tapez deux numéros: ");
 scanf("%d %d", &num1, &num2);
 printf("La somme: %d\n", num1 + num2);
 return 0;
}

Remarque: Regardez l'opérateur &. L'opérateur & est utilisé pour obtenir l'adresse mémoire des variables num1 et num2, de sorte que les valeurs entrées par l'utilisateur puissent être stockées à ces emplacements en mémoire.

2.20. La saisie d'un mot par l'utilisateur

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


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

int main(int argc, char ** argv) {
 char nom[50];
 printf("Bonjour. Votre nom? ");
 scanf("%s", nom);
 printf("Bonjour %s!", nom);
 return 0;
}

Remarque: Regardez nom sans l'opérateur &. Lorsque vous utilisez la fonction scanf pour lire une chaîne de caractères à l'aide du format "%s", vous n'avez pas besoin d'inclure le symbole & devant la variable nom. La raison en est que nom est déjà un pointeur vers un tableau de caractères (c'est-à-dire un pointeur vers la première case mémoire du tableau), et scanf s'attend à recevoir l'adresse de la mémoire où stocker la chaîne de caractères. Par conséquent, nom en tant que pointeur fournit cette adresse.

2.21. La saisie par l'utilisateur: scanf

Mots clés Code de conversion
char c
unsigned char hhu
short hd
unsigned short hu
int d, i
unsigned int u
long int ld
unsigned long int lu

2.21. La saisie par l'utilisateur: scanf

Mots clés Code de conversion
long long int lld
unsigned long long int llu
float f, F
double g, G
long double Lg
string of characters s

2.22. fgets: la saisie d'une phrase par l'utilisateur

/* Fichier: fgets-message.c
* affichage d'une phrase entrée par un utilisateur
* la phrase peut contenir un espace
* auteur: John Samuel
*/


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

int main(int argc, char ** argv) {
 char message[50];
 printf("Tapez votre message: ");
 fgets(message, sizeof(message), stdin);
 printf("Votre message: %s\n", message);
 return 0;
}

Remarque: Regardez le paramètre stdin.La fonction fgets est utilisée pour lire une ligne de texte depuis l'entrée standard ( stdin) et stocker cette ligne dans la variable message. La taille du tampon de destination ( message) est spécifiée par sizeof(message), ce qui permet de garantir que la fonction ne lira pas plus de caractères que ce que le tampon peut contenir, évitant ainsi les dépassements de mémoire.

2.23. Les manuels Linux

Les manuels Linux, accessibles via la commande man, fournissent une documentation détaillée sur les commandes, les fonctions système et les fonctions de bibliothèque disponibles dans Linux. Par exemple, man ls affiche la documentation de la commande ls, man 3 scanf présente des informations sur la fonction scanf de la bibliothèque C.

$ man ls
$ man 3 scanf
$ man 3 printf
$ man 2 open
$ man fopen
..

Les chiffres suivant man indiquent la section du manuel Linux où vous trouverez des informations spécifiques : la section 1 concerne les commandes, la section 2 concerne les appels système, la section 3 concerne les appels de bibliothèque C, et ainsi de suite. Cela permet de rechercher efficacement des informations sur des sujets particuliers dans la documentation Linux.

2.23. Les manuels Linux

Le manuel commence par la section NAME, qui indique le nom de la fonction ou de la commande couverte. La section SYNOPSIS fournit une syntaxe de base pour l'utilisation (de scanf). La section DESCRIPTION explique en détail comment la fonction est utilisée et à quoi elle sert. Enfin, la section RETURN VALUE décrit les valeurs de retour possibles de la fonction. Ces sections sont courantes dans les manuels Linux et aident les utilisateurs à comprendre et à utiliser efficacement les commandes et les fonctions.

NAME/NOM
 scanf
 ...
SYNOPSIS
 ...
DESCRIPTION
 ...
RETURN VALUE/VALEUR DE RETOUR/RETOURNE
 ...
..

2.24. La manipulation d'une chaine de caractères

/* Fichier: string.c
* manipulation d'une chaine 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: %lu\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.24. La manipulation d'une chaine de caractères

La compilation

$ gcc -o strlen string.c

1ere Exécution

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

2.24. La manipulation d'une chaine 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.24. La manipulation d'une chaine de caractères

/* Fichier: string.c
* manipulation d'une chaine 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: %lu\n", strnlen(nom, sizeof(nom)));
 return 0;
}

Objectif : La fonction strnlen a pour objectif de calculer la longueur d'une chaîne de caractères en comptant le nombre de caractères présents jusqu'à ce qu'elle atteigne une taille maximale spécifiée. Elle permet ainsi de déterminer la longueur d'une chaîne tout en évitant les dépassements de mémoire potentiels. Cela aide à garantir la sécurité et la stabilité du programme en s'assurant qu'il n'essaie pas de lire au-delà de la taille maximale autorisée pour la chaîne de caractères.

2.24. La manipulation d'une chaine de caractères

/* Fichier: string.c
* scanf("%[0-9]s",...).
* 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: %lu\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.24. La manipulation d'une chaine 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: %lu\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.24. La manipulation d'une chaine 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: %lu\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.24. La manipulation d'une chaine de caractères

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


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

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.

2.24. La manipulation d'une chaine 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[10] = "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 points-virgules, puis stocke ces valeurs dans les variables inum1, fnum2, et fnum3.

2.24. La manipulation d'une chaine 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[10] = "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 espaces, puis stocke ces valeurs dans les variables inum1, fnum2, et fnum3.

2.24. La manipulation d'une chaine de caractères

La compilation

$ gcc -o strnlen string2.c

1ere Exécution

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

2.24. La manipulation d'une chaine de caractères

2.24. La manipulation d'une chaine de caractères

/* Fichier: string3.c
* manipulation d'une chaine 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", message));
 return 0;
}

2.24. La manipulation d'une chaine de caractères

/* Fichier: string4.c
* manipulation d'une chaine 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", message));
 return 0;
}

2.24. La manipulation d'une chaine 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.25. La manipulation de fichiers

Les fonctions open, read, write, et close sont couramment utilisées pour manipuler des fichiers.

2.25. La manipulation de fichiers

/* Fichier: file.c
* Lecture d'un fichier
*/

#include <stdio.h> // en-têtes(headers)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char ** argv) {
 char content[1000];
 int fd, count, size;

 fd = open ("./file.c", O_RDONLY);
 size = read(fd, content, 1000);
 for (count = 0; count < size; count ++) {
  printf("%c", content[count]);
 }
 close(fd);
 return 0;
}

2.25. La manipulation de fichiers

/* Fichier: file.c
* Lecture d'un fichier
*/

#include <stdio.h> // en-têtes(headers)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char ** argv) {
 char content;  int fd, count, size;
 fd = open ("./file.c", O_RDONLY);
 while (1) {
  size = read(fd, &content, 1);
  if (size < 1) {
   break;
  }
  printf("%c", content);
 }
 close(fd);
 return 0;
}

2.25. La manipulation de fichiers

/* Fichier: file.c
* Écriture dans un fichier en mode ajout
* auteur: John Samuel
*/


#include <stdio.h> // en-têtes(headers)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char ** argv) {
 char content[] = "Bonjour";
 int fd, count, size;

 fd = open ("./message.txt", O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR);
 size = write(fd, content, sizeof(content));
 close(fd);
 return 0;
}

2.25. La manipulation de fichiers

/* Fichier: file.c
* Écriture dans un fichier en mode ajout
*/

#include <stdio.h> // en-têtes(headers)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char ** argv) {
 char content[] = "Bonjour";
 int fd, count, size;

 fd = open ("./message.txt", O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR);
 size = write(fd, content, sizeof(content));
 close(fd);
 return 0;
}

Lorsqu'on utilise l'option O_APPEND avec la fonction open en programmation, cela permet d'ouvrir un fichier en mode ajout, ce qui signifie que toutes les données écrites dans le fichier seront ajoutées à la fin existante plutôt que d'écraser le contenu existant.

2.26. 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.26. 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>

int main(int argc, char ** argv) {
 char *content = malloc(10 * sizeof(char));
 strncat(content, "Bonjour", 10);
 free(content); //libération de mémoire
 return 0;
}

2.26. 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>

int main(int argc, char ** argv) {
 char *content = calloc(10, sizeof(char));
 strncat(content, "Bonjour", 10);
 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.27. Les unions

Union

Une union est declarée comme une structure. L'objectif d'une union en programmation est de permettre le partage d'un même espace mémoire par plusieurs variables de types différents. La taille de l'union est déterminée par la taille de son élément le plus grand, ce qui permet d'économiser de la mémoire en ne réservant qu'un seul emplacement mémoire pour plusieurs utilisations potentielles.

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

Remarque: La structure union `etudiant` peut stocker différentes informations telles que le prénom, le nom, la rue et la ville, mais elle ne peut stocker qu'un seul de ces éléments à la fois.

2.27. Les unions

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

union etudiant dupont;

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

L'affichage

Dupont
Dupont
Dupont

Remarque: Tous les élements partagent la même espace de mémoire.

2.27. Les unions

Avantages

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

2.27. Les unions

/* Fichier: memory3.c
* Allocation dynamique de mémoire */

#include <stdio.h> // en-têtes(headers)
#include <stdlib.h>
int main(int argc, char ** argv) {
 struct content c;
 strcpy(c.type, "char", 10);
 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));
 }
 return 0;
}

2.27. Les unions

Point3D

La structure union point3d permet de stocker des coordonnées tridimensionnelles en utilisant soit un tableau d'entiers de taille 3 ( value[3]) soit une structure avec des membres x, y, et z. Cela signifie que vous pouvez accéder aux coordonnées de deux manières différentes, en fonction de l'utilisation. Par exemple, vous pouvez utiliser point3d.value[0] pour accéder à la coordonnée x en utilisant le tableau d'entiers, ou point3d.x pour accéder à la coordonnée point3d.value[0] en utilisant la structure. Cela offre une certaine flexibilité lors de la manipulation de coordonnées tridimensionnelles, selon le contexte de votre programme.

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

2.27. 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;
}

2. Question

Écrivez une structure pour la répresentation d'une couleur RGB
(rouge, vert, bleu: chaque couleur prend un octet) en utilisant union et struct.

Source: https://commons.wikimedia.org/wiki/File:AdditiveColorMixiing.svg

2.28. Débogage et test

Le code suivant (erreurs.c) présente une erreur majeure : il tente d'accéder à des indices de tableau supérieurs à la taille maximale du tableau (100).

#include <stdio.h>

int main() {

   int tableau[100];

   for (int compteur = 0; compteur < sizeof(tableau); compteur++) { //Erreur
       tableau[compteur] = tableau[compteur] * 2;
   }

   return (0);

}

2.28. Débogage et test

Pour la compilation et la création d'un exécutable

		
$ gcc erreurs.c
		

Lorsque nous exécutons le code, nous obtenons l'erreur suivante et le programme se plante (comme prévu).

		
./a.out
*** stack smashing detected ***: terminated
fish: Job 1, './a.out' terminated by signal SIGABRT (Abort)
		

2.28. Débogage et test

Pour déboguer ce code et trouver la source de l'erreur, nous devrons d'abord le compiler et ajouter des informations supplémentaires pour le débogage avec l'option `-ggdb3`.

		
$ gcc --ggdb3 erreurs.c
		

2.28. Débogage et test

Nous allons maintenant exécuter le code avec gdb.

		
$ gdb a.out
GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
(gdb)
		

2.28. Débogage et test

Une fois que vous exécutez gdb, vous verrez une autre ligne de commande avec `(gdb)`. Tapez `r` pour exécuter le programme.

		

(gdb) r
Starting program: ./ProgC/gdb/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
*** stack smashing detected ***: terminated

Program received signal SIGABRT, Aborted.
__pthread_kill_implementation (no_tid=0, signo=6, threadid=140737351481152) at ./nptl/pthread_kill.c:44
44	./nptl/pthread_kill.c: No such file or directory.
		

2.28. Débogage et test

Le programme s'est écrasé et pour trouver la ligne qui est la raison de l'écrasement, tapez `bt`.

		
(gdb) bt
#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=140737351481152) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=140737351481152) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=140737351481152, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3  0x00007ffff7dbc476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7da27f3 in __GI_abort () at ./stdlib/abort.c:79
#5  0x00007ffff7e036f6 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ffff7f55943 
    "*** %s ***: terminated\n")
    at ../sysdeps/posix/libc_fatal.c:155
#6  0x00007ffff7eb076a in __GI___fortify_fail (msg=msg@entry=0x7ffff7f5592b "stack smashing detected") 
    at ./debug/fortify_fail.c:26
#7  0x00007ffff7eb0736 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
#8  0x00005555555551c1 in main () at erreurs.c:13
(gdb)
		

2.28. Débogage et test

Nous voyons que l'erreur se situe à la ligne 13 du programme (erreurs.c). Vous pouvez maintenant quitter gdb en utilisant `quit`, corriger le code et répéter les étapes ci-dessus jusqu'à ce que l'erreur soit résolue.

		
(gdb) quit
A debugging session is active.

	Inferior 1 [process 3886] will be killed.

Quit anyway? (y or n) y
		

2. Programmation en C: Références

Références

Crédits d'images