Article View: fr.comp.lang.c
Article #15317[FAQ] fr.comp.lang.c - partie 2/4
From: Guillaume Rumeau
Date: Sun, 02 Oct 2005 09:49
Date: Sun, 02 Oct 2005 09:49
1331 lines
39301 bytes
39301 bytes
Archive-Name: fr/comp/lang/faq-c-2
Archive-Name: fr/comp/lang/faq-c-2
---------------------------------------------------------------------------
FAQ de fr.comp.lang.c
18 avril 2003
Partie 2/4 (sections 5 � 9)
---------------------------------------------------------------------------
5. D�clarations et initialisations
5.1 Quels types utiliser ?
Il existe en C plusieurs types de nombres entiers.
Si vous avez � g�rer de grands nombres, il faut utiliser le type
long. Avec la norme C99 (cf. 3.7), un type long long est disponible.
Pour des nombres de petites tailles, et si la place m�moire
manque, c'est le type short qu'il faut prendre.
Le type char peut parfois �tre utilis� comme tr�s petit
entier. Mais cela doit �tre �vit� au maximum. En effet, outre les
probl�mes de signe, le code g�n�r� peut �tre plus complexe et
risque finalement de prendre plus de place et de faire perdre du
temps. M�me les short font parfois perdre du temps.
Dans les autres cas, int est bien adapt�.
Le mot cl� unsigned est � utiliser pour les nombres
positifs, si vous avez des probl�mes dus aux d�bordements ou pour
les traitements par bits.
Le choix entre float et double ne se pose pas.
On devrait toujours utiliser double, sauf si on a
vraiment des contraintes de m�moire (cf. 11.1
et 11.10).
5.2 Comment d�finir une structure qui pointe sur elle-m�me ?
Il y a plusieurs fa�ons correctes de le faire.
Le probl�me est que dans la d�finition de la structure avec un
typedef, le type n'est pas encore d�fini. Voici la
mani�re la plus simple, o� pour contourner le probl�me, on ajoute
un tag � la structure.
typedef struct node {
char * item;
struct node * next;
} node_t;
Une autre solution consiste � d�clarer le type de la structure
avant sa d�finition, avec un pointeur.
typedef struct node * node_p;
typedef struct node {
char * item;
node_p next;
} node_t;
Cette construction r�cursive est utilis�e pour obtenir des listes
cha�n�es ou des arborescences.
5.3 Comment d�clarer une variable globale ?
Sauf dans des cas pr�cis, vous devriez �viter d'utiliser des
variables globales.
Si vous y tenez vraiment, la meilleure solution est de d�clarer la
variable dans un fichier xxx.c, et de mettre la
d�claration extern dans un xxx.h associ�. Ceci
�vitera des red�finitions de la variable lors des inclusions
d'en-t�tes.
Le mot cl� static permet d'avoir des variables locales
persistantes, ce qui peut �tre une bonne alternative aux
globales.
5.4 Quelle est la diff�rence entre const et #define ?
Cela n'a rien � voir.
Le #define permet de d�finir une macro, alors que le mot
cl� const indique que l'objet qualifi� est prot�g� en
�criture.
Lors de la compilation, la premi�re phase est effectu�e par le
pr�-processeur qui remplace les macros par leurs valeurs. C'est un
simple copier/coller.
Une variable d�clar�e const reste quant � elle une
variable, mais on ne peut lui affecter une valeur que lors de
l'initialisation. Apr�s, elle est n'est plus modifiable.
Le mot-cl� const permet aussi au compilateur d'effectuer
des optimisations en pla�ant par exemple la variable dans un
registre.
Il est � noter qu'avec la nouvelle norme C99, il est possible de
d�clarer un tableau dont la taille est donn�e par une variable
const. Auparavant, il fallait utiliser une macro.
Voir aussi les questions 13.7 et 5.5.
5.5 Comment utiliser const avec des pointeurs ?
Le mot-cl� const permet de prot�ger une variable de
modifications ult�rieures. Une variable constante n'est modifiable
qu'une fois, lors de l'initialisation.
Un pointeur �tant une variable comme les autres, const
s'utilise de la m�me fa�on.
Voici un exemple :
const char * sz1;
char const * sz2;
char * const sz3;
char const * const sz4;
Les variables sz1 et sz2 sont des pointeurs sur
objet constant de type char. La variable sz3 est
un pointeur constant sur un objet (non constant) de type
char. Enfin, sz4 est un pointeur constant sur un
objet constant de type char.
Un petit � amusement � pour terminer, que signifie la d�claration
const char * (* f)(char * const * s); ?
Voir aussi la question 5.4.
5.6 Comment bien initialiser ses variables ?
Les variables globales, ou d�clar�es static, sont
initialis�es automatiquement lors de leur d�finition.
Si aucune valeur n'est sp�cifi�e, c'est un z�ro qui est pris
(suivant le type de la variable, 0, 0.0 ou
NULL).
Ce n'est pas le cas pour les variables automatiques (les autres).
Il est donc n�cessaire de le faire � � la main �.
Les variables allou�es dynamiquement avec malloc() ne
le sont pas non plus.
On pourra utiliser calloc() qui initialise les
variables allou�es. Il est � noter que calloc() met les
bits � 0 comme le ferait memset(). Cette
initialisation est valide pour les types entiers (char,
short, int et long), mais non portable
pour les pointeurs et les flottants.
Une bonne m�thode qui initialise correctement les variables
suivant leur type est celle-ci :
{
type a[10]={0};
struct s x ={0};
}
Avec la variante dynamique :
[static] const struct s x0 ={0};
{
struct s *px =malloc(sizeof *px);
*px=x0;
}
5.7 Comment d�clarer un tableau de fonctions ?
Deux cas se pr�sentent. Si toutes les fonctions ont le m�me
prototype, il suffit de faire ainsi :
extern char * f(int, int); /* une fonction */
char * (*fp[N])(int, int); /* Un tableau de N fonctions */
char * sz;
fp[4] = f; /* affectation de f dans le tableau */
sz = fp[4](42, 12); /* utilisation */
Si les fonctions ont des prototypes diff�rents, il faut d�clarer
un tableau de fonctions g�n�riques. Les fonctions g�n�riques
n'existent pas � proprement parler. Il faut utiliser une fonction
sans argument sp�cifi� et retournant un int.
int (*fp[N])(); /* Un tableau de N fonctions quelconques*/
Cela � capte � la plupart des cas, sauf les fonctions
au nombre d'arguments variables, comme printf().
Voir aussi la question 7.4.
5.8 Comment conna�tre le nombre d'�l�ments d'un tableau ?
On peut utiliser une macro de ce type l� :
#define NELEMS(n) (sizeof(n) / sizeof *(n))
5.9 Quelle est la diff�rence entre char a[] et char * a?
Il faut bien se rappeler qu'en C, un tableau n'est pas un
pointeur, m�me si � l'usage �a y ressemble beaucoup.
char a[] d�clare un tableau de char de taille
inconnue (type incomplet). D�s l'initialisation, par
char a[] = "Hello";
a se transforme en char[6], soit un tableau de
six char (type complet).
Il arrive souvent de voir la confusion entre � tableau � et �
pointeur constant � (moi m�me je la fais parfois ;-) )
Ce � pointeur constant � est inspir� par K&R, 1�re
�dition. C'�tait une m�taphore malheureuse de K&R qui voulait
exprimer qu'un tableau se comporte en g�n�ral comme
une � valeur (rvalue) du type pointeur �, et bien entendu une
valeur est toujours constante. Par cette m�taphore ils ont essay�
d'expliquer pourquoi on ne pouvait pas prendre l'adresse d'un
tableau.
Malheureusement ceci n'explique pas pourquoi sizeof a
donne la taille du tableau et non la taille d'un pointeur.
Sur ce sujet, la norme est plus pr�cise en disant qu'un tableau
dans une expression --- sauf dans &a ou
sizeof a ou dans des initialisations par des cha�nes
litt�rales "xyz" --- est converti automatiquement en
une valeur du type pointeur qui pointe sur l'�l�ment initial du
tableau (merci � Horst Kraemer pour ces pr�cisions).
Voir aussi la question 7.1.
5.10 Peut-on d�clarer un type sans sp�cifier sa structure ?
Plus pr�cis�ment, est-il possible de d�finir un type de donn�es
dont on veut cacher � l'utilisateur de la biblioth�que
l'impl�mentation ?
Oui c'est possible, en encapsulant ce type dans une structure.
Il y a au moins deux m�thodes. La premi�re est de d�finir une
structure (publique) qui contient un pointeur void *, qui
pointe vers une variable du type priv�. Ce type priv� est d�fini
avec les fonctions, dans un .c.
Il faut alors pr�voir des fonctions d'initialisation et de
destruction des objets.
Une autre solution consiste � d�clarer une structure qui contient
une donn�e du type � prot�ger, et � acc�der aux types par des
pointeurs uniquement.
Voici un exemple :
/* data.h */
struct Data_s ;
typedef struct Data_s Data_t ;
extern Data_t * DataNew(int x, int y);
extern int DataFonction(Data_t * this);
/* data.c */
#include <stdio.h>
#include <stdlib.h>
#include "data.h"
struct Data_s {
int x;
int y;
};
Data_t * DataNew(int x , int y) {
Data_t * pData;
pData = malloc(sizeof *pData) ;
if (pData) {
pData->x = x;
pData->y = y;
}
return pData;
}
int DataFonction(Data_t * this) {
if (!this)
return 0;
return (this->x * this->x) + (this->y * this->y);
}
/* main.c */
#include <stdio.h>
#include "data.h"
int main(void) {
Data_t *psData;
psData = DataNew(3, 4);
printf("%d\n", DataFonction(psData));
return 0;
}
(Merci � Yves Roman pour cet exemple.)
6. Structures, unions, �num�rations
6.1 Quelle est la diff�rence entre struct et typedef struct ?
struct x1 { ... };
typedef struct { ... } x2;
La premi�re �criture d�clare un tag de structure
x1. La deuxi�me d�clare un nouveau type nomm�
x2.
La principale diff�rence est l'utilisation.
La deuxi�me �criture permet un peu plus l'abstraction de type.
Cela permet de cacher le v�ritable type derri�re x2,
l'utilisateur n'�tant pas sens� savoir que c'est une structure.
struct x1 v1;
x2 v2;
6.2 Une structure peut-elle contenir un pointeur sur elle-m�me ?
Oui.
Voir aussi la question 5.2.
6.3 Comment impl�menter des types cach�s (abstraits) en C ?
L'une des bonnes fa�ons est d'utiliser des pointeurs sur des
structures qui ne sont pas publiquement d�finies. On peut en plus
cacher les pointeurs par des typedef.
Voir aussi la question 5.10.
6.4 Peut-on passer des structures en param�tre de fonctions ?
Oui, c'est parfaitement autoris�. Toutefois, rappelons que les
param�tres en C sont pass�s par valeurs et copi�s dans la pile.
Pour des grosses structures, il est pr�f�rable de passer un
pointeur dessus, et �ventuellement un pointeur constant.
6.5 Comment comparer deux structures ?
Il n'existe pas en C d'op�rateur ou de fonction
pour comparer deux structures. Il faut donc le faire � la main,
champs par champs.
Une comparaison bit � bit n'est pas portable, et risque de ne pas
marcher, en raison du padding (alignement sur
certains octets).
6.6 Comment lire/�crire des structures dans des fichiers ?
Il faut utiliser les fonctions fread() et
fwrite(). Attention : les fichiers obtenus ne sont pas
portables.
Une m�thode plus portable consiste � enregistrer les structures
dans un fichier texte.
6.7 Peut-on initialiser une union ?
La norme pr�voit d'initialiser le premier membre d'une union.
Pour le reste, ce n'est pas standard.
En C99, on peut initialiser les champs d'une union :
union { /* ... */ } u = { .any_member = 42 };
6.8 Quelle est la diff�rence entre une �num�ration et des #define ?
Il y a peu de diff�rences.
L'un des avantages de l'�num�ration est que les valeurs num�riques
sont assign�es automatiquement.
De plus, une �num�ration se manipule comme un type de donn�es.
Certains programmeurs reprochent aux �num�rations de r�duire le
contr�le qu'ils ont sur la taille des variables de type �num�ration.
6.9 Comment r�cup�rer le nombre d'�l�ments d'une �num�ration ?
Ceci n'est possible de fa�on automatique que si les valeurs se
suivent.
typedef enum { A, B, C, D} type_e;
Dans cette �num�ration, les valeurs sont donn�es par le
compilateur dans l'ordre croissant, � partir de 0 et avec un pas de
1. Ainsi, le nombre d'�l�ments de type_e est D + 1.
On peut rajouter un �l�ment � l'�num�ration qui donne directement
le nombre d'�l�ments :
typedef enum { RED, BLUE, GREEN, YELLOW, NB_COLOR} color_e;
le nombre d'�l�ments de color_e est donc NB_COLOR.
Si l'on est oblig�, pour des raisons diverses et vari�es,
de fixer d'autres valeurs aux constantes, alors cette solution ne
marche pas. On peut toujours rajouter un champs dans l'�num�ration
et fixer manuellement sa valeur.
6.10 Comment imprimer les valeurs symboliques d'une �num�ration ?
On ne peut pas le faire simplement. Il faut �crire une fonction
qui le fait. Un probl�me qui se pose alors est la maintenance, car
une modification des valeurs de l'�num�ration entra�ne la
n�cessit� d'une mise � jour de cette fonction.
Voici un code qui limite les probl�mes de mise � jour :
/* Fichier foo.itm */
ITEM(FOO_A)
ITEM(FOO_B)
ITEM(FOO_C)
/**/
/* Fichier foo.h */
#ifndef FOO_H
#define FOO_H
#define ITEM(a) a,
typedef enum {
#include "foo.itm"
FOO_NB
} foo_t;
#undef ITEM
#define ITEM(a) #a,
const char * const aFoo[] = {
#include "foo.itm"
};
#undef ITEM
#endif
/**/
/* Fichier foo.c */
#include <stdio.h>
#include "foo.h"
int main(void) {
foo_t foo;
for (foo = FOO_A; foo < FOO_NB; foo++) {
printf("foo=%d ('%s')\n",foo, aFoo[foo]);
}
return 0;
}
Merci � Emmanuel Delahaye pour cet exemple.
7. Tableaux et pointeurs
7.1 Quelle est la diff�rence entre un tableau et un pointeur ?
Un tableau n'est pas un pointeur.
Un tableau est une zone m�moire pouvant contenir N
�l�ments cons�cutifs de m�me type.
Un pointeur est une zone m�moire qui contient l'adresse d'une
autre zone m�moire. Toutefois, dans un grand nombre de cas, tout
se passe comme si c'�tait la m�me chose.
� ce titre, il faut bien faire la diff�rence entre a[i]
pour un tableau et ap[i] pour un pointeur.
Voici un exemple :
char a[] = "Bonjour";
char *ap = "Au revoir";
L'expression a[3] signifie que l'on acc�de aux quatri�me
�l�ment du tableau. ap[3] signifie que l'on acc�de � la
zone m�moire point�e par (ap+3).
Autrement dit, a[3] est l'objet situ� 3 places apr�s
a[0] (a est le tableau entier),
alors que ap[3] est l'objet situ� 3 places apr�s l'objet
point� par ap. Dans l'exemple, a[3] vaut
'j' et ap[3] vaut 'r'.
Voir aussi la question 5.9.
7.2 Comment passer un tableau � plusieurs dimensions en param�tre d'une fonction ?
Ce n'est pas si facile.
La r�gle de base est qu'il faut conna�tre la taille des
N-1 derni�res dimensions. Pour un tableau � deux
dimensions, la deuxi�me doit �tre connue, et la fonction doit �tre
d�clar�e ainsi :
int f1(int a[][NCOLUMNS]);
/* a est un tableau a deux dimensions (cf. remarque) */
int f2(int (*ap)[NCOLUMNS]);
/* ap est un pointeur sur un tableau */
Si elle n'est pas connue, il faut passer la taille du tableau en
param�tre (ligne ET colonne) et un pointeur sur le tableau :
int f(int * a, int nrows, int ncolumns);
On acc�de aux �l�ments du tableau ainsi :
a[i * ncolumns + j] /* element de la ieme ligne
* et de la jeme colonne */
Une remarque :
Dans une d�claration de param�tre
int f1(int a[][NCOLUMNS])
ou
int f1(int a[42][NCOLUMNS])
a est un pointeur sur int[NCOLUMS] malgr�
l'�criture. La d�claration est interpr�t�e comme
int (*a) [NCOLUMS]
Ainsi les d�clarations de f1() et f2() dans
l'exemple initial sont exactement les m�mes.
7.3 Comment allouer un tableau � plusieurs dimensions ?
La premi�re solution est d'allouer un tableau de pointeurs, puis
d'initialiser chacun de ces pointeurs par un tableau dynamique.
#include <stdlib.h>
int ** a = malloc(nrows * sizeof *a );
for(i = 0; i < nrows; i++)
a[i] = malloc(ncolumns * sizeof *(a[i]));
Dans la vraie vie, le retour de malloc() doit �tre
v�rifi�.
Une autre solution est de simuler un tableau multi-dimensions
avec une seule allocation :
int *a = malloc(nrows * ncolumns * sizeof *a);
L'acc�s aux �l�ments se fait par :
a[i * ncolumns + j] /* element de la ieme ligne
* et de la jeme colonne */
7.4 Comment d�finir un type pointeur de fonction ?
On utilise typedef, comme pour n'importe quel autre
type. Voici un exemple :
int f(char * sz); /* une fonction */
int (*pf)(char *);/* un pointeur sur une fonction */
typedef int (*pf_t)(char *);
/* un type pointeur sur fonction*/
Il est toutefois pr�f�rable de ne pas cacher le pointeur dans un
typedef. La solution suivante est plus jolie :
typedef int (f_t)(char *); /* un type fonction */
f_t * pf; /* un pointeur sur ce type */
On l'utilise alors de cette fa�on :
pf = f;
int ret = pf("Merci pour cette reponse");
Voir aussi la question 5.7.
7.5 Que vaut (et signifie) la macro NULL ?
NULL est une macro qui repr�sente une valeur sp�ciale
pour d�signer un pointeur nul lorsque converti au type appropri�.
Elle est d�finie dans <stddef.h> ou dans
<stdio.h>.
La valeur r�elle de NULL est d�pendante de
l'impl�mentation, et n'est pas n�cessairement un pointeur, ni de
type pointeur. Des valeurs possibles sont ((void *)0) ou
0.
NULL permet de distinguer les pointeurs valides des
pointeurs invalides. Par exemple, malloc() renvoie une
valeur comparable � NULL quand elle �choue.
Voir aussi la question 12.3.
7.6 Que signifie l'erreur � NULL-pointer assignment � ?
Cela signifie que vous avez essay� d'acc�der � l'adresse 0 de la
m�moire. Vous avez probablement d�r�f�renc� un pointeur
NULL, ou oubli� de tester la valeur retour d'une
fonction, avant de l'utiliser.
7.7 Comment imprimer un pointeur ?
La seule mani�re pr�vue par la norme pour imprimer correctement un
pointeur est d'utiliser la fonction printf() avec le
code de format %p.
Le pointeur doit �tre d'abord cast� en un pointeur
g�n�rique void *.
char * p;
printf("Pointeur p avant initialisation: %p\n",
(void *)p);
7.8 Quelle est la diff�rence entre void * et char * ?
Le premier est un pointeur g�n�rique, qui peut recevoir l'adresse
de n'importe quel type d'objet.
Le second est un pointeur sur un caract�re, g�n�ralement utilis�
pour les cha�nes.
Avant la norme ANSI, le type void n'existait pas. C'�tait
donc char * qui �tait utilis� pour faire des pointeurs
g�n�riques. Depuis la norme, ce n'est plus valide.
De nombreux programmeurs ont toutefois gard� cette habitude,
notamment dans le cast de fonctions comme
malloc() (cf. 12.1).
8. Cha�nes de caract�res
8.1 Comment comparer deux cha�nes ?
Pour comparer deux cha�nes entre elles, il faut utiliser la
fonction strcmp(), et non l'op�rateur
==. Celui-ci comparera les pointeurs entre eux, ce qui
n'est probablement pas ce qui est voulu !
const char * sz = "non";
if(strcmp(sz, "oui") == 0) {
/* sz et "oui" sont egaux */
}
Il existe aussi la fonction strncmp() qui permet de
contr�ler la longueur de comparaison.
8.2 Comment recopier une cha�ne dans une autre ?
Il faut utiliser la fonction strcpy(),
et non l'op�rateur d'affectation =.
Il faut s'assurer que l'on dispose d'espace suffisant dans la
cha�ne cible avant d'utiliser strcpy(), qui ne fait
aucun contr�le de d�bordement.
Pour une copie plus s�curis�e, on pr�f�rera la fonction
strncpy().
8.3 Comment lire une cha�ne au clavier ?
Il y a de nombreuses fonctions qui le font.
Le plus s�r est d'utiliser fgets().
char tab[20];
fgets(tab, sizeof tab, stdin);
Voici un exemple complet d'utilisation propre de
fgets() pour la lecture d'une ligne, avec un contr�le
d'erreurs. Cette fonction est fournie par Emmanuel Delahaye.
#include <stdio.h>
#include <string.h>
int get_line(char *buf, size_t size) {
int ret; /* 0=Ok 1=Err 2=Incomplet
* On peut aussi definir des constantes. (Macros, enum...)
*/
if (fgets(buf, size, stdin) != NULL) {
char *p = strchr(buf, '\n'); /* search ... */
if (p != NULL) {
*p = 0; /* ... and kill */
ret = 0;
}
else {
ret = 2;
}
}
else {
ret = 1;
}
return ret;
}
Il peut �tre int�ressant dans certains cas de faire un traitement
particulier dans le cas 2 (lecture incompl�te), comme vider le
buffer du flux ou redimentionner la zone de r�ception (voir la question
14.5).
La fonction gets() est � proscrire, car il n'y a aucun
contr�le de d�bordement, ce qui peut engendrer de nombreux bugs
(stack overflow) (cf. 8.7).
8.4 Comment obtenir la valeur num�rique d'un char (et vice-versa) ?
En C, un char est un petit entier. Il n'y a donc aucune
conversion � faire. Quand on a un char, on a aussi sa
valeur, et vice-versa.
8.5 Que vaut sizeof(char) ?
Un char vaut et vaudra toujours 1 ind�pendamment de
l'impl�mentation. En effet, les tailles d'allocation en C se
calculent en char (size_t). Or, un char
a une taille de 1 char. Donc
sizeof(char) == (size_t) 1
Pour autant, un char ne fait pas forc�ment 8 bits (un
octet) (voir aussi 16.2).
8.6 Pourquoi sizeof('a') ne vaut pas 1 ?
En C, les caract�res constants sont des int, et non des
char. Ainsi,
sizeof('a') == sizeof(int)
ce qui peut valoir 2 ou 4 sur votre machine, ou autre chose.
8.7 Pourquoi ne doit-on jamais utiliser gets() ?
Serge Paccalin donne l'exemple suivant :
#include <stdio.h>
int main(void)
{
char chaine[16];
printf("Tapez votre nom :\n");
gets(chaine);
printf("Vous vous appelez %s.\n",chaine);
return 0;
}
Quand on tape une cha�ne de plus de 15 caract�res, rien ne va plus :
gets() accepte sans broncher la cha�ne mais lorsqu'il
est question de la stocker dans
char chaine[16]
on peut obtenir un magnifique
Segmentation fault (core dumped).
Dans certains cas, avec certains compilateurs
sur certaines machines et certains OS, il est possible que �a
passe. Mais attention ! C'est un leurre, et le changement de cible
d�montrera la malfa�on.
8.8 Pourquoi ne doit-on presque jamais utiliser scanf() ?
scanf() est une fonction de la biblioth�que standard, qui est
souvent la premi�re que l'on apprend pour lire des donn�es au clavier.
Cette fonction n'est pas plus dangereuse qu'une autre, � condition de
bien savoir l'utiliser, ce qui n'est pas donn� � tout le monde.
Par exemple, regardez le programme suivant donn� par Serge
Paccalin :
#include <stdio.h>
int main(void) {
int val = 0;
while (val != 1){
printf("Tapez un nombre"
"(1 pour arreter le programme) :\n");
scanf(" %d",&val);
printf("Vous avez saisi %d.\n",val);
}
return 0;
}
Et il explique : � Quand le programme demande un nombre, taper
"toto" suivi de la touche
Entr�e. Le programme part en boucle parce que tous les
scanf() successifs butent sur "toto" qui reste
ind�finiment dans stdin. �
Dans la plupart des cas, l'utilisation de la fonction fgets()
sera plus simple et moins risqu�e.
scanf() est une fonction qui peut �tre utilis�e dans
certaines conditions, et � condition de bien savoir ce que l'on
fait. L'utilisation de scanf() pour la lecture de
nombres (entiers ou flottants) est l'une des plus acceptable.
Par contre, la lecture d'une cha�ne avec scanf() sans
contr�le de format est aussi dangereux que gets().
scanf("%s", astring) ;
est donc a proscrire.
scanf() n'est pr�f�rable � fgets() que dans
le cas ou l'on veut lire mot par mot et non ligne par ligne,
et conserver le reste dans le buffer du flux
d'entr�e.
Sinon, lire un mot avec fgets() ne pose pas de probl�me
et est m�me plus simple.
scanf("%4s", astring) ;
Cette construction, par exemple, ne pose pas les probl�mes cit�s
plus haut (si le contr�le d'erreurs est fait), et est parfois
utile.
Voir aussi les questions 8.7 et 8.3
9. Fonctions et prototypes
9.1 Pour commencer ...
Il y a trois notions :
- d�claration,
- d�finition,
- prototype.
La d�claration d'une fonction, c'est annoncer que tel
identificateur correspond � une fonction, qui renvoie tel type. La
d�finition d'une fonction est une d�claration o�, en plus, on
donne le code de la fonction elle-m�me. Le prototype est une
d�claration de fonction o� le type des arguments est �galement
donn�.
Par exemple :
int f(); /* declaration de f(), renvoyant un int, pas de prototype */
int f(void);/* declaration de f(), renvoyant un int, prototype (0 arg) */
int f(void) /* definition de f() avec declaration avec prototype */
{
return 42;
}
int f(x) /* definition de f() avec declaration sans prototype */
int x;
{
return x;
}
int f() /* definition de f() avec declaration sans prototype */
{
return 42;
}
Ce qui n'est pas possible :
- avoir une d�finition sans d�claration,
- avoir un prototype sans d�claration.
Ce qui est autoris� :
- appeler une fonction d�clar�e, qu'elle ait un prototype ou
pas.
Ce qui �tait autoris� en C90 mais ne l'est plus en C99 :
- appeler une fonction non d�clar�e ; l'appel valait
d�claration dite � implicite �, sans prototype et avec type de
retour int.
Ce qui est encore autoris� en C99 mais dispara�tra bient�t :
- d�clarer/d�finir une fonction sans prototype.
Ce qu'il faut faire quand on veut programmer lisiblement, en
d�tectant les bugs et en gardant du code maintenable et compatible
avec le futur :
- utiliser des d�clarations et d�finitions avec prototype.
9.2 Qu'est-ce qu'un prototype ?
Un prototype est une signature de fonction.
Comme tout objet en C, une fonction doit �tre d�clar�e avant son
utilisation. Cette d�claration est le prototype de la fonction.
Le prototype doit indiquer au compilateur le nom de la fonction,
le type de la valeur de retour et le type des param�tres (sauf
pour les fonctions � arguments variables, comme printf().
(cf. 9.6).
int fa(int a, char const * const b);
int fb(int, char const * const);
Les noms de param�tre sont optionnels, mais il est fortement
conseill� de les laisser. Cela donne une bonne indication sur
leurs r�les.
Les fonctions de la biblioth�que ont �galement leur prototype.
Avant l'utilisation de celles-ci, il faut inclure les fichiers
d'en-t�te contenant les prototypes.
Par exemple, le prototype de malloc() se trouve dans
stdlib.h.
Certains pr�f�rent ajouter le mot cl� extern au
prototype, afin de rester coh�rent avec la d�claration des
variables globales.
Voir aussi les questions 9.3,
12.1, 13.5 et
14.17.
9.3 O� d�clarer les prototypes ?
Un prototype de fonction doit �tre d�clar� avant l'utilisation de
la fonction. Pour une plus grande lisibilit�, mais aussi pour
simplifier la maintenance du code, il est conseill� de regrouper
tous les prototypes
d'un module (fichier xxx.c) dans un en-t�te
(<xxx.h>). Ce dernier n'a plus alors qu'� �tre inclus
dans le code qui utilise ces fonctions. C'est le cas des fonctions de
la biblioth�que standard.
Voir aussi les questions 13.5 et
9.10.
9.4 Quels sont les prototypes valides de main() ?
La fonction main() renvoie toujours un int.
Les prototypes valides sont :
int main(void);
int main(int argc, char * argv[]);
Tout autre prototype n'est pas du tout portable et ne doit jamais
�tre utilis� (m�me s'il est accept� par votre compilateur).
En particulier, vous ne devez pas terminer la fontion
main() sans retourner une valeur positive (non nulle en
cas d'erreur).
Les valeurs de retour peuvent �tre 0,
EXIT_SUCCESS ou EXIT_FAILURE.
On pourra aussi rencontrer (sous Unix) le
prototype suivant :
int main (int argc, char* argv[], char** arge);
dans le but d'utiliser les variables d'environnement du
shell actif. Ce n'est ni portable ni standard,
d'autant plus que les fonctions getenv(),
setenv() et putenv() le sont et suffisent
largement.
Enfin, rappelons que le prototype suivant
int main () ;
est parfaitement valide en C++ (et est synonyme du premier
pr�sent� ici), mais ne l'est pas en C.
9.5 Comment printf() peut recevoir diff�rents types d'arguments ?
printf() est une fonction � nombre variable de
param�tres. Son prototype est le suivant :
int printf(const char * format, ...); /* C 90 */
int printf(const char * restrict format, ...); /* C 99 */
Le type et le nombre des param�tres n'est pas d�fini dans le
prototype, c'est le traitement effectu� dans la fonction qui doit
les v�rifier.
Pour utiliser cette fonction, il est donc imp�ratif d'inclure
l'en-t�te <stdio.h>.
Pour �crire une fonction de ce type, lire la question suivante
(9.6).
9.6 Comment �crire une fonction � un nombre variable de param�tres ?
La biblioth�que standard fournit des outils pour faciliter la
gestion de ce type de fonctions.
On les trouve dans l'en-t�te <stdarg.h>.
Le prototype d'une fonction � nombre variable de param�tres doit
contenir au moins un param�tre explicite, puis se termine par ...
Exemple :
int f(int nombre, ...);
Il faut, d'une fa�on ou d'une autre, passer dans les param�tres le
nombre d'arguments r�ellement transmis.
On peut le faire en donnant ce nombre explicitement (comme
printf()), ou passer la valeur NULL en dernier.
Attention toutefois avec la valeur NULL dans ce cas.
En effet, NULL n'est pas n�cessairement une valeur du
type pointeur mais une valeur qui donne un pointeur
nul si elle est affect�e ou pass�e ou compar�e � un type
pointeur. Le passage d'une valeur � un param�tre n'est pas une
affectation � un pointeur mais une affectation qui ob�it aux lois
sp�ciales pour les param�tres � nombre variable (ou pour les
param�tres d'une fonction sans prototype). Les lois de promotion
pour les types arithm�tiques sont appliqu�es). Si NULL est
d�fini par
#define NULL 0
alors (int)0 est pass� � la fonction. Si un pointeur n'a
pas la m�me taille qu'un int ou si un pointeur nul n'est
pas repr�sent� par � tous les bits 0 � le passage d'un 0 ne passe
donc pas de pointeur nul. La m�thode portable est
donc
f(toto,titi,(void*)NULL);
ou
f(toto,titi,(void*)0);
C'est le seul cas o� il faut caster NULL parce qu'il ne s'agit pas
d'un contexte syntactique � de pointeur �, seulement d'un contexte
� de pointeur par contrat �.
Apr�s cela, les fonctions va_start(),
va_arg() et va_end() permettent de parcourir
la liste des param�tres.
Voici un petit exemple :
#include <stdarg.h>
int vexemple(int nombre, ...){
va_list argp;
int i;
int total = O;
if(nombre < 1)
return 0;
va_start(argp, nombre);
for (i = 0; i < nombre; i++) {
total += va_arg(argp, int);
}
va_end(argp);
return total;
}
Merci � Horst Kraemer pour ces remarques.
9.7 Comment modifier la valeur des param�tres d'une fonction ?
En C, les param�tres sont pass�s par valeur. Dans la plupart des
impl�mentations, cela se fait par une copie dans la pile.
Lors du retour de la fonction, ces valeurs sont simplement
d�pil�es, et les modifications �ventuelles sont perdues.
Pour pallier cela, il faut simuler un passage des param�tres par
r�f�rence, en passant un pointeur sur les variables � modifier.
Voici l'exemple classique de l'�change des valeurs entre deux
entiers :
void echange(int * a, int * b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
9.8 Comment retourner plusieurs valeurs ?
Le langage C ne permet pas aux fonctions de renvoyer plusieurs
objets. Une solution consiste � passer l'adresse des objets �
modifier en param�tre.
Une autre solution consiste � renvoyer une structure, ou un
pointeur sur une structure qui contient l'ensemble des valeurs.
G�n�ralement, quand on a ce genre de choses � faire, c'est qu'il
se cache une structure de donn�es que l'on n'a pas identifi�e.
La pire des solutions est d'utiliser des variables globales.
9.9 Peut-on, en C, imbriquer des fonctions ?
Non, on ne peut pas.
Les concepteurs ont jug� cela trop compliqu� � mettre en oeuvre
(port�e des variables, gestion de la pile etc.).
Certaines impl�mentations, comme GNU CC le supportent
toutefois. Ceci dit, on peut tr�s bien s'en passer, en utilisant
des pointeurs sur les structures de donn�es � partager, ou en
utilisant des pointeurs de fonctions.
9.10 Qu'est-ce qu'un en-t�te ?
.
Un en-t�te est un ensemble de d�clarations, d�finitions et prototypes
n�cessaires pour compiler et pour utiliser un module.
Par exemple, pour utiliser les fonctions d'entr�es/sorties de la
biblioth�que standard, il est n�cessaire d'inclure dans son programme
l'en-t�te <stdio.h>.
Par abus, on parle souvent de fichier d'en-t�te, car historiquement,
et encore aujourd'hui pour de nombreuses impl�mentations, ces en-t�tes
sont des fichiers. C'est �galement le cas pour les en-t�tes personnels.
Toutefois, la norme n'exige pas que les en-t�tes standards soient des
fichiers � proprement parl�.
Technical Details
Message-ID:
<fr.comp.lang.faq-c-2-1128246592.421010@calva.glou.org>Path:
arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!proxad.net!u-picardie.fr!nocworld.net!news.valadon.net!ircam.fr!fr-chartes!maintfaqShow Full Headers
Complete Article Headers
X-Google-Thread: f9822,f6d7e7f8dee7499c,start X-Google-Thread: 102e92,f6d7e7f8dee7499c,start X-Google-Attributes: gidf9822,gid102e92,public X-Google-Language: FRENCH,ASCII Path: arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!proxad.net!u-picardie.fr!nocworld.net!news.valadon.net!ircam.fr!fr-chartes!maintfaq From: Guillaume Rumeau <guillaume.rumeau@wanadoo.fr> Newsgroups: fr.comp.lang.c,fr.usenet.reponses Subject: [FAQ] fr.comp.lang.c - partie 2/4 Supersedes: <fr.comp.lang.faq-c-2-1126950698.289705@calva.glou.org> Followup-To: fr.comp.lang.c Date: Sun, 2 Oct 2005 09:49:50 GMT Organization: FCLC Approved: fr-reponses@frmug.org User-Agent: MaintFaq version 2.1b Message-ID: <fr.comp.lang.faq-c-2-1128246592.421010@calva.glou.org> Content-Type: text/plain; charset=iso-8859-15 Content-Transfer-Encoding: 8bit Summary: Faq de fr.comp.lang.c X-No-Productlink: yes Lines: 1332