Obligement - L'Amiga au maximum

Vendredi 06 juin 2025 - 12:28  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Dossier : Le C ANSI
(Article écrit par Denis Jarril et extrait d'Amiga News Tech - janvier 1992)


Voici un article sur les spécifications de la norme ANSI pour le langage C, avec comme plat de résistance les fonctions de la bibliothèque standard.

ANSI est l'abréviation de American National Standards Institute, ce qui signifie en français et à peu de choses près, Institut National Américain de Standardisation (INAS). Mais bon, ANSI, c'est tout de même plus simple. Le but de ce vénérable établissement est tout simplement et comme son nom l'indique, de fournir au bon peuple américain des standards pour tout ce qu'il est possible de standardiser. Et Dieu sait que rien qu'en informatique, ces gens-là ont bien du boulot...

On ne reviendra pas particulièrement sur le rôle et l'utilité d'un standard, qu'il s'agisse de machines à laver ou de contrôleurs de disques optiques réinscriptibles à triple faisceau laser orthogonalement entrelacé. Les travaux informatiques de l'ANSI sont mondialement connus, ne serait-ce que concernant les codes de contrôles des consoles d'ordinateurs, codes auxquels l'Amiga a d'ailleurs totalement adhéré. Ainsi, un fichier texte ASCII contenant quelques codes ANSI de mise en valeur (gras, italique, etc.) sera visualisé exactement de la même manière sur un Amiga que sur un PC équipé du pilote ANSI.SYS. Sauf que ce sera plus joli sur l'Amiga, car bon, quand même, hein, faut pas déconner.

L'un des travaux les plus importants de l'ANSI a surtout été de fournir une standardisation pour presque tout langage informatique évolué, de façon à le rendre indépendant de la machine. C'est ainsi qu'il existe une norme ANSI pour le BASIC, pour le Pascal, le Lisp... Et bien entendu le C, qui nous intéresse plus particulièrement aujourd'hui.

K&R vs ANSI

Les créateurs du langage C, messieurs Kernighan et Ritchie, avaient déjà entrepris un effort de standardisation de leur bébé, que l'on a tout naturellement baptisé "norme K&R" et qu'ils ont exposé dans leur livre "Le Langage C". Seulement voilà, deux hommes seuls, et malgré toute la bonne volonté dont ils ont pu faire preuve, ne sauraient penser à tout. Aussi les ANSI-mens se sont-ils penchés sur le problème et ont étendu cette première norme, tout en essayant d'en rester le plus près possible (le problème étant bien entendu de rester compatible). Résultat, la norme ANSI, par ailleurs elle aussi décrite dans un excellent livre "Le Langage C, 2ème édition" signé... Kernighan et Ritchie.

Par la suite, nous ne parlerons plus de la norme K&R, qui n'est finalement plus qu'un sous-ensemble de la norme ANSI. Pour la petite histoire, on peut signaler que la norme K&R date de 1978, cependant que la norme ANSI date, elle, de 1983, et qu'elle fut définitivement acceptée fin 1988.

Principes

Aujourd'hui, on trouve de çà et de là divers compilateurs C, dont beaucoup se réclament compatibles ANSI. L'utilisation d'un tel compilateur ANSI sur une machine donnée, garantira que le code source sera directement accepté par un autre compilateur ANSI sur une autre machine, disposant d'un autre système d'exploitation.

Cela suppose donc qu'un certain nombre de fonctions, notamment d'entrées-sorties, soit effectivement disponibles dans les deux environnements. Comble de bonheur, ces fonctions sont toutes regroupées dans une bibliothèque (en anglais, library) qui se doit d'être standard. Peu importe donc pour le programmeur C la manière dont ces fonctions ont été effectivement implémentées sur telle ou telle machine, il est sûr de les retrouver quel que soit l'environnement et surtout, il est sûr que le nombre et la signification des paramètres de chacune sont identiques. C'est ça, la standardisation.

Limites

Pour être indépendante de la machine, la norme doit faire quelques sacrifices. Premier exemple, très parlant : l'ouverture d'une fenêtre sur Amiga s'effectue par un appel de la fonction OpenWindow() de l'intuition.library. Bien. MS-DOS ne disposant pas de système de fenêtrage, un programme Amiga utilisant OpenWindow() ne sera pas directement portable en MS-DOS. Quant aux Macintosh, Archimedes, Atari ST et autre NeXT, même s'ils disposent de fenêtres graphiques, ils n'ont pas d'intuition.library, donc pas d'OpenWindow(), donc notre programme ne sera pas non plus portable sur ces machines. Ce qui explique sans aucun doute possible que la fonction OpenWindow() ne fasse pas partie de la norme ANSI.

Autre exemple, moins évident : supposons que l'on désire ouvrir un fichier à l'aide de la fonction ANSI appropriée. Assumons pour les besoins de l'exemple que cette fois-ci, la portabilité est totale, et que le source soit compilé sans aucune erreur particulière quelque soit le système. Des problèmes risquent encore de surgir à l'exécution du programme : AmigaDOS autorise des noms de fichiers d'une longueur de 30 caractères maximum, et le point décimal n'est pas considéré comme un séparateur particulier. Cette règle est également vraie sur Mac, mais pas en MS-DOS, où seuls huit caractères maximum sont autorisés, plus le point, plus trois autres caractères pour l'extension.

Ce second exemple démontre clairement que toute la standardisation du monde ne saurait suffire à assurer une portabilité parfaite. Le programmeur doit également faire attention à ne pas utiliser, directement ou indirectement, telle ou telle particularité d'un système donné, sous peine de problèmes sérieux. En cas de conflit, comme dans l'exemple précédent, il devra se conformer aux exigences du système le plus contraignant (ici, MS-DOS) ou modifier explicitement chacune des versions de son oeuvre en conséquence - mais alors, à quoi donc sert une norme ?

Prototypes

Encore une petite digression avant de parler des fonctions elles-mêmes. L'un des principaux apports de la norme ANSI pour le langage C est la possibilité de déclarer des prototypes pour les fonctions que le programme emploie.

Le raisonnement est simple : avant, la norme K&R indiquait qu'une déclaration de fonction ne spécifiait que le type de la fonction, c'est-à-dire et plus exactement, le type du résultat que cette fonction renvoyait. Par exemple :

int Puissance(); /* Déclaration de fonction, façon K&R */

Le nombre et le type des paramètres qu'elle exigeait n'était connu du compilateur qu'au moment-même de la compilation de la fonction. De plus, une fonction non-déclarée avant son utilisation était automatiquement considérée comme renvoyant un entier (int).

Depuis la norme ANSI, une déclaration de fonction peut également indiquer le nombre et le type de ses paramètres. Ainsi, le compilateur peut vérifier dès l'appel de la fonction que le programmeur n'a pas commis d'erreur. Pour reprendre l'exemple ci-dessus, la déclaration de notre fonction Puissance() devient :

int Puissance(int, int); /* La même, façon ANSI */

Il est même possible de donner également les noms des paramètres. Cela n'ajoute rien aux vérifications que le compilateur peut entreprendre, mais peut rendre le listing plus clair et plus facilement compréhensible par une tierce personne :

int Puissance(int base, int n); /* encore mieux ! */

L'ancienne forme est toujours valable, et tout compilateur se doit de l'accepter. Cela dit, les avantages de la seconde forme sont évidents, et je vous encourage plus que vivement à l'utiliser chaque fois que vous le pourrez.

On pourrait continuer à disserter pendant des heures sur toutes les modifications, bonnes et mauvaises, que la norme ANSI a apporté à la norme K&R, mais ce n'est pas vraiment là le but de cet article...

La bibliothèque

Découvrons à présent quelles fonctions l'on trouve dans la bibliothèque standard ANSI, ainsi que leur utilité.

Note : pour le compilateur SAS/Lattice C 5.10, cette bibliothèque porte le nom évocateur de lc.lib, ou lcs.lib pour le modèle "small" (entiers 16 bits), ou lcr.lib pour la version réentrante, ou encore lcm.lib pour la version avec nombres en virgule flottante... Évidemment, seules les fonctions effectivement utilisées seront placées par l'éditeur de liens dans le programme exécutable final. En Manx/Aztec C, je ne sais pas, vérifiez avec votre documentation.

La bibliothèque ne fait pas partie du langage C proprement dit. Cependant, tout compilateur C ANSI fournira les déclarations de fonctions, de macros et de types de cette bibliothèque. Elle se base sur plusieurs fichiers d'en-tête (header files), eux aussi standards :
  • <assert.h>
  • <float.h>
  • <math.h>
  • <stdarg.h>
  • <stdlib.h>
  • <ctype.h>
  • <limits.h>
  • <setjmp.h>
  • <stddef.h>
  • <string.h>
  • <errno.h>
  • <signal.h>
  • <stdio.h>
  • <time.h>
Un ou plusieurs de ces fichiers seront bien sûr inclus au source principal par la directive #include du préprocesseur. Ils peuvent être inclus plusieurs fois et dans n'importe quel ordre sans que cela ne provoque d'erreur, mais quoiqu'il arrive, et c'est chose normale en C, l'inclusion doit précéder toute utilisation des éléments qu'ils contiennent.

Leur rôle est donc de définir les prototypes des fonctions de la bibliothèque (printf() et consoeurs), ainsi que de fournir au programmeur des constantes (la plus célèbre étant NULL), des macros (max() par exemple) et des types (size_t par autre exemple), utiles pour son travail de développement. Ils contiennent également des références aux variables globales de la bibliothèque, telle que errno pour ne citer qu'elle, qui contient le code d'erreur éventuel de la dernière fonction appelée. Encore une fois et au risque de me répéter, toutes ces fonctions, constantes et macros sont (censées être) disponibles sur tous les systèmes de France, de Navarre et des environs.

La multiplicité des fichiers d'en-tête s'explique tout simplement par une envie de regrouper par thèmes toutes les déclarations. Ansi, <stdio.h> déclare les fonctions d'entrées-sorties, <string.h>, celles destinées à manipuler les chaînes de caractères, et <time.h> celles de gestion de l'heure système. C'est simple, pratique et économique en temps de compilation : si on n'a pas besoin de nombres flottants dans un programme, on n'inclut pas <math.h> et c'est toujours ça de gagné.

Petite précision

Tous les objets que nous allons voir dans ce qui suit, travaillent avec certains types de données, notamment les "int" (entiers) ou les "float" (flottants). D'autres travaillent avec des structures, composées d'objets de plusieurs types.

Le programmeur n'a normalement pas à connaître la taille de ces objets. En d'autres termes, il n'a pas à se soucier de savoir si, sur telle ou telle machine, dans tel ou tel environnement, un int est codé sur deux, quatre ou même huit octets.

Si une telle connaissance est souvent primordiale pour accéder aux ressources d'un système donné (par exemple pour l'Amiga, les structures définies par Intuition ou la graphics.library), il suffit de savoir que, dans le cas de la bibliothèque standard, telle ou telle fonction renvoie un int, un char, ou un pointeur quelconque. Si l'on a absolument besoin de connaître la taille d'un int (par exemple, pour une allocation mémoire), l'opérateur sizeof() doit être utilisé.

