IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Notes sur le langage C

Notes sur le langage C


précédentsommaire

XLIV. Borland C : "floating point formats not linked"

Un programme généré avec l'IDE Borland C++ 3.1 signale parfois ce message à l'exécution :

 
Sélectionnez
scanf : floating point formats not linked
Abnormal program termination

Il s'agit en fait d'un bug connu de certains compilateurs Borland. Il se produit lorsqu'on utilise un format 'flottant' avec *printf() ou *scanf(), et qu'on n'utilise pas de fonction de la bibliothèque mathématique.

La parade est simple. Il suffit d'ajouter ces quelques lignes dans le code source contenant le main(), par exemple.

 
Sélectionnez
#ifdef __BORLANDC__
/* The pesky "floating point formats not linked" killer hack : */
extern unsigned _floatconvert;
#pragma extref _floatconvert
#endif

XLV. Code standard ? Code portable ? Je suis perdu !

On entend parler de code standard, de code portable ? Qu'est-ce que ça signifie ? Cela sert à quoi ?

XLV-A. "standard" ?

Le terme 'standard' est erroné. C'est un anglicisme de 'standard' qui signifie 'norme' (subst.) ou 'normalisé' (adj.).

Le langage C est normalisé. Cela signifie en clair qu'il est défini par un document spécifié et publié sous la responsabilité de l'ISO (International Standard Organisation ou Organisme de normalisation international). Ce document décrit la syntaxe et la sémantique du langage ainsi que l'interface et le comportement des fonctions de la bibliothèque d'exécution (RTL ou Run-Time Library).

Cette spécification s'applique aux compilateurs réputés 'conformes à la norme' et par conséquent aux programmeurs qui les utilise. La spécification définit en gros trois domaines :

  • ce qui est défini par la norme ;
  • ce qui est défini par la cible[1] ;
  • ce qui n'est pas défini.

Ce qui n'est pas défini par la norme peut l'être par une implémentation du C qui comporte des extensions spécifiques à une cible ou à un système. (Mots clés, fonctions, bibliothèques)

Par exemple system() est une fonction normalisée, dont le paramètre est une chaîne de caractères. Cependant, la sémantique du texte porté par cette chaîne de caractères peut varier d'un système à l'autre, voire ne pas être reconnue du tout par le système.

[1] (on dit aussi implémentation (anglicisme), implantation ou plate-forme)

XLV-B. "portable" ?

