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

#include <stdio.h> // Bibliothèque pour le printf

int main ()
{
  printf ("hello world \n");
  return 0;
}
  

   BASH TERMINAL

# gcc [fichier c à compiler] -o [exécutable]
$ gcc main.c -o main
# rendre le fichier exécutable
$ chmod +x main
# Exécution
$ ./main 
hello world

compilation_gcc.svg

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.

PROJECT_NAME = first_prog
CC=gcc
PROJECT_SRC = .
vpath %.c $(PROJECT_SRC)

########################################################################
SRCS = main.c
SRCS +=

########################################################################
INC_DIRS = .
INCLUDE = $(addprefix -I,$(INC_DIRS))
########################################################################

CFLAGS=-std=c99 -g -O0 -U__STRICT_ANSI__ \
       -Wall -Wextra -Wc++-compat -Wwrite-strings -Wconversion \
       -pedantic 

LDFLAGS=

########################################################################

all: $(PROJECT_NAME)

$(PROJECT_NAME): $(SRCS)
	$(CC) $(INCLUDE) $(CFLAGS) $^ -o $@ $(LDFLAGS) 			# $@ = cible, $^ = prérequis 

%.o: %.c
	$(CC) $< -c $(INCLUDE) $(CFLAGS)						# $< = premier des prérequis / -c = compile sans édition de liens	

clean:
	rm -f *.o $(PROJECT_NAME)

########################################################################

   BASH TERMINAL

$ make
gcc -I.  -std=c99 -g -U__STRICT_ANSI__ -W -Wall -pedantic -O0 -D_REENTRANT    main.c -o first_prog
$ ./first_prog 
hello world 

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

tdb.png

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*

workspace.png

L’arborescence du projet est alors visible :

arborescence.png

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 :

debug.png

Sélectionner la configuration de debug :

debug_2.png

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.

modele.svg

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

if(condition)
{ 
	// instructions si condition respectée
}
else
{
	// instructions si condition non respectée
} 

if_then_else.svg


Les Boucles

for(int i=0 ; i < VAL_MAX ;  i++)
{
	// instructions à exécuter VAL_MAX fois
}

boucle_for.svg


i = VAL_MAX
while(i > 0)
{
	// instructions à réaliser tant que i>0
	i--;
}

boucle_while.svg

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 :

mot_mystere.svg




WORKSPACE_NOMBRE_MYSTERE.zip

#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

fonctions.svg



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.

WORKSPACE_CERCLE.zip

#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 :

#include <stdio.h> 

//======================================================================
// Funtions Prototypes
void calc_function(int);	

//======================================================================
int main ()
{
  int a=5;
  printf("before calc_function : a = %d \n",a);
  
  calc_function(a);
  printf("after  calc_function : a = %d \n", a);
  
  return 0;
}
//======================================================================
void calc_function(int param)
{	
	printf("before calc : param = %d \n",param);
	param = param+2 ;
	printf("after  calc : param = %d \n",param);
}	
//======================================================================

   BASH TERMINAL

$ gcc pointeur1.c -o pointeur1
$ ./pointeur1 
before calc_function : a = 5 
before calc : param = 5 
after  calc : param = 7 
after  calc_function : a = 5 

pointeur1.svg

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.

#include <stdio.h> 

//======================================================================
// Funtions Prototypes
void calc_function(int*);	

//======================================================================
int main ()
{
  int a=5;
  printf("before calc_function : a = %d \n",a);
  
  calc_function(&a);
  printf("after  calc_function : a = %d \n", a);
  
  return 0;
}
//======================================================================
void calc_function(int *param)
{	
	printf("before calc : param = 0x%lx \n",(long)param);
	printf("before calc : *param = %d \n",*param);

	*param = *param+2 ;
	
	printf("after  calc : param = 0x%lx \n",(long)param);
	printf("after  calc : *param = %d \n",*param);
}	
//======================================================================

   BASH TERMINAL

$ gcc pointeur2.c -o pointeur2
$ ./pointeur2
before calc_function : a = 5 
before calc : param = 0x20000000 
before calc : *param = 5 
after  calc : param = 0x20000000 
after  calc : *param = 7 
after  calc_function : a = 7 