Enfin, certains types définis particuliers seront rencontrés au cours de notre exploration de la bibliothèque. Nous les détaillerons à ce moment là.

Les entrées-sorties : stdio.h

Nous attaquons par le plus gros morceau... Les fonctions, macros et types d'entrées-sorties (Input-Output, ou plus simplement IO), représentent presque un tiers de la bibliothèque.

On y trouve notamment la définition des flux : il ne s'agit ni plus ni moins que d'un flot de données associées à un périphérique quelconque (disque bien sûr, mais également clavier, écran, imprimante...). Les flux peuvent être gérés soit en mode texte, soit en mode binaire. Dans le premier mode, les données sont organisées en lignes, conclues par le caractère de fin de ligne, "\n". Ce caractère peut être, de manière interne, représenté soit par CR (ASCII 13), soit par LF (ASCII 10), soit par une combinaison des deux. C'est le système qui gère cela. Un flux binaire est constitué d'une suite d'octets non traités par le système, même s'il devait contenir un ou plusieurs caractères de fin de ligne. C'est le programmeur qui décide d'ouvrir un flux en mode texte ou au contraire en mode binaire.

Au début de l'exécution du programme, le code de démarrage ouvre toujours les trois flux standard stdin (entrée), stdout (sortie) et stderr (sortie d'erreur, peut être différent de stdout).

