Langage C
1 - Premier Programme et Compilation
1.1 - Environnement de travail
Ouvrir un terminal sous linux.
# Création d un répertoire de travail
$ mkdir langage_c
$ cd langage_c
1.2 - Compilation avec GCC
|
BASH TERMINAL
|
1.3 - Premier Makefile
Afin de rendre la compilation plus simple, un makefile permet d’effectuer la compilation, puis l’édition de lien ( placement mémoire ).
Dès lors qu’un fichier makefile est présent dans un répertoire, la commande make exécute ce fichier.
|
BASH TERMINAL
|
1.4 - Debug
Les exemples ci-dessous pourront être testés avec les 2 environnements suivants :
Avec une interface minimale (dbg)
L’option “-g” dans gcc permet de compiler un programme avec possibilité de debug.
( installation : cf installations )
$ dbg first_prog
Avec un IDE ( STM32CUBEIDE )
Comme nous allons par la suite utiliser STM32CubeIDE , basé sur eclipse, nous pouvons également utiliser ce logiciel pour compiler et tester du code en natif ( sur PC ).
Chargement d’un projet :
Dans les exemples ci-dessous, après avoir récupéré et extrait le dossier compressé (.zip), décompresser le fichier.
Lancer stm32cubeide :
$ stm32cubeide
sélectionner le répertoire WORKSPACE*
L’arborescence du projet est alors visible :
Pour compiler : CTRL+B
Si la compilation s’effectue sans erreur, un exécutable est généré ( prog_base )
Pour tester ( debuggage ), basculer dans l’environnement debug :
Sélectionner la configuration de debug :
Le programme est alors chargé, pour :
- Lancer en continu : F8
- Lancer en pas à pas total : F5
- Lancer en pas à pas step over ( sans détailler les fonctions ) : F6
- Placer un point d’arrêt : double cliquer dans la marge ou shift+ctrl+B
Pour visualiser les variables, on peut utiliser soit la fenêtre Variables ( Window -> Show View -> Variables ) ou Watch Expression ( Window -> Show View -> Watch Expression )
Les printf s’affichent dans la fenêtre Console
2 - Modèle ( simplifié ) d’un système à processeur
- Le code, enregistré en mémoire ( mémoire programme ), est exécuté dans le processeur.
- Le processeur est constitué d’un ensemble de registres et d’unités de calcul.
- Le processeur lit ou écrit dans des variables, situées en mémoire de données ou/et des registres.
La variable a contenant la donnée 5 est située à l’adresse
0x20000000.
Pour désigner l’adresse d’une variable, on utilise le symbole & ( &a).
3 - Les Variables
Au cours de l’exécution d’un programme, le processeur effectue des calculs.
Ces calculs sont réalisés avec des variables.
En langage C il faut être précis sur les types utilisés.
Dans un premier temps nous allons retenir les types suivants :
- int : nombre entier signé sur 32 ou 64 bits ( dépend de l’architecture du processeur )
- char : nombre entier signé sur 8 bits ; permet également de stocker le codage de caractères au format ASCII
- float : nombre flottant ( à virgule ) signé sur 32 bits
- double : nombre flottant ( à virgule ) signé sur 64 bits
4 - Quelques exercices de Base
4.1 - Algorithmie
Les Conditions
|
|
Les Boucles
|
|
|
|
4.2 - Découvrir un nombre mystère
Q. Compléter la fonction main afin de faire deviner le nombre nbMyst tiré aléatoirement, selon l’algorithme suivant :
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX 10
#define MIN 1
//======================================================================
int main()
{
int nbMyst = 0, nbSaisi =0;
srand(time(NULL));
int random = rand();
nbMyst = ( random % ( MAX-MIN+1) ) + MIN;
printf( "nombre ? \n");
scanf("%d", &nbSaisi ); // saisie du nombre dans la console, dans la variable nbSaisi
/****************************
* A COMPLETER *
****************************/
}
//======================================================================
REMARQUE : La fonction printf est une fonction de haut niveau permettant d’afficher un message dans la console.
printf("j'afficher un message\n\r");
printf("j'affiche un entier : %d", variable_int);
printf("j'affiche un nombre réel : %f", variable_f);
4.3 - Les Fonctions : Calculs autour du cercle
Plutôt que de tout mettre dans la fonction main(), nous pouvons organiser notre code sous forme de fonctions
Q. Compléter le programme suivant afin de pouvoir calculer le diamètre, le périmètre et l’aire d’un cercle dont on a saisi le rayon.
REMARQUE : nous aurons besoin de la constante M_PI et de la fonction pow() présentes dans la bibliothèque math.
Il est donc nécessaire de modifier le makefile en conséquence.
#include <stdio.h>
//======================================================================
float calc_aire(float rayon)
{
/* A COMPLETER */
}
float calc_perimetre(float rayon)
{
/* A COMPLETER */
}
float calc_diametre(float rayon)
{
/* A COMPLETER */
}
//======================================================================
int main()
{
float r;
printf("rayon =");
scanf("%f", &r);
printf("diametre = %f, perimetre = %f, aire = %f \n", calc_diametre(r), calc_perimetre(r), calc_aire(r));
return 0;
}
//======================================================================
5 - Pointeurs
5.1 - Concept
Je veux modifier le contenu d’une case mémoire dans une fonction :
|
BASH TERMINAL
|
Conclusion : Le résultat du calcul a été affecté à un registre, la case mémoire notée ‘a’ n’a pas été modifiée.
Il faut transmettre l’adresse à la fonction, et non la valeur de la variable.
|
BASH TERMINAL
|
5.2 - Exercice : Fonction cutTime
Q. Compléter la fonction cutTime permettant de mettre à jour les variables hours, minutes et seconds, de telle sorte que :
- 0 ⩽ minutes < 60
- 0 ⩽ seconds < 60
REMARQUE : La fonction modulo % permet de calculer le reste de la division entière.
Ex :
45%60=45
62%60=2
Dans une boucle : i=(i+1)%5 –> i vaut successivement 0,1,2,3,4,0,1,2,3,4,…
#include <stdio.h>
#include <stdlib.h>
//======================================================================
void cutTime()
{
/********************
* A COMPLETER *
********************/
}
//======================================================================
int main()
{
int hours = 0;
int minutes = 1000;
int seconds = 50000;
cutTime( /* A COMPLETER */ );
printf("%d:%d:%d \n", hours, minutes,seconds);
}
//======================================================================
6 - Tableaux
6.1 - Concept
Le nom du tableau correspond à l’adresse du premier élément du tableau.
|
BASH TERMINAL
|
6.2 - Exercice : Calcul min/max/moyenne
Q. Compléter les fonctions calc_min, calc_max et calc_average.
#include <stdio.h>
#define N 10
//======================================================================
int calc_min(int* tableau, int size_tab)
{
/******************
* A COMPLETER *
*****************/
}
//======================================================================
int calc_max(int* tableau, int size_tab)
{
/******************
* A COMPLETER *
*****************/
}
//======================================================================
int calc_average(int* tableau, int size_tab)
{
/******************
* A COMPLETER *
*****************/
}
//======================================================================
int main()
{
int min, max, average = 0;
int tab[N]={1,5,-10,2,3,25,7,8,-2,0};
min = calc_min(tab, N);
max = calc_max(tab, N);
average = calc_average(tab, N);
printf("min=%d, max=%d, average=%d \n", min, max, average);
return 0;
}
6.3 - Exercice : Chaines de caractères
Représentation d’une chaine de caractères dans un processeur : le code ASCII
Comme toute donnée dans un système à processeurs, les caractères sont codés avec des 0 et des 1.
EXEMPLES:
- Le caractère ‘a’ est représenté par le code ascii 0x61
- Le caractère ‘1’ est représenté par le code ascii 0x31
Affichage du résultat d’un calcul
Q. Compléter la fonction conv_int_str permettant de transformer un résultat entier ( inférieur à 100) en chaine de 2 caractères.
#include <stdio.h>
#include <stdlib.h>
//======================================================================
int conv_int_str(int data, char* str_res)
{
/************************
* A COMPLETER *
************************/
return 0;
}
//======================================================================
int main()
{
char buffer[50] = "25+22= ";
char str_res[3];
int res=25+22;
buffer[6]=res;
printf("avec printf, ça marche : 25+22= %d \n", res);
//--------------------------------------
printf("problème : le résultat d'un calcul n'est pas une chaine de caractères : \n");
printf("%s \n", buffer);
//--------------------------------------
printf("Resultat de la conversion 'à la main' : \n");
conv_int_str(res, str_res);
buffer[6]=str_res[0];
buffer[7]=str_res[1];
printf("%s \n", buffer);
//--------------------------------------
printf("Utilisation de sprintf : \n");
sprintf(buffer,"%d+%d=%d \n", 85,2,87);
printf("%s \n", buffer);
return 0;
}
//======================================================================
Login / Password
Considérons à titre d’exemple le programme login.c permettant de tester un login et un mot de passe :
REM : la fonction strcmp permet de comparer 2 chaînes de caractère.
#include <stdio.h>
#include <stdlib.h>
int main()
{
char login[40];
char password[40];
printf( "Please, enter your login: " );
scanf( "%s", login );
printf( "Enter your password: " );
scanf( "%s", password );
if ( strcmp( login, "admin" ) == 0 && strcmp( password, "mdp" ) == 0 ) {
printf( "You are connected\n" );
} else {
printf( "Login failed. Retry later.\n" );
}
return 0;
}
Convertisseur de Monnaie
Q. Compléter le programme suivant permettant de convertir un montant d’une monnaie à une autre.
#include <stdio.h>
#include <stdlib.h>
// TAUX DE CHANGE
#define EUR_USD 1.09282
#define EUR_GBP 0.844684
#define USD_EUR 0.915092
#define USD_GBP 0.772992
#define GBP_EUR 1.18383
#define GBP_USD 1.29367
//======================================================================
//======================================================================
int main()
{
char monnaie_source[4];
char monnaie_cible[4];
float montant = 0.0;
int source = 0, cible = 0;
printf( "monnaie_source ( EUR / USD / GBP ) ? \n");
scanf("%s", monnaie_source );
printf( "monnaie_cible ( EUR / USD / GBP ) ? \n");
scanf("%s", monnaie_cible );
printf( "montant ? \n");
scanf("%f", &montant );
/*************************
A COMPLETER
************************/
}
//======================================================================
7 - Structures
7.1 - Concept
|
|
|
7.2 - Exercice : Calcul des Moyennes à partir de Notes d’Etudiants
Q. Compléter le fichier main.c permettant de calculer la moyenne de 3 étudiants à partir de 3 notes.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//======================================================================
struct carnet_TypeStruct
{
char nom[20];
float note[3] ;
float moyenne;
};
//======================================================================
int calc_average( float *tab, float N, float *result)
{
float acc=0;
for( int i=0 ; i < N ; i++)
{
acc = acc + tab[i];
}
*result = acc / N;
return 0;
}
//======================================================================
int main()
{
struct carnet_TypeStruct pers_1, pers_2, pers_3;
sprintf(pers_1.nom,"roger");
pers_1.note[0]= 15.5;
pers_1.note[1]= 18.5;
pers_1.note[2]= 5.5;
sprintf(pers_2.nom,"marcel");
pers_2.note[0]= 12.5;
pers_2.note[1]= 13.0;
pers_2.note[2]= 11.5;
sprintf(pers_3.nom,"pierre");
pers_3.note[0]= 2.5;
pers_3.note[1]= 5.5;
pers_3.note[2]= 10.0;
calc_average( /* A COMPLETER */ ) ;
printf( "Moyenne de %s = %f \n", /* A COMPLETER */ );
calc_average( /* A COMPLETER */) ;
printf( "Moyenne de %s = %f \n", /* A COMPLETER */ );
calc_average( /* A COMPLETER */) ;
printf( "Moyenne de %s = %f \n", /* A COMPLETER */ );
}
//======================================================================
8 - Portée des variables
PORTEE : Endroit dans le code où une variable peut être utilisée.
DUREE D’EXISTENCE : la valeur de ma variable est-elle conservée si je sors d’une fonction ?
Variable Locale
ma_fonction()
{
int a = 0;
}
- Portée : Fonction
- Durée d’existence : Fonction ( la valeur disparait quand on sort de la fonction )
- Emplacement physique : Registre / Pile / Mémoire ; au démarrage la valeur initiale est située en mémoire.
Variable Globale
int a = 0;
ma_fonction()
{
}
- Portée : Fichier, utilisable depuis toutes fonctions du fichier
- Durée d’existence : Programme
- Emplacement physique : Mémoire
Variable Locale Statique
Si l’on souhaite retrouver la valeur modifiée de notre variable quand on retourne dans une fonction :
ma_fonction()
{
static int a = 0;
}
- Portée : Fonction
- Durée d’existence : Programme
- Emplacement physique : Mémoire
Variables Externe
Quand une variable globale est déclarée et initialisée dans un fichier, mais qu’on veut l’utiliser dans un autre fichier :
extern int a;