Voir les cours et résoudre les problèmes en :
Le C est un langage de programmation impératif conçu pour la programmation système. Inventé au début des années 1970 avec UNIX, C est devenu un des langages les plus utilisés. De nombreux langages plus modernes se sont inspirés de sa syntaxe. Il privilégie la performance sur la simplicité de la syntaxe. [En savoir plus]
Le C++ est un langage de programmation impératif. Inventé au début des années 1980, il apporte de nouveaux concepts au langage C (les objets, la généricité), le modernise et lui ajoute de nombreuses bibliothèques. C++ est devenu l'un des langages les plus utilisés. Sa performance et sa richesse en font le langage de prédilection pour les concours. [En savoir plus]
Pascal est un langage de programmation impératif inventé dans les années 1970 dans un but d'enseignement. Quoiqu'encore utilisé à cette fin, l'absence de bibliothèque standard en limite son utilisation malgré une grande efficacité. Sa syntaxe a été reprise par d'autres langages plus modernes avec plus ou moins de succès. [En savoir plus]


Remarque : Les cours pour ce langage ne sont disponibles que jusqu'au chapitre 4, « Lecture de l'entrée ». Les corrections sont toutefois toujours fournies.
OCaml est un langage de programmation fonctionnel inventé au milieu des années 1990. Il permet aussi une programmation impérative ou objet. Il permet d'écrire des programmes courts et faciles à vérifier et est ainsi utilisé pour certains systèmes embarqués très sensibles comme ceux des avions. Il est utilisé dans l'enseignement en classes préparatoires aux grandes écoles. [En savoir plus]


Remarque : Les cours pour ce langage ne sont disponibles que jusqu'au chapitre 4, « Lecture de l'entrée ». Les corrections sont toutefois toujours fournies.
Java est un langage de programmation impératif et orienté objet. Inventé au début des années 1990, il reprend en grande partie la syntaxe du langage C++ tout en la simplifiant, au prix d'une performance un peu moins bonne. S'exécutant dans une machine virtuelle, il assure une grande portabilité et ses très nombreuses bibliothèques en font un langage très utilisé. On lui reproche toutefois la « verbosité » de son code. [En savoir plus]


Remarque : Pour un débutant souhaitant apprendre Java, nous conseillons fortement de commencer par JavaScool, plus facile à apprendre, bien que fortement similaire.
Java's Cool (alias JavaScool) est conçu spécifiquement pour l'apprentissage des bases de la programmation. Il reprend en grande partie la syntaxe de Java sur laquelle il s'appuie, mais la simplifie pour un apprentissage plus aisé. La plateforme JavaScool est accompagnée d'un ensemble d'activités diverses de découverte de la programmation. [En savoir plus]
Python est un langage de programmation impératif inventé à la fin des années 1980. Il permet une programmation orientée objet et admet une syntaxe concise et claire qui en font un langage très bien adapté aux débutants. Étant un langage interprété, il n'est cependant pas aussi performant que d'autres langages. [En savoir plus]

Jusqu'à présent, vous avez utilisé les commandes "runC" et "runT" afin d'exécuter et tester vos programmes. Comme nous vous l'avions indiqué, ces commandes n'existent pas normalement, nous les avions ajoutées afin de simplifier votre découverte de la ligne de commande sous Linux. Dans ce cours, nous allons vous apprendre les "véritables" commandes qui permettent de compiler/exécuter/tester un programme.

Rendez vous dans le sous-dossier "tasks/hello_world"

Exécuter un programme

Pour exécuter le programme, lancez la commande suivante :

$ python3 prog.py
Hello world!

Redirections de fichiers

Rendez vous dans le sous-dossier "tasks/double" et compilez le code source.

Un programme lit ses données sur ce qu'on appelle l'entrée standard et écrit sur la sortie standard. Il est possible de prendre un fichier (par exemple un fichier test) et l'envoyer sur l'entrée standard de votre programme :

