Algorithmes et C appliqués aux Systèmes Numériques
Les appels systèmes, les erreurs et la chaine de compilation
John Samuel
CPE Lyon
Année : 2023-2024
Courriel : john(dot)samuel(at)cpe(dot)fr
Objectifs
La portée des variables
Préprocesseur
Les appels systèmes
Les erreurs
La chaine de compilation
Une variable constante
/* Une variable constante
*/ #include <stdio.h>
int main () {
const int year = 2017 ; // une variable constante
year = 2019 ; // tentative de modification d'une variable constante
printf ("C'est l'annee %d" , year );
return 0; }
Erreur pendant la compilation
$ gcc bonjour.c
const.c: In function ‘main’:
const.c:5:8: error: assignment of read-only variable ‘year’
year = 2019;
Une variable globale
/* affiche un message à l'écran en utilisant une variable globale
*/ #include <stdio.h>
int year = 2017 // une variable globale;
int main ()
{
printf ("C'est l'annee %d" , year ); //affiche 2017 return 0; }
Une variable globale
/* affiche un message à l'écran en utilisant une variable globale
*/ #include <stdio.h>
int main ()
{
printf ("C'est l'annee %d" , year ); return 0; }
int year = 2017 // une variable globale;
Erreur pendant la compilation
$ gcc bonjour.c
bonjour.c: In function ‘main’:
bonjour.c:5:30: error: ‘year’ undeclared (first use in this function)
5 | printf("C'est l'annee %d", year);
| ^~~~
bonjour.c:5:30: note: each undeclared identifier is reported only once for each function it appears in
Une variable locale
/* affiche un message à l'écran en utilisant une variable locale
*/ #include <stdio.h>
int year = 2017 // une variable globale;
int main ()
{
int year = 2018 // une variable locale;
printf ("C'est l'annee %d" , year ); //affiche 2018 return 0; }
Une variable locale
/* affiche un message à l'écran en utilisant une variable locale
*/ #include <stdio.h>
int year = 2017 // une variable globale;
int main ()
{
int year = 2018 // une variable locale;
{
int year = 2019 // une variable locale;
printf ("C'est l'annee %d" , year ); //affiche 2019
}
printf ("C'est l'annee %d" , year ); //affiche 2018
return 0; }
1. Passage par valeur
void
echange(
int
a ,
int
b
) {
int
temp
= a ;
a
= b ;
b
= temp ;
}
int main ()
{
int a = 10;
int b = 20;
echange (a , b );
printf ("a: %d, b: %d" , a , b ); //affiche 10, 20 return 0; }
2.1 Passage par référence: une variable
void
echange(
int
*a ,
int
*b
) {
int
temp
= *a ;
*a
= *b ;
*b
= temp ;
}
int main ()
{
int a = 10;
int b = 20;
echange (&a , &b );
printf ("a: %d, b: %d" , a , b ); //affiche 20, 10 return 0; }
2.2. Passage par référence: un tableau
void
affichage(
char
message[10]
) {
printf ("%s\n" , message );
}
int main ()
{
char message[10] =
"Bonjour" ;
affichage (message );
return 0; }
2.2. Passage par référence: un tableau
void
affichage(
char
message[]
) {
printf ("%s\n" , message );
}
int main ()
{
char message[10] =
"Bonjour" ;
affichage (message );
return 0; }
2.2. Passage par référence: un tableau
void
affichage(
char
*message
) {
printf ("%s\n" , message );
}
int main ()
{
char message[10] =
"Bonjour" ;
affichage (message );
return 0; }
2.2. Passage par référence: un tableau
void
affichage(
int
tableau[2][2]
) {
for ( int i = 0;
i < 2; i ++) {
for ( int j = 0;
j < 2; j ++) {
printf ("%d\n" ,
tableau [i ][j ]);
}
}
}
int main ()
{
int prix [2][2] = {
{11, 12},
{13, 14}};
affichage (prix );
return 0; }
2.2. Passage par référence: un tableau
void
affichage(
int
tableau[][]
) {
for ( int i = 0;
i < 2; i ++) {
for ( int j = 0;
j < 2; j ++) {
printf ("%d\n" ,
tableau [i ][j ]);
}
}
}
int main ()
{
int prix [2][2] = {
{11, 12},
{13, 14}};
affichage (prix );
return 0; }
2.2. Passage par référence: un tableau
Erreur pendant la compilation
$ gcc tableau.c
tableau.c:3:21: error: array type has incomplete element type ‘int[]’
3 | void affichage( int tableau[][] ) {
| ^~~~~~~
tableau.c:3:21: note: declaration of ‘tableau’ as multidimensional array must have bounds for all dimensions except the first
tableau.c: In function ‘main’:
tableau.c:15:13: error: type of formal parameter 1 is incomplete
15 | affichage(prix);
2.2. Passage par référence: un tableau
void
affichage(
int
tableau[][2]
) {
for ( int i = 0;
i < 2; i ++) {
for ( int j = 0;
j < 2; j ++) {
printf ("%d\n" ,
tableau [i ][j ]);
}
}
}
int main ()
{
int prix [2][2] = {
{11, 12},
{13, 14}};
affichage (prix );
return 0; }
2.2. Passage par référence: un tableau
void
affichage(
int
(*tableau)[2]
) {
for ( int i = 0;
i < 2; i ++) {
for ( int j = 0;
j < 2; j ++) {
printf ("%d\n" ,
tableau [i ][j ]);
}
}
}
int main ()
{
int prix [2][2] = {
{11, 12},
{13, 14}};
affichage (prix );
return 0; }
2.2. Passage par référence: un tableau
void
affichage(
int
**tableau ,
int
lignes ,
int
colonnes
) {
for ( int i = 0;
i < lignes ; i ++) {
for ( int j = 0;
j < colonnes ; j ++) {
printf ("%d\n" ,
tableau [i ][j ]);
}
}
}
int main ()
{
int **prix , lignes , colonnes ;
....
affichage (prix ,
lignes ,
colonnes );
return 0; }
un tableau à deux indices
Passage par référence: un tableau
int main ()
{
int **tableau , lignes = 2, colonnes = 10;
tableau = calloc (sizeof(int *), lignes );
for ( int i = 0;
i < lignes ; i ++) {
tableau [i ] = calloc (sizeof(int ), colonnes );
}
for ( int i = 0;
i < lignes ; i ++) {
for ( int j = 0;
j < colonnes ; j ++) {
tableau [i ][j ] = i+j;
}
}
...
Passage par référence: un tableau
...
affichage (tableau ,
lignes ,
colonnes );
for ( int i = 0;
i < lignes ; i ++) {
free (tableau [i]);
}
free (tableau );
return 0; }
cercle.c
#define PI 3.14159
float area (
float radius ) {
return (PI * radius
* radius );
}
Prototype (defs.h)
#define PI 3.1415926535
cercle.c
#include "defs.h"
#ifndef PI // Si PI n'est pas défini
#define PI 3.14159
#endif
float area (
float radius ) {
return (PI * radius
* radius );
}
Remarque 1: PI n'est pas une variable
Remarque 2: Utilisez l'option gcc -E pour comprendre l'objectif d'un préprocesseur
Prototype (defs.h)
Erreur: PI n'est pas une variable
#include "defs.h"
#ifndef PI
#define PI 3.14159
#endif
float area (
float radius ) {
PI = 3.14;
return (PI * radius
* radius );
}
Erreur (compilation)
Error: lvalue required as left operand of assignment
PI = 3.15;
^
operators.h
int
add(
int ,
int );
L'utilisation
#include
"operators.h" // en-têtes(headers)
#include
"operators.h" // pas d'erreurs
operators.h
int
num = 20;
L'utilisation
#include
"operators.h" // en-têtes(headers)
#include
"operators.h" // erreur
$ gcc operator.c
note: previous definition of ‘num’ was here
int num=20;
operators.h (bonne pratique)
#ifndef __OPERATORS_H__
#define __OPERATORS_H__
int
num = 20;
int
add(
int ,
int );
#endif //__OPERATORS_H__
L'utilisation
#include
"operators.h" // en-têtes(headers)
#include
"operators.h" // 2eme fois, mais pas d'erreurs
#ifndef PI
#define PI 3.14159
#endif
#ifndef square
#define square(value) value * value
#endif
float area (
float radius ) {
return (PI * square(radius) );
}
Remarque: Il'n y a pas d'espace entre square et (
Source: https://commons.wikimedia.org/wiki/File:AdditiveColorMixiing.svg
struct couleur1{
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned int count;
};
struct couleur2{
unsigned char rouge;
unsigned int count;
unsigned char vert;
unsigned char bleu;
};
printf (
"size- couleur1: %lu, couleur2: %lu\n" ,
sizeof (
struct
couleur1 ),
sizeof (
struct
couleur2 ));
Question: Quel est l'affichage de ce programme?
Alignement d'un octet
couleur1
couleur2
Alignement de 4 octets
couleur1
couleur2
Alignement en utilisant gcc
En utilisant gcc
#pragma pack(push)
#pragma pack(1) //alignement d'un octet
struct couleur3{
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned int count;
};
#pragma pack(pop)
printf (
"%lu\n" ,
sizeof (struct couleur3 ));
Remarque: L'affichage de ce programme: 7
struct couleur4{ //alignement de 4 octets
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned char _pad;
unsigned int count;
};
struct couleur5{ //alignement de 4 octets
unsigned char rouge;
unsigned char _pad1[3];
unsigned int count;
unsigned char vert;
unsigned char bleu;
unsigned char _pad2[2];
};
Remarque: Utilisez l'option gcc -Wpadded pour savoir si une structure nécessite du padding our être alignée
struct couleur{
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned int count;
};
int main () {
struct couleur c1 ;
struct couleur *scptr = &c1 ;
c1.bleu = 0xff ;
scptr->bleu = 0x01 ;
printf (
"c1.bleu: %hhx\n" , c1.bleu ); //0x01
(*scptr).bleu = 0x22 ;
printf (
"c1.bleu: %hhx\n" , c1.bleu ); //0x22
}
Source: https://commons.wikimedia.org/wiki/File:AdditiveColorMixiing.svg
struct couleur *scptr = &c1 ;
Les notations équivalents
(*scptr).bleu
scptr->bleu
struct couleur{
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned int count;
};
void change (
struct couleur *c) {
c->bleu = 0x01 ;
}
int main () {
struct couleur c1 ;
c1.bleu = 0xff ;
change(&c1 );
printf (
"c1.bleu: %hhx\n" , c1.bleu ); //0x01
}
struct couleur{
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned int count;
};
void nochange (
struct couleur c) {
c.bleu = 0x03 ;
}
int main () {
struct couleur c1 ;
c1.bleu = 0xff ;
nochange(c1 );
printf (
"c1.bleu: %hhx\n" , c1.bleu ); //0xff
}
Une liste de couleurs simplement chaînée
Une liste de couleurs simplement chaînée
struct couleur{
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned int count;
struct couleur *next;
};
Une liste de couleurs simplement chaînée
int main () {
struct couleur first , c1 , c2 ;
struct couleur *cptr ;
first.next = &c1 ;
c1.next = &c2 ;
c2.next = NULL ;
cptr = &first ;
while (cptr != NULL ) { //navigation
printf (
"cptr->bleu: %hhx\n" , cptr->bleu );
cptr = cptr->next ; //couleur suivante
}
}
Une liste d'entiers simplement chaînée
Une liste d'entiers simplement chaînée
struct element{
unsigned int numero;
struct element *suivant;
};
// insertion d'un élement dans une liste
void insertion (struct element *, int );
// parcours de la liste
void parcours (struct element *);
La saisie d'entiers par l'utilisateur
int main () {
struct element premier ;
premier.suivant = NULL ;
while (1) {
int num ;
char strnum [50];
fgets (strnum , sizeof(strnum ), stdin );
if (strcmp (strnum , "FIN\n" ) == 0) {
break ;
}
sscanf (strnum , "%d\n" , &num );
insertion (&premier , num );
}
parcours (&premier );
}
Une liste d'entiers simplement chaînée: insertion
Insertion d'un élément dans une liste simplement chaînée
void insertion (struct element *premier , int num ) {
struct element *nouveau ;
nouveau = malloc (sizeof (*nouveau ));
nouveau->num = num;
nouveau->suivant = premier->suivant ;
premier->suivant = nouveau ;
}
Une liste d'entiers simplement chaînée: parcours
Parcours d'une liste simplement chaînée
void parcours (struct element *premier ) {
struct element *elem = premier ;
while (elem != NULL ) {
printf("%d\n" , elem->num );
elem = elem->suivant ;
}
}
Une liste de couleurs doublement chaînée
Une liste de couleurs doublement chaînée
struct couleur{
unsigned char rouge;
unsigned char vert;
unsigned char bleu;
unsigned int count;
struct couleur *next;
struct couleur *prev;
};
int main () {
struct couleur first , c1 , c2 , last ;
struct couleur *cptr = &last ;
first.next = &c1 ;
first.prev = NULL ;
last.next = NULL ;
last.prev = &c2 ;
c1.next = &c2 ;
c1.prev = &first ;
c2.next = &last ;
c2.prev = &c1 ;
while (cptr != &first ) { //navigation
printf (
"cptr->bleu: %hhx\n" , cptr->bleu );
cptr = cptr->prev ; //couleur précédente
}
}
Une liste d'entiers simplement chaînée
Insertion d'un élément et parcours d une liste doublement chaînée
struct element {
int num ;
struct element *suivant ;
struct element *precedent ;
};
struct liste {
struct element premier ;
struct element dernier ;
};
void insertion_debut (struct liste *, struct element *);
void insertion_fin (struct liste *, struct element *);
void parcourir_debut (struct liste *);
void parcourir_fin (struct liste *);
Une liste de couleurs doublement chaînée: insertion
Une liste d'entiers doublement chaînée
void insertion_debut (struct liste *liste ,
struct element *nouveau ) {
nouveau->suivant = liste->premier.suivan t;
nouveau->precedent = &liste->premier ;
liste->premier.suivant->precedent = nouveau ;
liste->premier.suivant = nouveau ;
}
Une liste de couleurs doublement chaînée: insertion
Une liste d'entiers doublement chaînée
void insertion_fin (struct liste *liste ,
struct element *nouveau ) {
nouveau->suivant = &liste->dernier ;
nouveau->precedent = liste->dernier.precedent ;
liste->dernier.precedent->suivant = nouveau ;
liste->dernier.precedent = nouveau ;
}
Une liste d'entiers doublement chaînée
void parcourir_debut (struct liste *liste ) {
struct element *elem = liste->premier.suivant ;
while (elem != &liste->dernier ) {
printf ("%d\n" , elem->num );
elem = elem->suivant ;
}
}
Une liste d'entiers doublement chaînée
void parcourir_fin (struct liste *liste ) {
struct element *elem = liste->dernier.precedent ;
while (elem != &liste->premier ) {
printf ("%d\n" , elem->num );
elem = elem->precedent ;
}
}
La saisie d'entiers par l'utilisateur
int main () {
struct liste liste ;
liste.premier.suivant = &liste.dernier ;
liste.dernier.precedent = &liste.premier ;
liste.premier.precedent = NULL ;
liste.dernier.suivant = NULL ;
La saisie d'entiers par l'utilisateur
while (1) {
char strnum [50];
struct element *elem = malloc (sizeof (*elem ));
fgets (strnum , sizeof (strnum ), stdin );
if (strcmp(strnum, "FIN\n" ) == 0) {
break ;
}
sscanf (strnum , "%d\n" , &elem->num );
insertion_fin (&liste, elem );
}
parcourir_debut (&liste );
parcourir_fin (&liste );
}
int
add(
int
a ,
int
b
) {
return
a
+ b ;
}
int
subtract(
int
a ,
int
b
) {
return
a
- b ;
}
int main () {
int (*func )(int , int ); //pointeur de function
char op = '-' ;
int num1 = 20 , num2 = 30 ;
if (op == '+' ) {
func = add ;
}
else {
func = subtract ;
}
printf ("value: %d\n" ,func (20, 30));
}
/* Fichier: stats.c * la taille d'un fichier
* auteur: John Samuel */ #include
<stdio.h> // en-têtes(headers)
#include
<sys/types.h>
#include
<sys/stat.h>
#include
<unistd.h>
int main (int argc , char ** argv)
{
struct stat sf ;
stat ("./stats.c" , &sf);
printf ("Taille: %ld octets" , sf.st_size );
return 0; }
#include
<stdio.h> // en-têtes(headers)
#include
<sys/types.h>
#include
<sys/stat.h>
#include
<unistd.h>
#include
<stdlib.h>
int main (int argc , char ** argv)
{
struct stat sf ;
int status ;
status = stat (argv[1] , &sf);
if (status == -1) {
perror ("Stats" );
return (EXIT_FAILURE);
}
printf ("Taille: %ld octets" , sf.st_size );
return 0; }
La compilation
$ gcc -o stats stats.c
L'exécution
$./stats stats.c
Taille: ... octets
$ echo $?
0
La compilation
$ gcc -o stats stats.c
L'exécution
$ ./stats nostats
Stats: No such file or directory
$ echo $?
1
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main (int argc , char **argv ) {
if (argc < 2) {
printf ("Usage: readdir path\n" );
return (EXIT_FAILURE);
}
DIR *dirp = opendir (argv [1]);
if (dirp == NULL ) {
perror ("opendir" );
return (EXIT_FAILURE);
}
struct dirent * ent;
while (1) {
ent = readdir (dirp );
if (ent == NULL ) {
break ;
}
printf ("%s\n" , ent->d_name );
}
closedir (dirp );
return (0);
}
Client
Client
#define PORT 8089
int main() {
int socketfd;
int bind_status;
struct sockaddr_in server_addr, client_addr;
/*
* Création d'un socket
* AF_INET: Protocoles Internet IPv4
* SOCK_STREAM: flux d'octets bidirectionnels, basés sur la connexion
*/
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if ( socketfd < 0 ) {
perror("Impossible d'ouvrir un socket" );
return -1;
}
Client
// détails de l'adresse du serveur
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
//Se connecter au serveur
int connect_status = connect(socketfd, (struct sockaddr *)
&server_addr, sizeof(server_addr));
if ( connect_status < 0 ) {
perror("Impossible de se connecter au serveur" );
return -1;
}
Serveur
Serveur
int main() {
int socketfd;
int bind_status;
struct sockaddr_in server_addr, client_addr;
/*
* Création d'un socket
* AF_INET: Protocoles Internet IPv4
* SOCK_STREAM: flux d'octets bidirectionnels, basés sur la connexion
*/
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if ( socketfd < 0 ) {
perror("Impossible d'ouvrir un socket" );
return -1;
}
Serveur
int option = 1;
setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,
&option, sizeof(option));
// détails de l'adresse du serveur
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
bind_status = bind(socketfd, (struct sockaddr *)
&server_addr, sizeof(server_addr));
if (bind_status < 0 ) {
perror("Impossible de se lier à un socket" );
return -1;
}
// Commencez à écouter le socket
listen(socketfd, 10);
char data[1024];
int client_addr_len = sizeof(client_addr);
int client_socket_fd = accept(socketfd,
(struct sockaddr *) &client_addr, &client_addr_len);
if (client_socket_fd < 0 ) {
perror("Impossible d'accepter les demandes des clients" );
return -1;
}
6 étapes de la chaine de compilation
1. Préprocesseur
cercle.c
#ifndef PI
#define PI 3.14159
#endif
float area (
float radius ) {
return (PI * radius
* radius );
}
1. Préprocesseur
$ gcc -E cercle.c
# 1 "cercle.c"
# 1 ""
# 1 ""
# 31 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "" 2
# 1 "cercle.c"
float area(float radius) {
return(3.14 * radius * radius);
}
2. Langage intermédiaire
$ gcc -v bonjour.c # les étapes importantes
$ gcc -save-temps -v bonjour.c # les fichiers *.i, *.s
$ gcc -fdump-tree-all bonjour.c
$ gcc -fdump-rtl-all bonjour.c
2. Langage intermédiaire
$ gcc -fdump-tree-all -c cercle.c
$ cat cercle.c.005t.gimple
area (float radius)
{
float D.1914;
_1 = (double) radius;
_2 = _1 * 3.140000000000000124344978758017532527446746826171875e+0;
_3 = (double) radius;
_4 = _2 * _3;
D.1914 = (float) _4;
return D.1914;
}
3. Optimisation de code
$ gcc -O2 bonjour.c
$ gcc -O3 bonjour.c
Remarque: Autres optimisations: -O1 -O2 -O3 -Os -Ofast -Og
4. Génération de code natif
$ gcc -S bonjour.c
$ cat bonjour.s
4. Génération de code natif et l'optimisation de code
int main () {
int num = 2 + 3;
return (0);
}
Compilation (Pas d'optimisations)
$ gcc -O0 -S add.c
add.s
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $5, -4(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
4. Génération de code natif et l'optimisation de code
int main () {
int num = 2 + 3;
return (0);
}
Compilation (optimisation: 2)
$ gcc -O2 -S add.c
add.s
main:
.LFB0:
.cfi_startproc
xorl %eax, %eax
ret
.cfi_endproc
4. Génération de code
sur une machine d'architecture 64 bits
$ gcc bonjour.c
$ file a.out
a.out: ELF 64-bit LSB shared object, x86-64
4. Génération de code
sur une machine d'architecture 64 bits
$ gcc -march=i686 -m32 bonjour.c
$ gcc -S -march=i686 -m32 bonjour.c
$ file ./a.out
./a.out: ELF 32-bit LSB shared object, Intel 80386
5. Code objet
$ gcc -c client.c
$ gcc -c color.c
$ gcc -o color color.o client.o
5. Code objet (Modifications et recompilation)
$ gcc -c client.c
$ gcc -c color.c
$ gcc -o client client.o color.o
$ vim client.c # Modification du fichier
$ gcc -c client.c
$ gcc -c client.o color.o
$ vim client.c # Modification du fichier
$ gcc -c client.c
$ gcc -o client client.o color.o
Makefile
Makefile et make permettent de créer facilement un exécutable à partir du code source. Il prend en considération la dernière heure de modification des fichiers pour décider de l'exécution ou non d'une ou plusieurs commandes.
$ cat Makefile
client : client.c color.c # création de l'exécutable client
<TAB> gcc -o client client.c color.c
Makefile
$ make
gcc -o client client.c color.c
Makefile
$ cat Makefile
client : client.o color.o # création de l'exécutable client
gcc -o client client.o color.o
client.o : client.c client.h # compilation
gcc -c client.c
color.o : color.c color.h # compilation
gcc -c color.c
Makefile
$ cat Makefile
CC ?= gcc
CFLAGS ?= -Wall -Wextra -g
COBJS ?= client.o color.o
SOBJS ?= server.o color.o
.SUFFIXES : .c .o
SERVER = server
CLIENT = client
Makefile
all : $(SERVER) $(CLIENT) #création des exécutables
$(SERVER ): $(SOBJS) # création de l'exécutable server
$(CC) -o $(SERVER) $(SOBJS)
$(CLIENT ): $(COBJS) # création de l'exécutable client
$(CC) -o $(CLIENT) $(COBJS)
.c.o: # Compilation de tous les fichiers
$(CC) -c $*.c
Exécution d'un Makefile
$ vim server.c # Modification du fichier
$ vim client.c # Modification du fichier
$ vim color.c # Modification du fichier
$ make # Compilation de tous les fichiers et création d'un exécutable.
$ vim color.c # Modification du fichier
$ make # Compilation d'un seul fichier (color.c) et création d'un exécutable.
$ make # Ni compilation ni création d'un nouveau exécutable car aucun fichier n'a été modifié
Références
Crédits d'images