Programmation C source/types avancés
Apparence
#include <stdio.h>
/* inclut stdio.h qui décrit
- la fonction printf */
#include <stddef.h>
/* inclut stddef.h qui décrit
- la fonction offsetof */
#include <string.h>
/* inclut string.h qui décrit
- la fonction memcmp */
#include <stdlib.h>
/* inclut stdlib.h qui décrit
- la fonction malloc */
int main(void)
{
printf("--------------------\n");
printf(" Structures\n");
printf("--------------------\n");
struct ma_structure {
char champ1;
int champ2;
double champN;
} var1, var2, varM;
/* crée 3 variables var1, var2 et varM
de type struct ma_structure */
printf("\n--------------------\n");
printf(" Accès aux champs\n");
printf("--------------------\n");
var1.champ1 = 'x';
var1.champ2 = 3;
var1.champN = 4.6;
/* accès aux champs de la variable var1
de type ma_structure */
printf("\n--------------------\n");
printf(" Initilisation\n");
printf(" lors de la\n");
printf(" déclaration\n");
printf("--------------------\n");
struct ma_structure var3 = { 'a', 12345, 0.123 };
/* déclaration de var3 de type ma_structure
et initialisation var3.champ1 = a,
var3.champ2 = 12345, etc.
si on oublie des champs ils seront mis à 0
attention l'initialisation doit se faire dans l'ordre */
struct ma_structure var4 = { .champ2 = 6789, .champ1 = 'b', .champN = 12.3 };
/* valide en C version C99
on initialise ainsi dans le désordre les champs
si on oublie des champs ils seront mis à 0 */
printf("\n--------------------\n");
printf(" Opération de copie\n");
printf("--------------------\n");
struct ma_structure var5 = {};
/* déclaration de var5 de type struct ma_structure
et initialisation à zero */
printf("var5.champ1 = %c\n", var5.champ1);
printf("var5.champ2 = %d\n", var5.champ2);
printf("var5.champN = %f\n", var5.champN);
var5 = var4;
// copie de var4 dans var5
printf("\nvar5.champ1 = %c\n", var5.champ1);
printf("var5.champ2 = %d\n", var5.champ2);
printf("var5.champN = %f\n", var5.champN);
printf("\n--------------------\n");
printf(" Une structure\n");
printf(" en mémoire\n");
printf("--------------------\n");
struct ma_structure1 {
char champ1; // 8 bits
int champ2; // 32 bits
char champ3; // 8 bits
};
struct ma_structure1 ma_variable1;
/* Comment est placée une structure en mémoire ?
mémoire :
| bloc N | bloc N + 1 | bloc N + 2 |
---+---------------+---------------+---------------+---
| a | b | c | d | e | f | g | h | i | j | k | l |
---+---------------+---------------+---------------+---
les cases a, b, c, ... représentent des octets
les blocs des sections de 32 bits
on suppose qu'une variable ma_variable1 de type struct ma_structure1
doive être placée en mémoire à partir du bloc numéro N
alors un compilateur pourra, pour des raisons de performance,
placer champ1 en a, champ2 de e à h, et champ3 en i
Cela permettrait en effet d'accéder simplement à champ1, champ2, champ3:
le processeur fournit des instructions permettant de lire
ou d'écrire directement le bloc N, N + 1, etc.
Dans ce cas, les octets de b à d ne sont pas utilisés;
on dit alors que ce sont des octets de bourrage (ou padding en anglais)
Un autre compilateur (ou le même, appelé avec des options différentes)
peut aussi placer champ2 de b à e, et champ3 en f, pour optimiser l'utilisation mémoire.
Mais alors il devra générer un code plus complexe lors des accès à champ2,
le matériel ne lui permettant pas d'accéder en une seule instruction aux 4 octets b à e.
En fait il faut garder à l'esprit que toutes les variables suivent cette contrainte:
aussi bien les variables locales aux fonctions, les champs de structures,
les paramètres de fonctions, etc.
L'existence d'octets de bourrage ainsi que leur nombre sont
non seulement dépendants de l'architecture,
mais aussi du compilateur. */
printf("L'offset de champ2 vaut %zu.\n", offsetof(struct ma_structure1, champ2));
/* offsetof connaître la distance (en anglais offset) d'un champ
par rapport au début de la structure
la valeur renvoyée est le nombre de char
ceci est valable pour C norme C99 */
printf("L'offset de champ2 vaut %lu.\n", (unsigned long) offsetof(struct ma_structure1, champ2));
// en C90
// La plupart du temps offsetof est écrit comme ceci :
size_t distance = (size_t) &((struct ma_structure1 *)NULL)->champ2;
/* explication :
NULL est l'adresse nulle
or un pointeur est/contient une adresse
(struct ma_structure1 *)NULL transforme NULL en pointeur de type struct ma_structure1
on demande champ2 à ce pointeur ((struct ma_structure1 *)NULL)->champ2
et plus précisément l'adresse de champ2 &((struct ma_structure1 *)NULL)->champ2
qu'on transforme en size_t (size_t) &((struct ma_structure1 *)NULL)->champ2;
size_t est un type de variable capable de stocker la taille en char
d'un objet (une variable, un tableau, etc.)
dans notre cas c'est pas vraiment une taille mais une distance qu'on calcule */
printf("adresse de champ2 = %p\n", &((struct ma_structure1 *)NULL)->champ2);
printf("distance = %zu\n", distance);
struct ma_structure1 var6 = { 'a', 1, 'a' }, var7 = { 'a', 1, 'a' };
printf("comparaison = %d\n", memcmp(&var6, &var7, sizeof(struct ma_structure1)));
/* on pourrait par exemple comparer la mémoire occupée
par 2 structures différentes
c'est ce que fait la fonction memcmp au niveau des bits
elle renvoie
0 si var6 = var7
-1 si var6 < var7
+1 si var6 > var7
pourtant à cause des octets de bourrage
le résultat est déconcertant
donc à ne pas faire ! */
printf("comparaison champ1 = %d\n", memcmp(&var6.champ1, &var7.champ1, sizeof (char)));
printf("comparaison champ2 = %d\n", memcmp(&var6.champ2, &var7.champ2, sizeof (int)));
printf("comparaison champ3 = %d\n", memcmp(&var6.champ3, &var7.champ3, sizeof (char)));
// par contre on peut comparer chaque champ
/* De la même manière, il est sage de prendre quelques précautions avant de
transférer cette structure à l'extérieur du programme (comme un fichier,
un tube de communication ou une socket IP).
En général, il est préférable de traiter la structure champ par champ,
pour ce genre d'opérations. */
printf("\n--------------------\n");
printf(" Pointeurs\n");
printf(" vers structures\n");
printf("--------------------\n");
struct ma_structure1 * variable_pointeur1;
// déclaration d'un pointeur de type struct ma_structure1
variable_pointeur1 = malloc(sizeof(struct ma_structure1));
/* allocation dynamique, on réserve de la mémoire
pour une structure, malloc renvoit l'adresse de début
de la mémoire réservée */
(* variable_pointeur1).champ1 = 'z';
/* accéder à champ1: on déréférence le pointeur avec *
(on précise qu'on veut accéder à la valeur et pas l'adresse)
* s'applique ici au pointeur donc on met des parenthèses
et on rajoute le champ par .champ1 */
variable_pointeur1->champ1 = 'y';
/* idem exactement pareil, en C on peut utiliser ->
pour accéder à un champ dans le cas d'un pointeur */
free(variable_pointeur1);
// libère la mémoire alloué dynamiquement
printf("\n--------------------\n");
printf(" Unions\n");
printf("--------------------\n");
union mon_union1 {
char champ1; // 8 bits
int champ2; // 32 bits
};
// déclaration d'un type d'union
union mon_union1 varA;
// déclaration d'une variable de type union mon_union1
/* une union est en fait une variable qui peut
etre dans notre cas soit un champ1 soit un champ2
notre union va faire 32 bits
et pas 8 + 32 bits
elle prend la taille du plus grand champ (champ2 > champ1)
si on utilise notre union comme champ1
on lit/écrit sur les 8 premiers bits de champ1 et champ2
sinon sur 32 bits */
varA.champ1 = 'B';
printf("varA.champ1 = %c\n", varA.champ1);
printf("varA.champ2 = %d\n", varA.champ2);
/* résultat
varA.champ1 = B
varA.champ2 = -1208516798
il faut savoir que la mémoire n'est pas remise à zéro
donc quand on a déclaré varA il y a un nombre aléatoire dedans
donc B qui vaut 66 en ascii sur 8bits
accédé via champ2 se voit ajouter 24 autres bits
qui ne sont pas à zéro ce qui donne un nombre aléatoire */
varA.champ2 = 65;
printf("\nvarA.champ1 = %c\n", varA.champ1);
printf("varA.champ2 = %d\n", varA.champ2);
/* résultat
varA.champ1 = A
varA.champ2 = 65
par chance 65 n'est pas trop grand et vaut A en ascii
on comprend qu'une union est une seule variable */
varA.champ1 = 'B';
printf("\nvarA.champ1 = %c\n", varA.champ1);
printf("varA.champ2 = %d\n", varA.champ2);
/* résultat
varA.champ1 = B
varA.champ2 = 66
par chance B vaut 66 en ascii et les autres bits sont à zéro
ce qui fait qu'on a 66 on accédant à varA.champ2 */
varA.champ2 = 10082;
printf("\nvarA.champ1 = %c\n", varA.champ1);
printf("varA.champ2 = %d\n", varA.champ2);
/* résultat
varA.champ1 = b
varA.champ2 = 10082
(10082 en binaire voir les derniers 8 bits -> 98 -> b) */
varA.champ1 = 'R';
printf("\nvarA.champ1 = %c\n", varA.champ1);
printf("varA.champ2 = %d\n", varA.champ2);
/* résultat
varA.champ1 = R
varA.champ2 = 10066
(les 8 derniers bits sont modifiés, en ascii R -> 66) */
/* Dans un cas réel on utilise soit varA.champ1 soit varA.champ2
mais pas les 2, à cause des valeurs abérrantes */
printf("\n--------------------\n");
printf(" typedef\n");
printf("--------------------\n");
typedef char caractere;
// typedef définit un synonyme du type
char varX = 'a';
caractere varY = 'a';
/* c'est la même chose
char ou caractere */
// quelques autres exemples
typedef unsigned char octet;
typedef double matrice4_4[4][4];
typedef struct ma_structure * ma_struct;
typedef void (*gestionnaire_t)( int );
// Utilisation
octet nombre = 255;
matrice4_4 identite = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
ma_struct pointeur = NULL;
gestionnaire_t pointeur_sur_fonction = NULL;
/* autre exemple:
déclaration de ma_fonction confuse */
void (* ma_fonction(int varC, void (*pointeur_sur_fonction)(int)) )(int)
{
// ...
return pointeur_sur_fonction;
}
// typedef
typedef void (*psf)(int);
// déclaration claire grace au typedef
psf fonction(int varC, psf f)
{
// ...
return f;
}
printf("\n--------------------\n");
printf(" Enumérations\n");
printf("--------------------\n");
enum jours_de_la_semaine { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche };
// déclaration d'un type enum jours_de_la_semaine
enum jours_de_la_semaine courant = lundi;
// déclaration d'une variable courant de type enum jours_de_la_semaine
printf("courant = %d\n", courant);
courant = mardi;
printf("courant = %d\n", courant);
courant = mercredi;
printf("courant = %d\n", courant);
enum Booleen1 {Vrai1 = 1, Faux1};
// Vrai1 = 1 première valeur, donc Faux1 deuxième valeur = 2
enum Booleen1 variable1 = Faux1;
printf("\nvariable1 = %d\n", variable1);
enum Booleen2 {Vrai2 = 1, Faux2 = 0};
enum Booleen2 variable2 = Faux2;
printf("\nvariable2 = %d\n", variable2);
typedef enum Booleen_t {Faux, Vrai} Booleen;
Booleen variable = Faux;
printf("\nvariable = %d\n", variable);
printf("\n--------------------\n");
printf(" Types incomplets\n");
printf("--------------------\n");
struct liste {
struct liste * suivant;
struct liste * precedant;
void * element;
};
/* C permet d'utiliser le type struct liste pour * suivant
alors qu'il n'est pas encore complètement déclaré
attention ceci est valable pour un pointeur uniquement */
struct type_a {
struct type_a * champ1;
struct type_b * champ2;
int champ3;
};
struct type_b {
struct type_a * ref;
void * element;
};
return 0;
}