Les fichiers

L'ouverture d'un fichier renvoie un objet de type FILE, contenant toutes les informations nécessaires au contrôle du flux. Le type FILE est défini ainsi :

ANSI C
ANSI C

Les fonctions suivantes concernent le traitement des fichiers.

FILE *fopen(const char *filename, const char *mode)

"fopen()" tente d'ouvrir le fichier décrit par "filename", dans le mode défini par "mode", et retourne un flux en cas de succès, et NULL sinon. "mode" peut être :
  • r : fichier texte en lecture.
  • w : fichier texte en écriture. Si le fichier existait déjà, il est écrasé.
  • a : fichier texte en écriture. Si le fichier existait déjà, les nouvelles données lui seront ajoutées.
  • r+ : fichier texte en lecture/écriture.
  • w+ : fichier texte en lecture/écriture. Si le fichier existait déjà, il est écrasé.
  • a+ : fichier texte en lecture/écriture. Si le fichier existait déjà, les nouvelles données lui seront ajoutées.
Si l'on ajoute un "b" à l'un quelconque de ces modes (par exemple, "rb", ou "w+b"), le flux est ouvert en mode binaire au lieu du mode texte. La longueur des noms de fichiers est limitée à FILENAME_MAX caractères et FOPEN_MAX fichiers peuvent être ouverts simultanément.