$ python3 prog.py < test.01.in
10

Cette commande a pris les données contenues dans le fichier "test.01.in" et les a envoyées au programme pour que celui-ci puisse les lire.

La sortie du programme s'affiche dans le terminal. Si la sortie est longue, ou que l'on souhaite la regarder plus tard, il est plus pratique de rediriger la sortie standard du programme vers un fichier, plutôt que directement vers le terminal :

$ python3 prog.py < test.01.in > out
$ cat out
10

Le fichier "out" contient donc la sortie du programme et rien ne s'affiche dans le terminal.

Comparer deux fichiers

Lorsque la sortie d'un programme est très longue, ou que l'on souhaite automatiser les choses, il devient difficile de comparer manuellement cette sortie à la sortie qu'on souhaitait avoir (le fichier ".out"). Il existe une commande permettant de comparer deux fichiers :

$ diff out test.01.out

Ici rien ne s'affiche car les deux fichiers sont identiques. Modifiez le fichier "test.01.out" et relancez la commande précédente pour voir ce qui se passe.

Il existe plusieurs options utiles pour "diff" :

  • Si vous voulez juste savoir si les fichiers sont égaux ou non et ne pas visualiser tous les détails, utilisez l'option "-q" (q comme quiet) :
    $ diff -q out test.01.out
    
  • Si vous souhaitez ignorer les différences qui sont simplement des caractères d'espacement comme ' ', '\t', '\n', utilisez l'option "-w" (w comme white-space) :
    $ diff -w out test.01.out
    

Combiner les commandes

Il est donc nécessaire d'exécuter trois commandes

$ python3 prog.py < test.01.in > out
$ diff out test.01.out

Comme cela est un peu long, nous allons les combiner, de telle sorte qu'une commande s'exécute uniquement si la précédente c'est bien passée (pas d'erreur signalée) :

$ python3 prog.py < test.01.in > out && diff out test.01.out

On peut également éviter la création du fichier intermédiaire "out" et faire directement la comparaison entre la sortie du programme et la sortie de référence :

$ diff <(python3 prog.py < test.01.in) test.01.out

Attention à ne pas mettre d'espace entre le < et le ( sinon cela ne fonctionnera pas. Ce <( ) exécute la commande située au milieu et envoie le résultat dans un fichier temporaire qu'il "donne" ensuite à la commande diff. Ainsi, au lieu de faire nous-même un fichier temporaire "out", on laisse l'ordinateur s'en charger !

Voici une variante possible :

$ python3 prog.py < test.01.in | diff - test.01.out

Le "-" comme premier argument du "diff" indique qu'il faut comparer son entrée standard (et non pas un fichier) à son second argument. Le pipe ("|") permet justement d'envoyer la sortie du programme vers l'entrée standard du "diff".

A voir de choisir la variante que vous préférez.

Combiner plusieurs tests

La commande précédente permet de vérifier le résultat d'un programme sur un fichier test, comment faire si on a plusieurs fichiers tests ? On va faire une boucle !

Tout d'abord une boucle pour afficher le nom de tous les fichiers ".in" du répertoire :

$ for f in *.in; do echo "$f"; done
test.01.in
test.02.in
test.03.in

On fait une boucle sur la variable "f" qui vaudra, tout-à-tour, le nom de chacun des fichiers ".in". On demande ensuite à afficher le contenu de cette variable à l'aide de la commande "echo" et en mettant un "$" devant le nom de la variable.

Si on combine cela avec la commande de compilation/test :

$ for f in *.in; do echo "$f" ; diff <(python3 prog.py < "$f") ${f/.in/.out}; done

Le ";" permet de séparer des commandes indépendantes qui s'exécutent même si la précédente à renvoyée une erreur. Quand au ${f/.in/.out} il nous permet de prendre le contenu de la variable "f", de remplacer le ".in" par un ".out" et de renvoyer le résultat.

