Programming

Toolchain

La toolchain d’un langage de programmation est l’ensemble d’outils et de programmes nécessaires pour écrire, compiler, exécuter, et tester du code dans ce langage. C’est un peu comme la boîte à outils complète d’un développeur, qui permet de passer de simples lignes de code à un programme fonctionnel.

Composants typiques d’une toolchain Voici les éléments que l’on retrouve souvent dans la toolchain d’un langage de programmation :

  1. Compilateur

Le compilateur est l’outil principal qui transforme le code source (le code que tu écris) en code machine (le langage que comprend l’ordinateur). Par exemple, le compilateur de Rust s’appelle rustc, celui de C/C++ est gcc ou clang, et en Java c’est javac. Certains langages interprétés comme Python n’ont pas de compilateur, mais un interpréteur qui exécute le code directement.

  1. Linker

Le linker prend les différentes parties du programme (souvent, des fichiers compilés séparément) et les “lie” ensemble pour créer un fichier exécutable. Cet outil est essentiel pour les programmes plus grands, où le code est souvent divisé en plusieurs morceaux.

  1. Bibliothèques standard

La toolchain inclut souvent des bibliothèques standard, qui fournissent des fonctionnalités de base comme la manipulation de chaînes de caractères, la gestion des fichiers, le réseau, etc. Par exemple, Rust a sa propre bibliothèque standard (std), qui est incluse automatiquement dans la plupart des projets.

  1. Gestionnaire de paquet

Un gestionnaire de paquets permet d’ajouter des bibliothèques externes au projet, les télécharger et les mettre à jour facilement. En Rust, c’est Cargo, en JavaScript c’est npm, et en Python, c’est pip.

  1. Débogueur

Un débogueur permet d’analyser et d’exécuter le code ligne par ligne pour repérer les erreurs. GDB est un exemple populaire de débogueur, compatible avec plusieurs langages.

  1. Environnement de test

Cet outil permet de tester le code automatiquement, souvent en utilisant un cadre (framework) de tests fourni avec la toolchain. En Rust, par exemple, Cargo permet de définir et d’exécuter des tests unitaires avec la commande cargo test.

Exemples de toolchains :

Prenons quelques exemples concrets pour comprendre la toolchain dans des langages spécifiques :

Rust : La toolchain de Rust inclut rustc (le compilateur), Cargo (gestionnaire de paquets et outils de build), et la bibliothèque standard std. Cargo facilite aussi l’exécution des tests et la gestion des dépendances.

Java : La toolchain Java inclut javac (le compilateur), la JVM (pour exécuter le bytecode), et le JDK (Java Development Kit) qui contient des outils de débogage et de documentation. Maven ou Gradle peuvent être utilisés comme gestionnaires de paquets.

Python : Python, étant interprété, n’a pas de compilateur dans le même sens que Rust ou C. La toolchain inclut l’interpréteur Python (python), pip pour gérer les paquets, et souvent unittest ou pytest pour les tests.

Pourquoi la toolchain est-elle importante ?

La toolchain est essentielle car elle fournit tous les outils pour passer du code source à un programme final. Une bonne toolchain rend le développement plus simple, plus rapide et moins sujet aux erreurs, car elle intègre des outils pour compiler, tester et déboguer le code de manière efficace.

Memoire

Stack

La stack est un espace de la mémoire du process qui stocke les variables créés par les fonctions d’un programme. La mémoire de chaque fonction est appelée la stack frame.

À chaque nouvel appel de fonction, un nouveau stack frame est alloué au dessus du dernier. La fonction qui a crée le stack frame est la seule à avoir accès à cet espace mémoire. C’est ce qui donne le scope de la fonction.

La taille que chaque variable de la stack doit être connue à la compilation. Si on veut stocker un tableau, nous devons connaître le nombre d’éléments qu’il contiendra.

Quand la fonction se termine, la mémoire de la stack frame est libérée automatiquement. Nous n’avons pas besoin de nous occuper de la libération de la mémoire.

Exemple :