Note : FILENAME_MAX n'est pas défini en Lattice/SAS C 5.10, et FOPEN MAX est défini sous le nom de _NFILES.

FILE *freopen(const char *filename, const char *mode, FILE *stream)

"freopen()" ouvre le fichier décrit par "filename" dans le mode défini par "mode", et lui associe le flux "stream". On utilise en général freopen() pour changer les fichiers associés à stdin, stdout et stderr.

int fflush(FILE *stream)

"fflush()" provoque l'écriture anticipée des données encore présentes dans la mémoire tampon du flux "stream", qui doit avoir été ouvert en écriture. Son effet est indéfini pour un flux ouvert en lecture. Elle retourne EOF pour une erreur d'écriture, 0 si tout va bien.

int fclose(FILE *stream)

"fclose()" force l'écriture des données encore présentes dans la mémoire tampon du flux "stream", libère tout tampon mémoire alloué automatiquement et ferme le flux, qui devient inutilisable. Elle retourne EOF en cas d'erreur, 0 sinon.

int remove(cont char *filename)

"remove()" détruit le fichier décrit par "filename". Elle retourne une valeur différente de 0 en cas d'échec (fichier absent, erreur disque...).

int rename(const char *oldname, const char *newname)

"rename()" change le nom du fichier "oldname" en "newname". Elle retourne une valeur différente de 0 en cas d'échec.

FILE *tmpfile(void)

"tmpfile()" crée un fichier temporaire dans le mode "wb+", qui sera automatiquement détruit lors de sa fermeture ou lors de la fin normale du programme.

Note : cette fonction n'est pas définie dans le stdio.h du Lattice/SAS C 5.10.

int setvbuf(FILE *stream, char *buf, int mode, size_t size)

"setvbuf()" permet de contrôler la mise en mémoire tampon du flux "stream". Il faut l'appeler avant la première lecture ou écriture sur le flux. Le mode _IOFBF provoque un tamponnage complet, _IOLBF un tamponnage par ligne de texte et _IONBF ne provoque pas de tamponnage. Si "buf" est différent de NULL, il sera utilisé comme tampon, sinon il sera alloué. "size" détermine la taille du tampon. "setvbuf()" retourne une valeur différente de 0 en cas d'erreur. Le type "size_t" est défini comme "typedef unsigned int size_t".