Mesurer le temps d'exécution

Pour mesurer le temps mis par un programme pour s'exécuter, on va utiliser la commande time :

$ time ./prog < test.01.in
real  0m0.002s
user  0m0.000s
sys   0m0.000s

Notez que les temps peuvent être différents sur votre ordinateur.

  • Le temps real correspond au temps qui s'est effectivement écoulé, dans le monde réel, entre le début et la fin de l'exécution de votre programme. Il peut dépendre de nombreux facteurs, comme par exemple les autres programmes exécutés au même moment par la machine.
  • Le temps user correspond au temps que le processeur a passé à exécuter les instructions de votre programme, sans compter les appels systèmes.
  • Le temps sys correspond au temps que le processeur a passé à exécuter les appels systèmes, c'est-à-dire une partie du temps utilisé par des fonctions systèmes pour lire/écrire des données.

Le temps qui vous intéresse est le total du temps user et du temps sys.

N'oubliez pas que votre machine n'est pas nécessairement du même type que celle sur laquelle votre programme sera évalué, et donc le temps d'exécution du programme sur votre machine ne sera pas le même que le temps d'exécution sur le serveur. Nous vous invitons à faire quelques tests pour établir une correspondance.

Limiter la mémoire utilisée

On appelle "shell" le programme qui, dans le terminal, interprète les différentes commandes que vous tapez. Dès que vous ouvrez le terminal, le shell est déjà lancé et prêt à interpréter vos commandes !

Pour vérifier que votre programme n'utilise pas trop de mémoire, sous Linux, vous pouvez utiliser la commande bash ulimit, qui permet de fixer une limite pour certaines ressources. Ces limites seront appliquées à tout programme lancé à partir de ce shell.

Attention : une fois que vous avez fixé une limite, vous ne pouvez pas la réaugmenter sans quitter le shell. Lancez donc un nouveau shell temporairement (commande bash) pour faire votre test. Pour quitter ce nouveau shell, utilisez le raccourci clavier Ctrl+D (touche "Ctrl" et touche "D"). Une fois dans ce nouveau shell vous pouvez utiliser les commandes suivantes.

Pour fixer une limite globale de 16Mo à l'utilisation de la mémoire :

$ ulimit -v 16000

Vous pouvez remplacer la valeur 16000 par la valeur de votre choix, à exprimer en kilo-octets.

Si vous exécutez ensuite votre programme dans ce shell, une erreur sera affichée si la mémoire est insuffisante. Il peut s'agir d'un simple message "Killed", ou d'une erreur d'allocation, de segmentation....

Dans certains cas, on peut vous indiquer une limite séparée pour la taille de la pile. Vous pouvez la fixer avec l'option -s :

$ ulimit -s 10000

Encore une fois, la limite est à exprimer en kilo-octets.

Pour combiner les deux commandes :

$ ulimit -v 16000 -s 10000

A tout moment, vous pouvez afficher les limites actuelles du shell en appelant ulimit -v ou ulimit -s sans indiquer la taille. Pour plus de détails, utilisez la commande ulimit -a qui permet d'afficher toutes les limites qu'il est possible de fixer.

Conclusion

Nous vous avons donné les outils de base pour pouvoir compiler/exécuter/tester vos programmes sous Linux. Il n'est pas nécessaire d'en connaître plus pour résoudre tous les exercices que vous rencontrerez. Cela peut cependant vous aider, à vous de découvrir Linux par vous-même.

Pour les curieux n'hésitez pas à aller voir, dans quelques temps, comment fonctionnent les exécutables runC et runT :

$ less ~/bin/runT
$ less ~/bin/runC

Ils utilisent des choses non expliquées dans ces petits cours aussi il est normal que vous ne compreniez pas tout. Vous n'avez absolument pas besoin de savoir faire cela, nous vous les indiquons uniquement à titre indicatif.

Pensez à vous inscrire pour valider les cours et résoudre les exercices.