C'est la capacité qu'a un code source à produire un comportement identique sur différentes plate-formes. On distingue la portabilité absolue (pour n'importe quelle plate-forme) de la portabilité relative (limitée à un certain nombre de plate-formes bien définies).

XLV-B-1. portabilité absolue

C'est lorsqu'un code source ne contient que des éléments normalisés du langage dont la définition et l'utilisation ne dépendent pas de la plate-forme. Certaines pratiques additionnelles peuvent cependant rendre portable du code standard, comme ajouter fflush(stdout) après un printf() qui ne se termine pas par un '\n'.

XLV-B-2. portabilité relative

C'est un code portable 'absolu' auquel s'ajoutent des extensions (généralement, des bibliothèques) tierces conçues pour fonctionner sur un certain nombre de plate-formes bien définies.

La norme POSIX.1 en définit un certain nombre, notamment en matière de gestion des répertoires, processus légers (threads) et réseau (sockets). Mais il existe des initiatives indépendantes comme GTK+ qui définit une interface de programmation graphique pour utilisateur (GUI) commune à Windows, X (Unix/Linux), Apple/Mac et même BeOS.

XLV-C. Bon usage

Le fait de bien connaître les domaines couverts par le langage C constitue une aide considérable pour l'écriture du code. En effet, la portabilité n'est possible que si le code est portable et donc, dans sa grande majorité normalisé, ou tout au moins conforme aux définitions de telle ou telle bibliothèque d'abstraction.

Il est donc impératif de séparer le code normalisé (qui doit représenter la majorité de celui-ci) du code spécifique à telle ou telle plate-forme, et qui n'aurait pas trouvé sa place dans les bibliothèques d'abstraction.

XLVI. Enregistrer une structure

L'enregistrement d'une structure dans un fichier est une opération plus complexe qu'il n'y parait. En effet, le code naif suivant :

 
Sélectionnez
#include <stdio.h>
 
struct data
{
   char nom[32];
   int age;
};
 
int main (void)
{
#define FNAME "data.txt"
 
   struct data data = { "Emmanuel", 50 };
 
   FILE *fp = fopen (FNAME, "wb");
   if (fp != NULL)
   {
      fwrite (&data, sizeof data, 1, fp);
 
      fclose (fp), fp = NULL;
   }
   else
   {
      perror (FNAME);
   }
   return 0;
}

est certes simple et efficace, mais est malheureusement non portable, et ce pour plusieurs raisons :

  • la représentation interne des données en C peut changer d'une implémentation à l'autre, en terme de largeur (nombre de bits), de 'boutisme' (position du byte de poids fort) et de codage (entiers négatifs, nombres réel, jeu de caractères) ;
  • l'alignement requis. En effet, certaines architectures imposent que les éléments de la structure soient alignés sur une adresse multiple de 2, 4 ou autre, ce qui rend impossible de prévoir de façon portable, la signification des bytes dans le fichier.

Pour résoudre ce problème, il y a deux grandes familles de solutions :

  • le format binaire ;
  • le format texte.

XLVI-A. Le format binaire

C'est le plus portable, mais aussi le plus complexe. Il consiste à définir un format de données indépendant de toute implémentation. Il nécessite une conversion à l'écriture (host->file) et une conversion à la lecture (file->host), et ce dans le strict respect du format 'fichier' spécifié. Une méthode simple est TLV (Type, Longueur, Valeur ou Type, Length, Value).

(à venir : exemple de spécification TLV)

Il existe des solutions normalisées comme BER (Basic Encoding Rules) spécifié par les recommandations ITU-T X.209 et X.690 ou XDR (eXternal Data Representation) spécifié par la RFC 1832 plus ou moins basées sur TLV. Ces solutions sont complexes et sont plutôt utilisées avec l'aide d'une bibliothèque tierce comme BER sous Linux.

Dans tous les cas, le fichier est traité en mode binaire ("wb", "rb", "ab"), et les fonctions les plus utilisées sont fgetc(), fputc(), fread() et fwrite().

XLVI-B. Le format texte

Il est un peu moins portable, mais moins complexe. Il consiste à définir un format de données indépendant sous forme de texte. Il nécessite une conversion à l'écriture (host->file) et une conversion à la lecture (file->host), et ce dans le strict respect du format 'fichier' spécifié. Une méthode simple est CSV (Comma Separated Values).

On peut trouver les spécifications sur Wotsit , le site de référence des formats de fichiers.

Le fichier est traité le plus souvent en mode texte ("w", "r", "a"), mais aussi parfois en mode binaire pour régler les problèmes de fins de ligne hétérogènes. (voir plus loin). Les fonctions les plus utilisées sont fgetc(), fputc(), fgets(), fputs() et fprintf().

Les principaux problèmes de portabilité proviennent :

  • du jeu de caractères utilisé. En dehors d'EBCDIC utilisé sur les gros systèmes IBM (Mainframes), c'est généralement ASCII (0-127), mais ça peut ne pas suffire à encoder tous les caractères, notamment les caractères accentués et autres signes mathématiques ou pseudo-graphiques. Or il existe plusieurs façons de coder ces caractères (ASCII étendu, IBM-PC8, UTF-8, Unicode, etc.). Là encore, une définition indépendante peut aider… ;
  • de la façon de coder les fins de ligne (CR, LF, CRLF, etc.).

Certaines corrections doivent donc parfois être effectuées à l'aide de tables de codages et autres astuces algorithmiques.


précédentsommaire

Copyright © 2009 Emmanuel Delahaye. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.