size_t fread(void *ptr, size_t size, size_t nobj, FILE *stream)

"fread()" lit sur le flux "stream", "nobj" objets de taille "size" et les place dans le tampon mémoire "ptr". Elle retourne le nombre d'objets effectivement lus, qui peut être inférieur au nombre demandé.

size_t fwrite(void *ptr, size_t size, size t nobj, FILE *stream)

"fwrite()" écrit dans le flux "stream, nobj" objets de taille "size" depuis le tampon mémoire "ptr". Elle retourne le nombre d'objets effectivement écrits, qui peut être inférieur au nombre demandé.

int fseek(FILE *stream, long offset, int origin)

"fseek()" positionne le pointeur de fichier pour le flux "stream". Pour un fichier binaire, la nouvelle position est fixée à "offset" octets du début si "origin" vaut SEEK SET, de la fin si "origin" vaut SEEK END, ou de la position courante si "origin" vaut SEEK CUR. Pour un fichier texte, "offset" doit valoir 0 (ou une valeur retournée par "ftell()") et "origin" doit valoir SEEK SET. "fseek()" retourne une valeur non nulle en cas d'erreur.

int ftell(FILE *stream)

"ftell()" retourne la position courante dans le fichier pour le flux "stream", ou -1L en cas d'erreur.

void rewind(FILE *stream)

"rewind()" replace le pointeur de fichier au début du flux "stream" et réinitialise toute erreur détectée. "rewind(fp)" est l'équivalent de fseek(fp, 0, SEEK_SET); clearerr(fp);.

int fgetpos(FILE *stream, fpos_t *ptr)

"fgetpos()" place dans "*ptr" la position courante dans "stream". Elle retourne une valeur non nulle en cas d'erreur. Le type "fpos_t" est défini comme "typedef unsigned long fpos_t".

int fsetpos(FILE *stream, const fpos_t *ptr)

"fsetpos()" déplace le pointeur de fichier de "stream" à la position mémorisée dans "*ptr" par fgetpos(). "fsetpos()" retourne une valeur non nulle en cas d'erreur.

void clearerr(FILE *stream)

"clearerr()" remet à 0 les indicateurs de fin de fichier et d'erreur du flux "stream".

int feof(FILE *stream)

"feof()" retourne une valeur non nulle si l'indicateur de fin de fichier (EOF) est positionné pour le flux "stream".

Plusieurs types particuliers, généralement définis par un "typedef" bien placé et quelques fois par un "#define" de derrière les fagots, y figurent également. C'est notamment le cas de FILE, size_t et fpos_t, entrevus précédemment. D'autres types seront rencontrés, que nous expliciterons le moment venu.

int ferror(FILE *stream)

"ferror()" retourne une valeur non nulle si l'indicateur d'erreur est positionné pour le flux "stream".

void perror(const char *s)

"perror(s)" imprime la chaîne "s" suivie d'un message d'erreur (dépendant du système) qui correspond à l'entier "errno". C'est l'équivalent de fprintf(stderr, "%s: %s\n", s, sys_errlist[errno]);.

int fgetc(FILE *stream)

"fgetc()" retourne le caractère suivant du flux "stream", converti en int. "fgetc()" retourne EOF si la fin de fichier est atteinte.

char *fgets(char *s, int n, FILE *stream)

"fgets()" lit au plus n-1 caractères dans le flux "stream" et les place dans la chaîne "s", en s'arrêtant si elle rencontre un caractère de fin de ligne "\n". La chaîne est ensuite terminée par le caractère nul "\0". "fgets()" retourne "s" si tout s'est bien passé, et NULL en cas d'erreur.

int fputc(int c, FILE *stream)

"fputc()" écrit le caractère "c" dans le flux "stream". Elle retourne le caractère écrit, ou EOF en cas d'erreur.