pointeur2.svg

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,…

WORKSPACE_CUTTIME.zip

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

#include <stdio.h> 

//======================================================================
// Funtions Prototypes
void calc_function(int*);	

//======================================================================
int main ()
{
  int tab[5]={1,2,3,4,5};
  printf("tab = 0x%lx \n",(long)tab);
  printf("before calc_function : tab[0] = %d, tab[1] = %d, tab[2] = %d, tab[3] = %d, tab[4] = %d \n",tab[0],tab[1],tab[2],tab[3],tab[4]);
  calc_function(tab);
  printf("after  calc_function : tab[0] = %d, tab[1] = %d, tab[2] = %d, tab[3] = %d, tab[4] = %d \n",tab[0],tab[1],tab[2],tab[3],tab[4]);
  return 0;
}
//======================================================================
void calc_function(int *param)
{	
	int i = 0;
	
	for(i=0 ; i < 5 ; i++)
	{		
		param[i] = param[i]+1;	
		//*(param + i) = *(param + i)+1; // alternate syntax				
	}	
}	
//======================================================================

   BASH TERMINAL

$ gcc tableau.c -o tableau
$ ./tableau
tab = 0x20000000 
before calc_function : tab[0] = 1, tab[1] = 2, tab[2] = 3, tab[3] = 4, tab[4] = 5 
after  calc_function : tab[0] = 2, tab[1] = 3, tab[2] = 4, tab[3] = 5, tab[4] = 6 

tableau.svg

6.2 - Exercice : Calcul min/max/moyenne

Q. Compléter les fonctions calc_min, calc_max et calc_average.

WORKSPACE_MIN_MAX_AVERAGE.zip

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

ascii.svg

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.

WORKSPACE_CONV_INT_STR.zip

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

WORKSPACE_LOGIN.zip

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

WORKSPACE_CURRENCY.zip

#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

#include <stdio.h> 
//======================================================================
// Type definitions  
typedef struct 
{
	float real ; 
	float imag;
} complex_TypeDef;	
//======================================================================
// Funtions Prototypes
void calc_function(complex_TypeDef*);	

//======================================================================
int main ()
{
  complex_TypeDef c1 = {1.2, 2.5};
  printf("&c1 = 0x%lx \n",(long)&c1);
  printf("before calc_function : c1.real=%f c1.imag=%f \n",c1.real,c1.imag);
  calc_function(&c1);
  printf("after  calc_function : c1.real=%f c1.imag=%f \n",c1.real,c1.imag);
  return 0;
}
//======================================================================
void calc_function(complex_TypeDef *param)
{	
	param -> real = param -> real + 1.0; // (*param).i = (*param).i + 1.0 ;
	param -> imag = param -> imag + 1.0;
}	
//======================================================================
#include <stdio.h> 
//======================================================================
// Type definitions  
struct complex_TypeStruct
{ 
	float real ; 
	float imag;
};
//======================================================================
// Funtions Prototypes
void calc_function(struct complex_TypeStruct*);	

//======================================================================
int main ()
{
  struct complex_TypeStruct c1={1.2, 2.5}; // alternate syntax
  printf("&c1 = 0x%lx \n",(long)&c1);
  printf("before calc_function : c1.real=%f c1.imag=%f \n",c1.real,c1.imag);
  calc_function(&c1);
  printf("after  calc_function : c1.real=%f c1.imag=%f \n",c1.real,c1.imag);
  return 0;
}
//======================================================================
void calc_function(struct complex_TypeStruct *param)
{	
	param -> real = param -> real + 1.0; // (*param).real = (*param).real + 1.0 ;
	param -> imag = param -> imag + 1.0;
}	
//======================================================================
$ ./structure1
&c1 = 0x20000000 
before calc_function : c1.real=1.200000 c1.imag=2.500000 
after  calc_function : c1.real=2.200000 c1.imag=3.500000 

structure.svg

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.

WORKSPACE_CALCUL_NOTES.zip

#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;