Comparaison des langages
Une différence majeure entre Python et C est la manière dont on exécute un programme dans chacun des deux langages :
Python utilise un interpréteur qui lit le code source et l’exécute directement sur toute cible matérielle sur laquelle l’interpréteur Python est installé ;
C nécessite une étape de compilation spécifique pour produire un exécutable sur chaque cible matérielle. Par exemple un même code doit être compilé une fois pour Windows et une autre fois pour Linux.
Python utilise un interpréteur pour exécuter directement le code source
Un programme C peut être exécuté sans étape de compilation préalable
Pour exécuter un programme C, la première étape nécessaire est la
En C, un même code source doit être pour chaque cible matérielle différente
Structuration du code
Pour une première comparaison de la façon d’écrire et d’exécuter du code dans chacun des deux langages, considérons le calcul du PGCD de deux entiers selon l’algorithme d’Euclide
pgcd(a, b) = pgcd( b, reste(a, b) ) pour b ≠ 0
pgcd(a, 0) = a
PGCD : code Python
Le code Python correspondant au calcul du PGCD selon l’algorithme d’Euclide est donné ci dessous:
1# gcd.py
2
3def gcd(a, b):
4 if b == 0:
5 return a
6 else:
7 return gcd(b, a % b)
8
9def main():
10 print("GCD: " + str(gcd(24, 40)))
11
12if __name__ == "__main__":
13 main()
En Python le code est organisé en quatre parties :
l’importation de bibliothèques externes. Il n’y en a pas besoin ici, et cette partie est vide ;
la définition des variables globales. Il n’y en a pas besoin ici, et cette partie est vide ;
la définition des fonctions secondaires. Ici une seule fonction secondaire,
gcd()qui prend en argument deux nombres, implémente l’algorithme d’Euclide, et retourne le PGCD (lignes 3-7) ;la définition de la fonction principale
main()qui appelle les fonctions précédentes. Icigcd()est appelée avec les arguments 24 et 40, puis le résultat est affiché (lignes 9-10) ;l’appel de la fonction
main()déclenché lors de l’exécution du programme (lignes 12-13).
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire e3-programmation-labs-student et que le container Docker soit lancé.
Créer un fichier
exercices/gcd.pyet copier le code ci dessus ;Exécuter le programme avec la commande
python3 gcd.py;Vérifier que le résultat affiché est correct.
Lorsque c’est le cas :
git add .
git commit -m "Calcul du PGCD avec Python"
git push
PGCD : code C
Voici le même programme écrit en C :
1// gcd.c
2
3#include <stdio.h>
4
5int gcd(int a, int b)
6{
7 if (b == 0)
8 {
9 return a;
10 }
11 else
12 {
13 return gcd(b, a % b);
14 }
15}
16
17int main()
18{
19printf("GCD: %d\n", gcd(24, 40));
20return 0;
21}
L’organisation du code est quasi identique :
l’importation des bibliothèques externes (ligne 3) ;
la définition des variables globales. Il n’y en a pas besoin ici, et cette partie est vide ;
la définition des fonctions secondaires (lignes 5-15) ;
la définition de la fonction principale
main()(lignes 17-21) ;il n’y a pas d’appel explicite de
main(), celui ci est déclenché par l’exécution du programme.
Il y a cependant des différences dans le code proprement dit :
pour utiliser
printf(), il faut « importer » une bibliothèque (ligne 3), alors que la fonctionprint()fait partie du langage en Python ;les variables doivent être déclarées explicitement alors que Python pratique l’inférence de type. Ici ça concerne les paramètres de la fonction ainsi que sa valeur de retour (ligne 5) ;
la structuration du code est définie par une paire d’accolades alors qu’en Python elle est définie par l’indentation ;
toujours pour la structuration, chaque instruction C se termine par un
;;le prédicat d’un test (ligne 7) doit être encapsulé dans une paire de parenthèses
( );il est courant que la fonction
main()retourne 0 (ligne 20) pour signifier au système d’exploitation que tout s’est bien passé.
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire e3-programmation-labs-student et que le container Docker soit lancé.
Créer le fichier
exercices/gcd.cet copier le code ci dessus ;Compiler le programme avec la commande
gcc -std=c99 -Wall -Wextra gcd.c -o gcd;Exécuter le programme avec la commande
./gcd;Vérifier que le résultat affiché est correct.
Lorsque c’est le cas :
git add .
git commit -m "Calcul du PGCD avec C"
git push
La structure d’un programme Python et d’un programme C sont similaires
En Python, la fonction main est appelée automatiquement au démarrage du programme
En C, il est nécessaire d’importer
stdio.hpour pouvoir utiliserprintf()Le compilateur utilisé dans les exemples C de ce cours est
On vient de constater qu’il y a une grande similitude entre la structuration des deux langages. Observons maintenant les différences de code mises en évidence ici de manière approfondie.
Les variables
Les variables sont des zones de mémoire nommées qui permettent de stocker des valeurs. Elles sont utilisées pour mémoriser des informations et les réutiliser dans le programme.
Les variables en C
Le langage C nécessite de déclarer explicitement le type des variables utilisées. Cette connaissance est utilisée par le compilateur et permet d’optimiser la mémoire et le temps d’exécution.
Les principaux types sont :
intpour les entiers (32 bits) ;doublepour les nombres rééls (64 bits) ;charpour les caractères (8 bits).
Note
C dispose en fait de nombreux autres types de données qu’on n’abordera pas dans le cadre de ce cours. La table ci dessous en donne la liste, les bornes des valeurs qu’ils permettent de représenter sans erreur, et le spécificateur à utiliser avec printf(). Les valeurs ci dessous sont dépendantes de la plateforme matérielle (CPU) et logicielle (compilateur). Elles sont données ici pour une architecture Intel(R) Core(TM) i7-6700 CPU, une distribution Linux Ubuntu 20.04 et le compilateur gcc 9.4.0.
Data Type |
Memory (bytes) |
min |
max |
Format Specifier |
|---|---|---|---|---|
short int |
2 |
-32,768 |
32,767 |
%hd |
unsigned short int |
2 |
0 |
65,535 |
%hu |
unsigned int |
4 |
0 |
4,294,967,295 |
%u |
int |
4 |
-2,147,483,648 |
2,147,483,647 |
%d |
long int |
8 |
-2,147,483,648 |
2,147,483,647 |
%ld |
unsigned long int |
4 |
0 |
4,294,967,295 |
%lu |
long long int |
8 |
-9,223,372,036,854,775,808 |
9,223,372,036,854,775,807 |
%lld |
unsigned long long int |
8 |
0 |
18,446,744,073,709,551,615 |
%llu |
char |
1 |
-128 |
127 |
%c |
signed char |
1 |
-128 |
127 |
%c |
unsigned char |
1 |
0 |
255 |
%c |
float |
4 |
1.2E-38 |
3.4E+38 |
%f |
double |
8 |
2.3E-308 |
1.7E+308 |
%lf |
long double |
12 |
3.4E-4932 |
1.1E+4932 |
%Lf |
Contrairement à Python :
C n’a pas de type booléen. La vérité est stockée dans un
int(ce qui n’est pas très efficace d’un point de vue encombrement mémoire). Dans un prédicat,0est évalué à « False » et tout autre nombre entier est évalué à « True » ;le typage en C est statique, c’est à dire qu’une même variable ne peut pas changer de type au cours de la vie d’un programme.
Pour approfondir la notion de vérité en C, répondez aux questions suivantes.
Quel est le nombre d’octets utilisé pour stocker un entier ?
Quel est le nombre de bits utilisé pour stocker un entier ?
Quel est le nombre minimal théorique de bits nécessaire pour stocker un booléen ?
Déclaration et affectation
L’instruction ci dessous déclare une variable x de type int:
int x;
Ceci réserve un espace nommé dans la mémoire, à une adresse définie par le compilateur, de taille définie par le type. La valeur est indéterminée.
On lui affecte ensuite une valeur avec l’instruction :
x = 37;
On peut également effectuer les 2 opérations avec une seule instruction :
int x = 37;
On a une bijection entre la zone mémoire réservée et le nom qu’on lui attribue. C’est une différence majeure avec Python, on aura l’occasion de le voir plus loin.
Important
Une variable doit être déclarée une fois et une seule dans le domaine dans lequel elle est utilisée, avec deux possibilités :
déclaration et initialisation à deux endroits distincts du code ;
déclaration et initialisation sur la même ligne.
Si on tente de lui affecter un type différent, le compilateur effectue la conversion nécessaire si elle est possible/pertinente. Ci dessous, conversion d’un nombre décimal en entier :
int x = 37.0; // x = 37
Déclarer une autre variable et lui affecter le contenu d’une autre réserve un autre espace mémoire. Ainsi le code :
int x = 37;
int y = x;
va réserver un nouvel espace mémoire qui différera du premier :
par son adresse ;
et par le nom qui permet de le référencer.
Les 4 derniers digits hexadécimaux de l’adresse de
xsontLes 4 derniers digits hexadécimaux de l’adresse de
ysont4 digits hexadécimaux représentent octets
Sachant que
xest unint, le compilateur a réservé une adresse mémoire contigüe poury
En C, le type des variables doit être déclaré explicitement avant leur utilisation
C dispose d’un type booléen natif
En C, une même variable peut changer de type au cours de l’exécution d’un programme
Le type C pour stocker un entier sur 32 bits est
Le type C pour stocker un nombre réel sur 64 bits est
Dans un prédicat C, la valeur
0est évaluée àUne variable C doit être déclarée dans son domaine d’utilisation
Les variables en Python
C est un langage ancien et ne disposait pas des méthodes modernes de gestion automatique de la mémoire. Pour garantir une exécution rapide, il était nécessaire d’interagir directement avec la mémoire. Ce mécanisme est implémenté à très bas niveau dans le langage et perdure aujourd’hui, ce qui lui assure toujours une grande vitesse d’exécution, au détriment d’une certaine complexité de programmation.
En revanche Python, dispose d’un mécanisme automatisé de gestion de mémoire qui permet :
de réserver automatiquement un emplacement en mémoire en fonction de l’objet manipulé ;
de faire croître dynamiquement la taille réservée tout au long de la vie de l’objet ;
de libérer automatiquement la mémoire lorsque l’objet n’est plus utilisé.
Pour illustrer ce fonctionnement différent, observons l’affectation d’une variable en Python.
>>> x = 37
>>> id(x)
1658682502576
Un objet est créé en mémoire, et une référence lui est affectée. La fonction id() peut être vue comme une fonction de localisation en mémoire mais, contrairement au C, ne véhicule pas d’information sur la position physique en mémoire.
Contrairement à C, l’affectation de x à une nouvelle variable ne créée pas de nouvel objet en mémoire, comme le montre le code suivant
>>> y = x
>>> id(y)
1658682502576
>>> x is y
True
qui peut s’illustrer avec le schéma suivant :
En Python, l’affectation
y = xcrée un nouvel objet en mémoireEn Python, la gestion de la mémoire est automatique
La fonction
id()en Python donne la position physique de l’objet en mémoireAprès
x = 37puisy = xen Python, l’expressionx is yrenvoieEn Python, une variable est une vers un objet en mémoire
Variables locales
En C, si des variables locales sont nécessaires, la déclaration de ces variables est placée au début de la fonction qui les utilise. L’algorithme d’Euclide utilisé précédemment n’avait pas besoin de variables additionnelles. Les seuls paramétres a et b suffisaient.
Si l’on omet de déclarer une variable, le compilateur produit une erreur avec un message indicatif. La lecture approfondie de ce type de message est une étape nécessaire dans la conception d’un programme.
En Python, les variables locales sont créées automatiquement lors de leur première affectation.
En C, les variables locales sont déclarées au début de la fonction qui les utilise
En C, omettre la déclaration d’une variable est ignoré silencieusement par le compilateur
En Python, les variables locales doivent être déclarées explicitement avant leur première utilisation
En C, l’oubli de la déclaration d’une variable provoque une
Délimitation des blocs
Un bloc d’instructions est un ensemble d’instructions regroupées pour être exécutées ensemble. Il est souvent utilisé dans les structures de contrôle (conditions, boucles, fonctions, etc.) pour définir quelles instructions doivent être exécutées dans un certain contexte.
En Python, les espaces et les tabulations sont utilisés pour délimiter les blocs. Une même information est utilisée pour améliorer la lisibilité du code (pour l’humain) et pour structurer le code (pour la machine).
Avertissement
Un code mal structuré peut être valide syntaxiquement et s’exécuter correctement si l’indentation a un sens pour une autre structure de contrôle. Mais le comportement du programme sera alors différent de celui attendu.
En C, les blocs sont délimités par des accolades { } (pour la machine). Il faut ajouter des informations de lisibilité pour améliorer la compréhension du code. Les espaces et les tabulations sont utilisés pour cela. Ce qui signifie que le code ci dessous est parfaitement valide (pour la machine), quoique assez illisible (pour l’humain).
1// gcd2.c
2#include <stdio.h>
3int gcd(int a, int b){if (b == 0){return a;}else{return gcd(b, a % b);}}int main(){printf("GCD: %d\n", gcd(24, 40));return 0;}
Python force le programmeur à écrire un programme lisible. Ce n’est pas le cas de C mais la plupart des environnements de développements modernes, VS Code par exemple, utilisent des formatteurs qui indentent le code de façon efficace.
En C, les accolades { } sont utilisées pour structurer le code. Elles sont optionnelles si le bloc ne contient qu’une seule instruction. Ainsi, la fonction ci dessous est du code C valide
1int gcd(int a, int b)
2{
3if (b == 0)
4 return a;
5else
6 return gcd(b, a % b);
7}
que l’on peut, sans perte de lisibilité, également écrire :
1int gcd(int a, int b)
2{
3 if (b == 0) return a;
4 else return gcd(b, a % b);
5}
En Python, l’indentation seule délimite les blocs d’instructions
En C, les accolades { } sont toujours obligatoires, même si un bloc ne contient qu’une seule instruction
Un code mal indenté peut quand même s’exécuter correctement
En C, les blocs de code sont délimités par des
En Python, l’indentation sert à la fois à la lisibilité et à
Les commentaires
Le code source d’un programme contient des instructions écrites par un humain à destination d’une machine dans un langage déterminé (C ou Python par exemple).
Il doit également contenir des informations facilitant la compréhension, la relecture et la maintenance, pour celui qui l’a écrit mais également pour ceux amenés à intervenir sur le code à un moment ou un autre de la vie du programme.
On signale à la machine que ces instructions ne sont pas a exécuter avec une syntaxe particulière :
en Python, tous les caractères à droite du caractère
#seront ignorés ;en C :
tous les caractères à droite des caractères
//seront ignorés (commentaires de ligne) ;tous les caractères compris entre
/*et*/seront ignorés (commentaires de bloc).
Exemples de commentaires en Python:
# gcd.py # Calcul du Greatest Common Divisor def gcd(a, b): # Algorithme d'Euclide
Exemples de commentaires en C:
/* gcd.c Calcul du Greatest Common Divisor */ int gcd(int a, int b) // Algorithme d'Euclide
Une bonne pratique est de commenter son code en expliquant pourquoi on écrit une instruction et éventuellement comment si l’implémentation est complexe.
En Python, le caractère
#débute un commentaire de fin de ligneEn Python, les commentaires de bloc s’écrivent entre
/*et*/En C, un commentaire de ligne commence par les caractères
En C, un commentaire de bloc se termine par les caractères
Il est recommandé de commenter en expliquant pourquoi une instruction est nécessaire plutôt que comment elle fonctionne
L’affichage
printf() en C
En C, l’affichage est contrôlé par la fonction printf(). Cette fonction comporte n paramètres (au moins 1) :
- le premier paramètre est une chaîne de formatage :
contenant un texte fixe ;
et éventuellement
n-1places réservées pour l’affichage den-1variables ;
les
n-1variables éventuelles à afficher.
Ainsi l’instruction
printf("GCD: %d\n", gcd(24, 40))
se décompose en :
la chaîne de formatage :
"GCD: %d\n"dans laquelle%dest un espace réservé pour la valeur à afficher. Les autres caractères sont imprimés tels quels ;et la valeur à afficher :
gcd(24, 40)qui sera insérée dans l’espace réservé.
Dans la chaine de formatage, on utilise le caractère spécial \n qui insère un saut de ligne. Voici les caractères spéciaux les plus courants:
\n: saut de ligne ;\t: tabulation ;\\: le caractère\;\": le caractère".
Les espaces réservés suivants sont les plus courants :
%d: nombre entier. Par exemple 1000 ;%f: nombre réél en notation décimale. Par exemple 1000.0 ;%e: nombre réél en notation scientifique. Par exemple 1.000000e3 ;%c: caractère alphanumérique ;%s: chaîne de caractères.
Il y a beaucoup d’autres paramètres permettant de contrôler l’affichage. Une référence complète ici.
On souhaite écrire un programme qui affiche le résultat de l’opération de multiplication de deux entiers, de telle sorte qu’un exemple d’affichage dans le terminal puisse être:
$ ./mult
3 x 5 = 15
$
Quelle est le contenu de la chaine de formatage correspondante ?
print() en Python
La fonction print() en Python est plus simple à utiliser que printf() en C. Elle prend un nombre variable de paramètres, et affiche leur contenu dans le terminal. Par défaut, les paramètres sont séparés par un espace et un saut de ligne est inséré à la fin de l’affichage.
On peut réaliser le même affichage que précédemment avec l’instruction :
print(3, "x", 5, "=", 3*5)
Pour un formattage plus évolué, on utilise des f-strings. Il s’agit de chaînes de caractères précédées d’un f et dans lesquelles on peut insérer des expressions entre accolades { }. Ainsi, l’affichage précédent peut être réalisé avec l’instruction :
x = 3
y = 5
print(f"{x} x {y} = {x*y}")
Les fonctions
C’est une bonne pratique que d’encapsuler tout le code dans des fonctions mais ce n’est pas requis par le langage Python. Pour C au contraire, cette structuration est indispensable.
Reprenons le programme permettant de calculer le PGCD utilisé en début de chapitre.
En langage C
La ligne 5 représente la signature de la fonction, que l’on peut décomposer en trois parties :
le type de la valeur de retour :
int;le nom de la fonction :
gcd();la liste de ses paramètres formels typés placés entre parenthèses :
int a, int b.
Le corps de la fonction est placé entre accolades { } et doit retourner une valeur dont le type est cohérent avec la signature.
Si une fonction ne retourne rien, le type void est utilisé.
Dans un programme C découpé en fonctions, il doit exister une fonction principale main(). Elle est ici définie à la ligne 17 et ne prend aucun paramètre. Elle appelle la fonction gcd() avec les paramètres 24 et 40, puis affiche le résultat.
La fonction main() est obligatoire dans un programme C. Elle peut prendre des paramètres, mais ce n’est pas le cas ici. Elle retourne un entier dont la valeur est utilisée par le système d’exploitation pour connaitre le statut du programme après son exécution. Par convention, la valeur 0 est retournée si tout s’est bien passé, et un entier différent de 0 en cas de problème dans l’exécution.
Note
Rigoureusement on peut placer ces fonctions à la suite de la fonction main() mais il faut alors que le prototype des fonctions en question soit placé avant main(), le plus souvent dans un fichier .h. A titre d’exemple, la fonction printf() est utilisable dans main() car elle est déclarée dans stdio.h.
En Python
Pour le langage Python, la signature des fonctions n’impose pas de préciser le type des arguments ni celui de la valeur de retour. C’est une caractéristique du typage dynamique de Python, qui permet d’écrire des fonctions plus génériques. Ceci ne s’applique pas pour cet exemple car la fonction gcd() ne peut être utilisée qu’avec des entiers.
Une bonne pratique consiste cependant à utiliser le typing pour informer sur le type des paramètres et de la valeur de retour. Ainsi, la signature de la fonction gcd() pourrait être écrite comme suit :
def gcd(a: int, b: int) -> int:
if b == 0:
return a
else:
return gcd(b, a % b)
Cette information n’est pas utilisée par l’interpréteur Python mais peut être utilisée par des outils d’analyse statique du code pour détecter des incohérences dans le typage.
En Python, l’appel de main() n’est pas automatique, il faut le déclencher.
En C, la signature d’une fonction doit préciser le type des paramètres et de la valeur de retour
En Python, il est obligatoire de typer les paramètres dans la signature d’une fonction
En C, la fonction main est obligatoire dans tout programme
En Python, l’appel de la fonction main est automatique au démarrage du programme
Une fonction C qui ne retourne aucune valeur utilise le type
Par convention, la fonction main retourne si le programme s’est exécuté sans erreur
Les opérateurs
La plupart des opérateurs sont communs à Python et C. Voici les principaux.
Les opérateurs arithmétiques
Les opérateurs arithmétiques permettent le calcul mathématique.
Opérateur C |
Description |
Example |
Python |
|---|---|---|---|
|
Addition |
a + b |
|
|
Soustraction |
a - b |
|
|
Multiplication |
a * b |
|
|
Division |
a / b |
|
|
Reste de la division entière |
a % b |
|
|
Incrémentation |
a++ |
|
|
Décrémentation |
a-- |
Les opérateurs relationnels
Les opérateurs relationnels permettent de mettre en oeuvre les opérations de comparaison.
Opérateur C |
Description |
Example |
Python |
|---|---|---|---|
|
Egalité |
(a == b) |
|
|
Différence |
(a != b) |
|
|
Supérieur |
(a > b) |
|
|
Inférieur |
(a < b) |
|
|
Supérieur ou égal |
(a >= b) |
|
|
Inférieur ou égal |
(a <= b) |
|
Les opérateurs logiques
Les opérateurs logiques permettent de construire des prédicats.
Opérateur C |
Description |
Example |
Python |
|---|---|---|---|
|
Et logique |
(a && b) |
|
|
Ou logique |
(a || b) |
|
|
Négation |
( !a ) |
|
Les opérateurs d’affectation
Les opérateurs d’affectation permettent d’affecter une valeur à une variable.
Opérateur C |
Description |
Example |
Python |
|---|---|---|---|
|
Affectation |
a = b |
|
|
Addition et affectation |
a += 1 |
|
|
Soustraction et affectation |
a -= 1 |
|
|
Multiplication et affectation |
a *= 2 |
|
|
Division et affectation |
a /= 2 |
|
|
Modulo et affectation |
a %= 2 |
|
Priorité des opérateurs
Lors de l’évaluation d’expressions complexes, des règles de priorité s’appliquent. En cas de doute, il sera sage d’utiliser les parenthèses pour contrôler l’ordre d’application des opérateurs.
Par exemple, l’expression x = 7 + 3 * 2 est évaluée à 13 parce que l’opérateur * a une priorité supérieure à +. Il sera cependant sage d’écrire x = 7 + (3 * 2).
Voici la liste des opérateurs classés par ordre de priorités décroissantes.
Opérateurs C |
Associativité |
|---|---|
|
gauche à droite |
|
droite à gauche |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
droite à gauche |
Opérateurs Python |
Associativité |
|---|---|
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
|
gauche à droite |
L’équivalent Python de l’opérateur C
&&estL’équivalent Python de l’opérateur C
||estL’équivalent Python de l’opérateur C
!estPython dispose d’un opérateur d’incrémentation
++La valeur de l’expression
7 + 3 * 2estL’opérateur
%calcule le de la division entièrePython dispose de l’opérateur
**(puissance) que C ne possède pas nativement