int fputs(const char *s, FILE *stream)

"fputs()" écrit la chaîne "s" dans le flux "stream". Elle retourne EOF en cas d'erreur.

int getc(FILE *stream)

"getc()" équivaut à "fgetc()", mis à part que c'est une macro (qui peut donc évaluer stream plusieurs fois).

int getchar(void)

"getchar()" est une macro équivalant à "getc(stdin)".

char *gets(char *s)

"gets()" agit comme "fgets()", si ce n'est qu'elle utilise automatiquement "stdin" et qu'aucune vérification de longueur de la chaîne en entrée n'est effectuée. Elle retourne "s" si tout va bien, ou NULL en cas d'erreur.

int putc(int c, FILE *stream)

"putc()" équivaut à "fputc()", mis à part que c'est une macro.

int putchar(int c)

"putchar()" est une macro équivalant à "putc(c, stdout)".

int puts(const char *s)

"puts()" écrit la chaîne "s" et le caractère de fin de ligne "\n" sur stdout. Elle retour EOF en cas d'erreur.

int ungetc(int c, FILE *Stream)

"ungetc()" remet "c" dans le flux "stream", où il sera retrouvé à la prochaine lecture. Elle retourne le caractère remis, ou EOF en cas d'erreur.

int fprintf(FILE *stream, const char *format, ...)

"fprint()" convertit ses arguments (en nombre variable) d'après le format spécifié par la chaîne "format", et écrit le résultat dans le flux "stream". Elle retourne le nombre de caractères écrits, ou une valeur négative en cas d'erreur. La chaîne de formatage est décrite sous printf().

int fscanf(FILE *stream, const char *format, ...)

"fscanf()" lit les données depuis le flux "stream", d'après le format défini par la chaîne "format", et affecte les valeurs converties à chacun de ses arguments (en nombre variable), chacun de ceux-ci devant être un pointeur. Elle retourne le nombre d'objets convertis et affectés si tout va bien, et EOF en cas d'erreur. La chaîne de formatage est décrite sous scanf().

Les listes variables d'arguments

La bibliothèque standard C met à la disposition du programmeur des fonctions dont le nombre et le type des arguments peut être variable. L'exemple le plus frappant en est printf() et ses consoeurs.

Il n'y a bien entendu là-dedans aucun truc particulier (seulement une bonne gestion de la pile par le compilateur !) et le programmeur peut lui-même créer de telles fonctions. Bien que cela relève du fichier d'en-tête stdarg.h, il nous a paru opportun de faire un aparté sur le sujet.

On déclare une telle fonction en spécifiant trois points de suspension comme son dernier argument :

int fonction(char *arg1, int arg2, ...);

Dans cet exemple, notre fonction accepte au minimum deux arguments, appelés "arg1" et "arg2", respectivement de type "char" et "int". D'autres peuvent suivre, de n'importe quel type. Pour implémenter cette fonction, on définit une liste d'arguments de type "va_list", que l'on initialise (une seule fois !) au moyen de la macro "va_start", à laquelle on fournit le nom du dernier argument obligatoire :

va_list va;
va_start(va, arg2);

Par la suite, chaque appel à la macro "va_arg" retournera un objet avant le type et la valeur de l'argument suivant non nommé, et modifiera la liste "va" de telle sorte qu'elle pointe sur le prochain argument. Quand tous les arguments transmis auront été traités, il faut impérativement appeler la macro "va_end" avant de sortir de la fonction.

Pour illustrer tout ceci, la fonction suivante concatène toutes les chaînes qui lui sont transmises en une seule, dans le tampon mémoire"buf". Elle retourne le nombre total de caractères placés dans "buf".

ANSI C

Fonctions de type printf() et chaîne de formatage

La grande majorité des fonctions d'entrées-sorties de la bibliothèque standard du C version ANSI, est dérivée de printf(), dont la chaîne de formatage est un petit bijou de puissance... et de complexité.

