Comment écrire « portable »


Ada, c’est bien connu, est un langage portable. Ceci ne signifie pas pour autant que tout programme Ada se comporte exactement de la même façon sur toute machine ; heureusement même, car cela signifierait qu’il ne serait pas possible de profiter des particularités d’une machine donnée. Plus modestement, cela signifie qu’il est possible d’écrire des programmes portables, moyennant quelques précautions que nous allons détailler maintenant.

Quelle portabilité ?

Notons tout d’abord qu’il existe plusieurs formes de portabilités : en particulier on peut souhaiter une portabilité absolue (même résultat sur tous les systèmes), ou une portabilité relative (ajustement automatique du programme pour utiliser au mieux les possibilités de la machine). La première chose à faire pour écrire « portable » est donc de définir quelle forme de portabilité est souhaitée…

Notons que l’on peut considérer qu’unprogramme est portable si l’effort de portage est négligeable devant l’effort de développement initial ; en pratique ce coût est rarement nul. Si l’on doit dépendre par endroits des spécificités de l’implémentation, on veillera à accéder aux fonctionnalités concernées via des paquetages spécialisés dont il suffira d’ajuster le corps en cas de portage.

Utilisation des types prédéfinis

Les types prédéfinis sont ceux définis dans le paquetage Standard. Si Boolean ne crée pas de problème, les types numériques (Integer, Float, Duration) ont des intervalles de valeurs définis par l’implémentation. La première règle est donc de ne pas utiliser les types prédéfinis et de toujours définir ses propres types de données, à partir des spécifications du problème bien entendu.

Comme toute règle, celle-ci a ses exceptions : par exemple, Integer doit être utilisé pour indexer les String (c’est à peu près sa seule utilisation systématique). Noter que rigoureusement, il peut être non portable d’utiliser des chaînes de plus de 32K de long (la norme exige qu’Integer soit sur au moins 16 bits); en pratique le risque est limité, pratiquement tous les compilateurs ayant Integer sur (au moins) 32 bits aujourd’hui.

Le cas de Float est un peu différent : on peut l’utiliser à condition d’accepter que la précision des calculs soit la précision « naturelle » de la machine. On obtiendra donc des résultats de précision variable selon l’implémentation, mais ceci peut être acceptable dans certaines applications. Le tout est de définir quelle sorte de portabilité on souhaite !

Utilisation du paquetage System et de ses enfants

Le paquetage System est l’outil de base de la portabilité relative : il définit en effet les caractéristiques de la machine sur laquelle on tourne. Par exemple, si l’on souhaite simplement disposer du plus grand type entier disponible sur la machine, il suffit de déclarer :

type Big is range Min_Int .. Max_Int;

Les enfants de System sont des paquetages de services de bas niveau pouvant poser des problèmes de portabilité : Address_To_Access_Conversion, Storage_Elements, Storage_Pools… et bien entendu Machine_Code. Leur utilisation doit donc être encapsulée dans des paquetages d’accès.

Le problème de l’ordre d’élaboration

L’ordre d’élaboration des unités de compilation est (partiellement) déterminé par le compilateur. Si une unité utilise lors de son élaboration des services fournis par une autre unité, le corps de cette dernière doit avoir été élaboré avant, sous peine de Constraint_Error (cf. "L'ordre d'élaboration"). Du coup, un changement de compilateur fait souvent apparaître une dépendance abusive à l’ordre, révélant que le programme marchait par hasard. Une utilisation soigneuse du pragma Elaborate_All est indispensable. Noter que Gnat dispose d’une option (option –p de gnatbind) qui génère le plus mauvais ordre d’élaboration possible. Si le programme démarre correctement avec cette option, alors il est probable qu’aucun problème d’élaboration n’existe !

Petits et gros boutistes

Mis à part le cas des accès directs à la mémoire (dont la portabilité est intrinsèquement faible), l’ordre des octets dans un mot n’a pas de raison de créer de problèmes de portabilité en Ada. En revanche, le sens de numérotation des bits (à savoir, si le bit de poids fort porte le numéro 0 ou 31) peut créer des difficultés au niveau des clauses de représentation. C’est pourquoi Ada95 a rajouté l’attribut Bit_Order qui permet de savoir, et même de forcer, le sens de numérotation des bits. Attention, cet attribut n’a d’effet que sur l’interprétation des numéros de bits dans les clauses de représentation, il ne change en rien la représentation mémoire.


Copyright © 2000 Adalog / J-P. Rosen