s m t - - a - a i c b c n a S k t _ = = = a o c n 4 5 4 k l y F } F } U U N I C N I C N A C N T T L T T I E L I E O G O G N E s N E R t R m a s a a c t c i k a n = _ c = o k { 4 n _ 5 l o y n ( l a y ) ( I N T E G E R b ) {

Nota bene :

  • La stack frame de la fonction test a allouée un espace mémoire qui permet de stocker 2 variables alors que la fonction main uniquement un seul espace.

  • La stack a un espace limitée, sa taille est déterminée par l’architecture de la machine, de l’OS, du compilateur et d’autres facteurs. Si la stack est pleine, le programme s’arrêter avec une exception de stack overflow.

Heap & pointers

La heap est une région de la mémoire du processeur qui n’est pas géré automatiquement pour nous. Nous devons manuellement y allouée de la mémoire et la libérer par la suite quand nous n’en avons plus besoin. Si nous ne le faisons pas ou mal, le programme aura des fuites mémoire (memory leak).

La heap n’a pas de restriction de taille contrairement à la stack. Elle peut stocker de gros volume de données, elle est seulement limitée par la taille du disque physique de la machine.

Par ailleurs, la heap est accessible par n’importe quelle fonction et depuis n’importe quel endroit du programme.

Les allocations sur la heap sont couteuses et il vaut mieux les éviter quand c’est possible. Elle alloue de la mémoire de manière discontinue, ce qui fait que les données sont fragmentées. Et les accès aux données sont plus lents. De plus, cela prend du temps pour le programme de trouver un espace mémoire suffisant pour y stocker les données.

Exemple :

s s m t - - ( t - - a - a p a i c d e o c b c n a k i k S _ = = n _ = = = t a t o a n 6 0 e n 4 3 4 c d x u l k _ f r y h 6 ) e 7 a b p F } F } F } U U U N I C N I C N I P D C N A C N A C N O E T T L T T L T T I A I E L I E L I E N L O G O G O G T L N E s N E s N E E O R t R t R R C m a s a s A a a c t c c t d e T i k a k a E n = _ c = _ c = = o k a k e { 4 n _ 3 n _ 6 A l o d a L y n _ n L ( l h d O a y e _ C ) ( a h A I p e T N a E T p E I G { N E T R E G b E ) R 7 { H e a p 0 7 x f 6 7 b

Nota bene :

  • 0xf67b est l’addresse mémoire

  • suivre l’adresse mémoire signifie la déréferencer et recupère la valeur

  • si on ne libère pas la mémoire, la stack frame libère sa mémoire à la fin de la fonction et la heap continue de stocker sa valeur

Smart pointers

Dans les langages bas niveaux, les pointeurs peuvent menés à de nombreuses erreurs surtout si la codebase est vaste. Dans les langages de hauts niveaux, la mémoire inutilisée est libérée par le garbage collector mais cela signifie que le runtime sera lourd et rendra le programme un peu moins performant.

Rust et le C++ moderne utilisent les smart pointers. Un smart pointer est juste un wrapper autour d’un pointer standard à qui on a ajouté des fonctionnalités. Il y a plusieurs types de smart pointers mais le plus commun de tous, va s’assurer que la mémoire est libérée lorsqu’on sort du scope courant.

Exemple :

s s m t - - t - - a - a a i c d e c b c n a k k _ = = _ = = = a o n 6 S n 4 3 4 d M l S _ ( y t h 0 a e x c a f k p 6 7 b 7 ) F } F } F } U U U N I C N I C N I P C N A C N A C N O T T L T T L T T I I E L I E L I E N O G O G O G T N E s N E s N E E R t R t R R m a s a s a a c t c c t d e i k a k a n = _ c = _ c = = o k a k { 4 n _ 3 n _ 6 S l o d a M y n _ n A ( l h d R a y e _ T ) ( a h _ I p e P N a O T p I E N G { T E E R R ( b 7 ) ) { H e a p 0 7 x f 6 7 b

Nota bene :

  • Lorsque que la fonction stack_and_heap se termine, l’espace mémoire allouée sur la heap sera libérée également.