Rappelons en effet que toutes les fonctions de type printf() acceptent un nombre variable d'arguments de types quelconques. De ce fait, il fallait donner au programmeur un moyen pratique de contrôler le format de sortie des données, qui consiste à spécifier en premier argument de la fonction, le seul obligatoire d'ailleurs, une chaîne de formatage permettant non seulement ce contrôle de format, mais aussi et éventuellement des conversions de types.

La chaîne de formatage contient donc deux types d'objets : des caractères "ordinaires", qui seront recopiés tels quels sur le flux de sortie (écran, fichier, imprimante...) et des spécifications optionnelles de conversion, dont chacune provoque une interprétation différente par la fonction de l'argument suivant à sortir. Ces spécifications commencent par le caractère spécial "%" (simplement pour les distinguer des caractères normaux !) et se terminent par un caractère de conversion. Entre les deux, on peut éventuellement placer :

1. Des drapeaux (flags) en nombre et en ordre quelconques, qui modifient la spécification :
  • - : cadre l'argument à gauche dans son champ de sortie.
  • + : imprime obligatoirement le signe d'un nombre.
  • (espace) : si le nombre est positif, laisse un espace à la place de son signe.
  • 0 : complète le début du champ de sortie par des 0.
  • # : spécifie un format de sortie différent. Pour un nombre octal, le premier chiffre imprimé sera 0 ; pour un chiffre hexadécimal, le préfixe "0x" sera sorti avant le nombre lui-même ; pour un nombre réel, le point décimal sera toujours sorti.
2. Un nombre, qui précise la largeur minimum du champ de sortie. L'argument sera sorti dans un champ de largeur au moins égale, supérieure si nécessaire, à ce nombre. L'argument peut être numérique ou alphanumérique, et il sera automatiquement aligné à droite dans le champ de sortie, à moins que le drapeau "-" n'ait été spécifié. Le caractère de remplissage est normalement l'espace, à moins que le drapeau "0" n'ait été spécifié.

3. Un second nombre, qui décrit la précision désirée. Pour les chaînes, la précision donne le nombre maximum de caractères à sortir, même si la chaîne est plus longue. Pour les entiers, elle donne le nombre minimum de chiffres à sortir, en complétant au besoin par des "0". Pour des réels, elle spécifie le nombre de chiffres après le point décimal. Si la précision est donnée, elle doit être séparée de la largeur du champ par un point.

4. Un suffixe qui modifie le type du nombre à sortir : "h" indique un short ou un unsigned short, "1" indique un long ou un unsigned long et "L" indique un long double.

On peut également spécifier la largeur et/ou la précision par une étoile "*", qui indique que le nombre correspondant n'est pas inclus dans la chaîne de formatage, mais doit être lu depuis l'argument suivant de la fonction.

Les caractères de conversion sont réunis dans le tableau ci-dessous.

Caractère Type Conversion effectuée
d,i int Notation décimale signée (exemple : -10)
o int Notation octale non signée (exemple : 74)
x,X int Notation hexadécimale non signée (exemple : 9A)
u int Notation décimale non signée (exemple : 4294967056)
c int Un seul caractère (exemple : j)
s char * Une chaîne de caractères (exemple : ANT)
f double Notation décimale de la forme [-]m.de (exemple : 37.2)
e,E double Notation décimale de la forme [-]m.de x (exemple : 53.8E-10)
p void * Notation "pointeur" dépendant de l'implantation. Correspond à un ULONG (huit caractères sur Amiga

Note : la bibliothèque exec.library dispose d'une fonction de formatage des chaînes de caractères dans un tampon mémoire, RawDoFmt(), fortement inspirée de printf(), mais aux performances toutefois restreintes. Reportez-vous à la documentation adéquate pour plus de renseignements.

Quelques exemples de formats aideront certainement à éclaircir quelques points encore obscurs.

ANSI C

Et ainsi de suite... Il serait trop long, voire impossible, de détailler toutes les possibilités offertes par la chaîne de formatage. Le mieux est encore d'essayer !


[Retour en haut] / [Retour aux articles]