Académique Documents
Professionnel Documents
Culture Documents
/*-----------------------------------------------------------*/
/* cette fonction calcul la somme de N premiers nombres */
/* à l'aide d'une équation et affiche le résultat à l'écran */
/* via la fonction printf() */
/*-----------------------------------------------------------*/
void sommation2(unsigned int nombre){
nombre = nombre*(nombre+1)/2;
printf("la somme2 donne : %d\n",nombre);
}
Nous allons faire l’analyse de ce petit programme ensemble en utilisant le programme « CodeBlocks »
déjà installé sur votre ordinateur. Vous pourrez si vous le désirez, en faire l’installation chez vous ce qui
vous donnera l’occasion de tester vos connaissance et aussi de travailler vos exercices.
Dans la plupart des langages, il y a toujours des éléments nous permettant d’effectuer des tests logiques
donnant ainsi la possibilité de prendre des décisions selon le résultat obtenu comme par exemple, est-ce
que le contenu de la variable « a » est plus grand que le contenu de la variable « b », si oui alors on
exécute certaines instructions sinon on en exécute d’autres. Aussi il est possible d’effectuer certaines
instructions plusieurs fois selon nos besoins, alors l’utilisation d’instructions de bouclage sa employée.
Ces instructions font partie des mots réservés et nous allons en explorer quelques uns.
Avant de faire l’analyse du programme du début, nous allons mettre en lumière quelques éléments de
base entourant la syntaxe du langage C. Je la ferai sous la forme d’une suite d’énoncés.
Lorsque l’on veut mettre une ligne de commentaires dans le programme on place deux barres
obliques comme ceci // le texte qui suit est alors un commentaire ne faisant pas partie des
instructions du programme. Voir le programme en exemple.
Lorsque l’on veut mettre plusieurs lignes en commentaires alors on place une barre oblique
suivie d’un astérisque /* on place ensuite à la fin de la ligne complétant le commentaire la
séquence inverse soit */.
Une instruction se termine toujours par un point virgule « ; »
La position horizontale d’une instruction dans un programme n’a aucune influence pour le
compilateur, l’indentation des instructions permet seulement au lecteur d’avoir une meilleure
compréhension visuelle de la structure et de l’enchaînement des instructions.
Dans un test conditionnel, tel un « if » , un « else » les instructions qui seront exécutées doivent
être placées entre des accolades { instructions à être exécutées }
De même pour une boucle « for », une boucle « while » ou les instructions contenues dans une
fonction.
La déclaration de variables doit se faire au début d’une fonction juste avant les premières
instructions exécutables ceci vaut pour les variables locales à chaque fonction.
La fonction « main() » est la première à être exécutée lorsque le compilateur aura terminé la
traduction de votre programme. Elle doit être présente et doit avoir obligatoirement ce nom.
Toutes les autres fonctions pourront avoir n’importe quel nom mais sans espace dans leur nom.
Normalement le langage C est sensible à la casse soit aux majuscules et minuscules. Il est
fortement conseillé de mettre tout les termes employés en minuscules. Par convention on met
en majuscule que les éléments définis comme des constantes comme dans le programme plus
haut, où j’ai définie une constante « VALEUR » qui lui sera assignée le nombre 100. À chaque
fois que le compilateur va rencontrer le mot « VALEUR » il va substituer ce mot par le nombre
100.
Il y a bien sûr d’autres éléments de la syntaxe que nous verrons au fur et à mesure que nous avancerons
dans la matière.
Mots réservés
Spécificateurs de type
Indique qu’il n’y a pas de type. Utilisé devant la déclaration d’une
void fonction indiquant que celle-ci ne retourne rien ou quelle ne reçoit
aucun paramètre lors de son appel ex. : void fonction1(void)
Un seul octet, pouvant contenir un caractère du jeu de caractères de
char
la machine utilisée ou simplement un nombre entre 0 et 0xFF.
short Un entier court donc la moitié ou la même grandeur qu’un int
Reflète typiquement la taille naturelle des nombres entiers sur la
int
machine utilisée. Peut être de 16 bits sur le µC et 32 bits sur le PC
long Indique que l’élément associé à ce type sera représenté sur 32 bits
float Un nombre en virgule flottante en simple précision sur 32 bits
double Un nombre en virgule flottante en double précision sur 64 bits
signed Représente un nombre pouvant être négatif.
unsigned Spécifie que le type employé représente un nombre positif
On peut ajouter l’un des mots short ou long à int; mais le sens reste le même si l’on omet le int. On
peut ajouter l’un des mots signed et unsigned à int ou bien à char. Si l’on n’écrit que signed ou unsigned
cela représente un int. Le spécificateur signed devant un char sert à forcer les objets de ce type à porter
un signe; tandis que pour un int il est par défaut de type signed si il n’est pas précisé.
Séquences d’échappement
> Est-ce plus grand que < Est-ce plus petit que
>= Est-ce plus grand ou égal à <= Est-ce plus petit ou égal à
&& a && b est-ce que a et b sont vrais || a || b est-ce que a ou b est vrai
If (a > b){
y=a;
}
else{
y = b;
}
signifie que si la valeur de a est plus grande que la valeur de b, alors y = a sinon y = b. Ces instructions
pourraient être remplacées par une seule ligne plus compacte, la voici :
y = (a > b) ? a : b; correspond à expr1 ? expr2 : expr3 ce qui se lit est-ce que l’ expr1 est vrai
si oui alors prend comme résultat expr2 sinon prend expr3.
& ET bit à bit : ex. : 0x3a & 0x2f = 0x2a << Décalage à gauche ex. : 0x3a <<4 = 0xa0
| OU inclusif bit à bit ex. : 0x3a | 0x2f = 0x3f >> Décalage à droite ex. : 0x3a >>4 = 0x03
^ OU exclusif bit à bit ex. : 0x3a ^ 0x2f = 0x15 ~ Complément à 1 ex. : ~ 0x3a = 0xc5
Opérateurs d’affectation
Préfixé Postfixé
Dans un contexte où l’on veut seulement incrémenter une variable sans se servir de sa valeur, comme
par exemple dans :
If (volt == 12){
i++;
}
alors les formes préfixées et postfixées reviennent au même.
Mais dans certaines conditions selon le programme, le choix de i++ ou ++i aura une importance sur le
résultat attendu comme dans l’exemple suivant :
Supposons que nous avons un tableau tension contenant 10 int et que l’on veuille mettre à 12 tous ses
éléments.
Première version du programme qui utilise i++.
main() {
int tension[10];
int i = 0;
do {
tension[i++] = 12;
}
while(i <=9);
}
La boucle do-while s’exécute sans condition d’entrée, ici la valeur de i est 0 ce qui conduit à ce que
tension[0] qui est le premier élément du tableau reçoive la valeur 12, suite à cette opération i est alors
incrémenté de 1. L’instruction while vérifie si c’est vrai que i est plus petit ou égal à 9 avant de
recommencer la boucle do-while. Une fois que i sera rendu à 10, on sortira de la boucle. Tous nos
éléments de notre tableau auront été initialisés à 12. Voyons maintenant le comportement de notre
initialisation lorsque l’on utilise ++i au lieu de i++.
Opérateurs associativité
* / % De gauche à droite
+ - De gauche à droite
== != De gauche à droite
^ De gauche à droite
| De gauche à droite
|| De gauche à droite
?: De droite à gauche
, De gauche à droite
Les opérateurs qui figurent sur la même ligne, ont le même niveau de priorité et les lignes sont classées
en ordre décroissant de priorité. Il faut ici bien connaître l’ordre de priorité des opérateurs car lorsque
certains de ceux-ci sont placés dans une équation par exemple, si vous n’avez pas pris le soin de mettre
des parenthèses pour fixer vous-mêmes la priorité des certaines opérations, alors ces dernières seront
exécutées en respectant l’ordre du tableau ci-dessus.
Voici un exemple d’un résultat que l’on pourrait mal interpréter si l’on n’applique pas correctement la
priorité des opérateurs. Posons les variables a, b et c avec les valeurs suivantes 12, 20, 35
Y = a + b * 5 – c = 12 + 20 * 5 – 35 = 77
La multiplication sera effectuée en premier alors le calcul est 20*5 = 100 et par la suite l’addition et la
soustraction sont faites peut importe l’ordre, elles ont la même priorité. Ce qui donne 12+100-35 = 77
Les boucles
------------------------------> La boucle for <------------------------------
#include <stdio.h>
#include <stdlib.h>
/*-------------------------------------------------*/
void main(void){
char i,j; // déclaration de variables
int rep;
Explication : L’idée derrière l’usage de la boucle « for » est d’exécuter un nombre de fois la plupart du
temps connu, une ou plusieurs instructions qui se retrouvent alors placées entre
l’accolade ouvrante « { « et celle fermante « } » de la boucle « for ». Cette dernière se
divise en trois élément tous séparés par un « ; » le premier correspond à l’initialisation
d’une variable « j » à la valeur que l’on désire selon le fonctionnement recherché dans
notre programme. Le second élément est le test de vérification à savoir si la boucle
« for » doit se répéter ou s’arrêter et poursuivre l’exécution des instructions qui se
retrouveraient à la suite de l’accolade fermante « } ». Ce test de vérification est booléen
(soit VRAI ou FAUX), si c’est VRAI, la boucle peut se poursuivre, sinon elle quitte. Le
troisième élément sert à modifier la variable servant de « compteur » afin qu’elle puisse
un moment donné atteindre une valeur qui mettra fin à la boucle « for », sinon la boucle
n’aurait jamais de fin.
L’analyse du programme se fera en classe.
Explication : L’idée derrière l’usage de la boucle « while » est d’exécuter une ou plusieurs instructions
qui se retrouvent alors placées entre l’accolade ouvrante « { « et celle fermante « } » du
« while » un nombre de fois qui peut être connu ou non, la condition pour quitter cette
boucle dépendra du résultat du test booléen situé entre parenthèse suivant le « while ».
On peut traduire le « while » par « tant que », donc la boucle va s’exécuter « tant que « le
résultat du test booléen va être « vrai » sinon on quitte celle-ci. Par exemple on pourrait
vouloir vérifier l’état d’un bit dans un registre du microcontrôleur et ne faire que ça « tant
et aussi longtemps » que l’état de ce dernier soit au niveau que l’on désire avant de
poursuivre plus avant l’exécution des instructions suivantes. Dans le programme plus
haut, la boucle « while » va s’exécuter « tant que » la variable « i » sera strictement plus
grande que 4.
La boucle « do while » est similaire à la boucle « while » à la différence majeure que la boucle « while »
doit faire le test booléen avant de pouvoir exécuter les instructions comprises entre ses accolades,
tandis que la boucle « do while » s’exécute au moins une première fois et ensuite elle vérifie si elle doit
boucler à nouveau. La syntaxe est la suivante.
do{
i = i-j;
printf("Le calcul de i = %d\n",i);
j++;
}while(i<4); // ne pas oublier le « ; » à la suite du « while »
/*-------------------------------------------------*/
void main(void){
int rep; // Déclaration de trois variables de type entier
int a,b;
a = 7;
b = 13;
rep = a * b;
Explication : L’exécution du programme fait en sorte que la variable « rep » aura comme valeur 91, car
rep = a * b = 7 * 13 = 91. Le premier « if » sera testé et le résultat de ce test booléen est
soit VRAI (n’importe quelle valeur différente de 0) ou soit FAUX donc égal à 0, il faut alors
lire le « if » de la manière suivante, est-ce que « rep » est plus petit ou égal à 50, si c’est
vrai alors les instructions comprises dans les accolades { } suivant le « if », seront
exécutées. Ici le premier « if » est faux, donc on passe au second. Le test est maintenant
un « ET » logique entre deux tests conditionnels; il faut alors lire ceci de la façon suivante,
« est-ce que « rep » est plus grand que 50 « ET » est-ce que « rep » est plus petit ou égal à
100 », pour que le résultat soit VRAI, il faut absolument que les deux tests soient VRAI
comme pour un « ET » logique, 1 et 1 = 1. Ici le test sera VRAI car 91 est évidement plus
grand que 50 ET plus petit ou égal à 100. Il s’en suit que les instructions du « if » seront
exécutées et le mot « Bonsoir » va s’écrire à l’écran. Même si le second « if » a été
exécuté, le programme va poursuivre avec l’analyse du troisième « if ». Bien sûr celui-ci
ne sera pas VRAI et le mot « Bonjour » ne pourra s’écrire.
/*-------------------------------------------------*/
void main(void){
int rep;
int a,b;
a = 7;
b = 13;
rep = a * b;
Explication : L’exécution du programme donnera bien sûr le même résultat mais il y aura une
économie de temps d’exécution car tous les « if » ne seront pas testés car il y a
maintenant l’ajout du « sinon » correspondant au « else » qui viendra couper court à
l’exploration de chaque « if ». Commençons par le premier « if », celui devra bien sûr être
évalué au départ, étant donné que « rep » vaut 91, le « else if « sera donc évalué. Le test
étant VRAI, le mot « Bonsoir » s’affiche et le programme se termine là-dessus car le
« sinon » ne pourra être VRAI car le « si » l’était déjà avant lui. On voit bien ici que le
programme prendra moins de temps car il n’aura pas tous les tests à effectuer.
À la page suivante, nous voyons l’instruction « switch » qui permet d’une certaine manière
à remplacer ce que plusieurs « if et else if » peut accomplir dans une certaine mesure.
/*-------------------------------------------------*/
void main(void){
unsigned char rep,i,j; // déclaration de plusieurs variables
i = 35;
j = 52;
rep = i + j;
switch(rep){ // « rep » doit absolument être un entier
case MOYEN :
printf("Le soleil se leve :");
break;
case RIEN :
printf("Le soleil est couche :");
break;
case BAS :
printf("C'est nuageux :");
break;
case HAUT :
printf("Le soleil brille :");
break;
default :
printf("Oups :");
break;
}
}
/*-------------------------------------------------*/
Explication : L’exécution du programme fait en sorte que la variable « rep » aura comme valeur 87, car
rep = i + j = 35 + 52 = 87. L’instruction « switch » prend alors la valeur de « rep » et va
vérifier dans le cas (case) que si la valeur de « rep » vaut « BAS » soit 87, alors il exécutera
que les instructions situées dans le « case BAS : » jusqu’à ce qu’il rencontre un « break »
qui lui ordonnera alors de quitter l’instruction « switch ». Si aucun « case » ne
correspond, l’instruction « default » vous permet de répondre à toutes les autres
situations.
Les fonctions
Le langage C comme la plupart des langages de programmation offrent la possibilité d’écrire des
sections de programme appelé « fonction » que l’on peut par la suite faire appel n’importe d’où dans
l’ensemble de notre programmation. Chaque fonction possède un nom unique qu’on lui attribut et
lorsque l’on a besoin de l’exécuter, il suffit d’inscrire son nom à l’endroit dans le programme où la
séquence d’exécution l’exige.
Il y a différentes manières de déclarer une fonction et par la suite d’en faire l’appel, voici en gros les plus
fréquentes car certaines dépassent le cadre du cours à proprement dit.
Syntaxe de la déclaration
Syntaxe réelle d’appel dans un programme
d’une fonction
#include <stdio.h>
#include <stdlib.h>
/*--------------------------------------------------------*/
/* définition de la fonction avec toutes ses instructions */
void allume_led(void){
char led1,led2;
led1 = 1;
led2 = 1;
}
Lorsque le programme s’exécute, il commence toujours par la fonction main() , on observe alors que la
fonction allume_led() est appelée. Un saut à l’adresse de cette fonction est effectué et les instructions
sont exécutées séquentiellement. Une fois la fonction terminée, le processus revient dans la fonction
main() qui ensuite met fin au programme.
2e format de fonction :
Syntaxe de la déclaration
Syntaxe réelle d’appel dans un programme
d’une fonction
#include <stdio.h>
#include <stdlib.h>
while(i){
i--;
delai_ms(1000); // appel de la fonction
}
}
/*------------------------------------------------------*/
void delai_ms(int valeur){
int i;
while(valeur--){
for(i=0;i<400;i++){}
}
}
La fonction main() fait l’appel de la fonction delai_ms(1000) qui reçoit en paramètre l’entier 1000 qui
sera alors utilisé dans cette dernière pour lui indiquer le nombre de fois qu’elle devra faire la boucle
« for » prévue pour durer 1ms. La variable « valeur » déclarée localement dans la fonction delai_ms()
recevra alors le nombre 1000 qui a été passé en paramètre. Le contenu de « valeur » sera utilisé dans la
boucle « while » afin d’effectuer un bouclage qui donnera un délai de 1ms multiplié par ce que contient
« valeur ». Le paramètre passé à la fonction « delai_ms() » peut être un nombre donné directement ou
être le contenu d’une variable de type entier ou même le résultat d’une opération mathématique ou le
résultat de ce que pourrait retourner une fonction passée en paramètre. Les variantes sont
nombreuses.
3e format de fonction :
Syntaxe de la déclaration
Syntaxe réelle d’appel dans un programme
d’une fonction
Signifie qu’elle retourne une valeur de type « char » soit un entier de 8 bits
#include <stdio.h>
#include <stdlib.h>
while(1){
reponse = capteur(); // appel de la fonction et la valeur
// qu’elle retourne est mise dans la
// reponse
}
}
/*------------------------------------------------------*/
char capteur(void){
char lecture;
lecture = PTAD;
lecture >>= 4;
return lecture;
}
La fonction main() fait l’appel de la fonction capteur() continuellement. La valeur que va retourner
cette fonction sera alors placée dans la variable « reponse ». On doit donc lire la ligne « reponse =
capteur(); » comme suit : met dans la variable « reponse » le résultat que retournera la fonction
« capteur() ». Comme on peut le voir, la fonction « capteur() » retourne le contenu de sa variable locale
« lecture » qui sera copié dans la variable « reponse » ultimement
4e format de fonction :
Syntaxe de la déclaration
Syntaxe réelle d’appel dans un programme
d’une fonction
Signifie qu’elle retourne une valeur de type « int » soit un entier signé de 16 ou 32
bits, selon le microcontrôleur ou microprocesseur utilisé
#include <stdio.h>
#include <stdlib.h>
while(1){
reponse = conversion(125);
}
}
/*------------------------------------------------------*/
int conversion(char valeur){
int i;
i = (valeur * 3)+32;
return i;
}
La fonction main() fait l’appel de la fonction conversion() continuellement. La valeur que va retourner
cette fonction sera alors placée dans la variable « reponse ». Il faut lors de l’appel de cette fonction lui
passer en paramètre une valeur entière de 8 bits qui sera utilisée pour effectuer des calculs.
5e format de fonction :
Syntaxe de la déclaration
Syntaxe réelle d’appel dans un programme
d’une fonction
Signifie qu’elle retourne un pointeur sur des « int ». Donc la valeur retournée est
une adresse vers des « int »
Exemple d’utilisation d’une fonction dans ce format, mais avec plusieurs paramètres :
#include <stdio.h>
#include <stdlib.h>
La fonction « extraction » effectue le travail suivant : Lors de son appel, on lui passe en paramètre
l’adresse des tableaux d’entiers signés « tableau », « positif » et « negatif ». Vous remarquerez pourtant
que ce ne sont pas les noms des tableaux qui sont utilisés mais des pointeurs sur ces tableaux. Donc le
pointeur « ptr1 » contient l’adresse de « tableau », ensuite « ptr2 » contient celle de « positif » et
finalement « ptr3 » celle du tableau « negatif ». Comme dernier paramètre à l’appel de la fonction
« extraction » le nombre inscrit est une valeur négative comme -1 par exemple, si l’on désire extraire
tous les nombres négatifs contenus dans le tableau « tableau » ou une valeur positive si l’on veut une
extraction des nombres positifs de ce tableau. En fonction du choix d’extraction, le pointeur
« pointeur » déclaré dans le main() recevra après l’exécution de la fonction « extraction », l’adresse du
tableau dans lequel les nombres recherchés auront été recopiés. Nous pourrions par la suite faire
l’impression du contenu de ce qu’il y a dans le tableau ainsi rempli.
NOTE : Il y a deux façons de passer des paramètres à une fonction, par valeur ou par référence. Par
« valeur » cela signifie que c’est le contenu d’une variable ou une valeur directe qui est passé,
tandis que par « référence » c’est l’adresse d’une variable qui est alors passée en paramètre.
Ce que nous appelons un pointeur. Nous verrons en classe de quoi il s’agit.
Les STRUCT
Une structure rassemble une ou plusieurs variables, qui peuvent être de types différents, que l’on
regroupe sous un seul nom pour les manipuler plus facilement. Un exemple simple d’une structure est
la création d’un carnet d’adresses : composé du prénom, du nom de famille, du téléphone et de l’âge
etc… Cette structure recevra un nom qui permet de l’identifier et de la distinguer d’une autre structure.
La création d’une structure vient définir un nouveau type de variable comme pour les « char », « int » et
autres. Tant que l’on ne déclare pas de variable aillant le type de cette structure, celle-ci n’occupe
aucun espace mémoire. On peut le moment voulu déclarer plusieurs variables ayant le type de notre
nouvelle structure et ainsi posséder toutes ces caractéristiques. Voici un exemple de déclaration d’une
structure.
La variable « ami » du type «struct carnet » est composé de 4 membres ou éléments. L’espace mémoire
qu’elle occupera sera déterminée par le type des éléments qui la composent. À première vue, il suffit de
calculer la somme du nombre d’octets que prend chaque élément, ce qui donnerait 30+20+4+15 = 69,
mais dans les faits cela n’est pas tout à fait exact. Le compilateur va s’arranger pour faire en sorte que
l’adresse où vont être mis chaque élément, devra arriver sur une frontière qui sera un multiple du type
prenant le plus d’octet soit le « int ». Je m’explique, ici dans notre « struct carnet », il y a des « char » ce
type occupe à la base un octet, tandis que le « int » en occupe quatre. Supposons que l’adresse du
tableau nom[] débute à 448, si on lui ajoute 30 octets l’adresse du dernier octet de nom[] sera 477. Ce
qui conduit à ce que l’adresse du tableau prenom[] débute à 478 et normalement il se terminera à
l’adresse 497. C’est ici que ça change, étant donné que l’élément « age » est un « int » il devra donc
débuter à une adresse qui sera un multiple de 4 (4 octets), si on additionne 30 + 20 = 50 on se rend
compte que 50 n’est pas un nombre divisible par 4, le nombre supérieur le plus près est 52, donc
l’adresse de « age » débutera à l’adresse 500, soit 448+30+20+2 = 500. Par la suite le tableau
telephone[] commencera à l’adresse 504 et se terminera à l’adresse 520 pour que l’ensemble donne un
multiple de 4. En résumé notre variable « ami » occupera au total 30+20+2+4+15+1 = 72 octets.
Prenons le programme suivant qui place dans la variable « ami » du type struct carnet, pour chaque
élément la composant, une valeur et ensuite d’en faire l’impression.
void main(void){
struct carnet {
unsigned char nom[30];
unsigned char prenom[20];
unsigned int age;
unsigned char telephone[15];
};
struct carnet ami;
Si nous voulions avoir plus d’une information dans notre carnet d’ami, il faudrait alors déclarer d’autres
variables du type « struct carnet » mais comment faire si on veut imprimer tout notre carnet d’amis
mais que chaque ami a sa propre variable cela va devenir lourd surtout pour la création de nouvelles
variable pour chaque ami. La solution sera donc de se créer un tableau de structure afin que tout soit
sous la même rubrique. Voici comment faire : il suffit de déclarer un tableau qui pourra contenir disons
40 structures du type « struct carnet », la syntaxe sera simplement comme suit :
reponse = ami[4].age;
Pour accéder à un membre ou élément d’une structure, il faut mettre un « . » après le nom de la
structure pour spécifier à quel élément on fait allusion.
Les UNION
Une « union » se déclare de la même façon qu’une « struct » mais l’espace mémoire que partage chacun
de ses membres est le même. Voici comment elle se déclare et comment elle fonctionne.
Ici l’union est simplement déclarée et ne Ici l’union est déclarée et à la fin
prend aucun espace mémoire. La déclaration on y déclare une variable « valeur »
d’une variable de ce type pourra alors être qui sera du type « union ordre »
faite ultérieurement.
Si nous faisons cette déclaration de
variable, il faut alors écrire :
Supposons que la variable « valeur » de type « union ordre » est à l’adresse 2293516, l’élément
« nombre_total » débutera à l’adresse 2293516 pour se terminer à 2293519. Jusqu’ici rien de
particulier, mais ce qui est intéressant dans l’utilisation de l’union, c’est que l’élément
« nombre_separe[] » va lui aussi débuter à l’adresse 2293516, car ils occupent tous les deux le même
espace mémoire, c’est ce qui différencie la « struct » de l’« union ». On peut donc conclure que la
quantité d’octets que prendra la variable « valeur » sera de 4.
Vous vous dites sans doute, quelle est l’utilité d’avoir deux variables à la même place car si j’écris sur
une je modifie l’autre aussi ? Pour vous convaincre, prenez le fichier « mc9s08el32.h » de CodeWarrior,
vous pourrez observer que tous les registres et tous les bits composants ces registres sont déclarés dans
des « union » qui incluent des « struct ». Je vous montre une section de ce fichier portant sur la
déclaration d’une « union » décrivant le PORT A du µC.
Dans cette « union », il y a 3 éléments qui la composent, le premier est l’élément « Byte » qui est du
type « byte » portez attention au « B » et « b » ce qui n’est pas la même chose, cette élément occupe 1
octet, le second élément est la « struct » « Bits » qui elle contient 8 éléments occupant chacun un bit et
le troisième est aussi une « struct » nommée « MergeBits » qui possède 4 éléments occupant
respectivement 4 bits, 1, 1 et 2 bits. Comme chacun de ces 3 éléments de l’union vont occuper le même
espace mémoire, soit l’adresse du PORT A à $0000, je pourrai donc être en mesure d’écrire sur le PORT
A avec un seul octet directement ou aller écrire sur chacun des bits séparément en utilisant le nom des
éléments composant les « struct » incluses dans l’ « union ». C’est pourquoi vous êtes en mesure dans
vos programmes en langage C de mettre par exemple le bit 2 du PORT A à 1 en ciblant l’élément de la
« struct » s’occupant du bit 2 en écrivant PTAD_PTAD2 = 1. Je vous montrerai plus avant de quoi il
retourne dans le cours théorique.
En se référant aux explications précédentes, chaque élément occupe 4 octets en mémoire, mais faisant
partie d’une union, ils occuperont le même espace mémoire. Au début du programme, on fait
l’impression de la dimension en octets qu’occupe la variable « valeur » du type « union ordre », par la
suite on initialise l’élément « nombre_total » faisant partie de « valeur » avec le nombre 0x9a5be872, ce
qui occupe 4 octets. Étant donné que « nombre_total » et « nombre_separe[] » occupent les mêmes
cases mémoires, écrire dans un élément affectera aussitôt l’autre. Lors de l’impression de chaque octet
du tableau « nombre_separe[] », nous obtiendrons séparément le contenu de « nombre_total » ce qui
ne pouvait se faire si on imprimait le contenu de « nombre_total ». De plus on observe que l’octet le
plus significatif de « nombre_total » est 0x9a et qu’il se retrouve à la dernière adresse formant
« valeur ». Ce positionnement en mémoire par le système est nommé « BIG-ENDIAN » du fait que la
partie basse du nombre débute à la première adresse et que la partie haute se termine à la dernière
adresse.
Modifions par exemple seulement le deuxième octet le moins significatif de « nombre_total » soit la
valeur 0xE8 par 0xAA, on se rend vite compte qu’on ne peut le faire en écrivant directement sur
« nombre_total » car nous devrions réécrire exactement les mêmes octets pour tous ceux que l’on ne
veut pas changer, or l’élément « nombre_separe[1] me permet d’y accéder facilement, nous n’avons
qu’à écrire l’instruction suivante et le tour sera joué, « valeur.nombre_separe[1] = 0xAA. Si nous faisons
imprimer par la suite « nombre_total » nous aurons comme résultat ce qui est présenté dans la fenêtre
d’exécution du programme.
Il y a plusieurs fonctions de traitement des chaînes, je vous en énumère quelques unes pour vous
donner un aperçu de leur syntaxe et aussi de leur utilité.
Il y a bien plusieurs autres fonctions dans la bibliothèque standard du langage C, vous pouvez fouiller sur
Internet afin de le découvrir en tapant simplement les noms des en-têtes de la bibliothèque du C
montrées ci-dessous.
<locale.h>
Pour s'adapter aux différentes conventions culturelles.
<math.h>
Pour calculer des fonctions mathématiques courantes. C99 a ajouté de nombreuses fonctions
mathématiques, en particulier pour converger avec la norme CEI 559 dite aussi IEEE 754.
<setjmp.h>
Pour exécuter des instructions goto non locales (sortes d'exceptions).
<signal.h>
Pour contrôler les signaux (conditions exceptionnelles demandant un traitement immédiat, par
exemple signal de l'utilisateur).
<stdarg.h>
Pour créer des fonctions avec un nombre variable d'arguments.
<stdbool.h>
Pour avoir une sorte de type booléen (introduit par C99).
<stddef.h>
Définit plusieurs types et macros utiles, comme NULL.
<stdint.h>
Définit divers types d'entiers, c'est un sous-ensemble de inttypes.h (introduit par C99).
<stdio.h>
Fournit les capacités centrales d'entrée/sortie du langage C, comme la fonction printf.
<stdlib.h>
Pour exécuter diverses opérations dont la conversion, la génération de nombres pseudo-
aléatoires, l'allocation de mémoire, le contrôle de processus, la gestion de l'environnement et
des signaux, la recherche et le tri.
<string.h>
Pour manipuler les chaînes de caractères.
<tgmath.h>
Pour des opérations mathématiques sur des types génériques (introduit par C99).
<time.h>
Pour convertir entre différents formats de date et d'heure.
<wchar.h>
Pour manipuler les caractères larges (wide char), nécessaire pour supporter un grand nombre de
langues et singulièrement Unicode (introduit par Amd.1).
<wctype.h>
Pour classifier les caractères larges (introduit par Amd.1).
---------------------------------------------------------------------------------------------------------------------------------------
char * ; les caractères d’une chaîne sont imprimés jusqu’à rencontrer un ‘\0’
%s
ou jusqu’à avoir imprimé le nombre de caractères indiqué par la précision
int * ; le nombre de caractères écrits jusqu’à présent par cet appel de printf
%n
est écrit dans l’argument. Ne converti pas d’argument.
entier , int *. L’entier peut être sous forme octale, s’il est précédé par un 0, ou
&i
hexadécimale, s’il est précédé par 0x ou 0X.
&o entier sous forme octale (précédé ou non d’un zéro) ; int *