Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

#define vs const char * ou const int

3 réponses
Avatar
mpg
Bonjour,

Comment choisir entre une définition de macro ou une définition de
variable avec le qualificateur const ? J'ai bien lu la réponse 5.4 de la
FAQ, et je vois bien pourquoi « ça n'a rien à voir », sauf que je
n'arrive pas à percevoir en quoi ça doit influencer le choix entre les
deux dans la pratique.

Par exemple, il me semble qu'on voit couramment des définitions comme

/* debug.h */
#define KPSE_DEBUG_STAT 0 /* stat calls */
#define KPSE_DEBUG_HASH 1 /* hash lookups */
#define KPSE_DEBUG_FOPEN 2 /* fopen/fclose calls */
/* etc */

J'ai aussi vu des exemples comme

/* erreurs.h */
extern char * err_malloc;
extern char * err_realloc;
/* etc */

/* erreur.c */
#include "erreurs.h"
const char * err_malloc = "erreur d'allocation mémoire";
const char * err_realloc = "erreur d'extension mémoire";
/* etc */

Est-ce que ça dépend du type (entier, chaîne) de la constante ? Est-ce
que ça fait une différence technique ou bien est-ce une question de
style ?

Merci,

--
Manuel Pégourié-Gonnard Institut de mathématiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/

3 réponses

Avatar
Antoine Leca
Le 08/05/2009 11:56, mpg écrivit :
Comment choisir entre une définition de macro ou une définition de
variable avec le qualificateur const ?



En 2009, le conseil normal, c'est de préférer const (lorsque c'est
possible).

Là où cela risque de poser le plus de difficultés, c'est pour les
constantes entières (genre le debug.h de ton exemple), et il y a de
grandes chances que tu sois obligé d'utiliser des valeurs du
pré-processeur (#define), surtout pour les dimensions de tableaux.

Avantages des #defines: le programme résultant sera plus rapide (surtout
pour les vieux compilateurs), et n'utilisera pas d'espace mémoire
inutile pour les constantes numériques. C'est aussi plus facile à
utiliser, car il n'est pas besoin de se préoccuper des initialisations
multiples (ce qui est un souci si ta constante apparaît dans plusieurs
unités sans être déclarée static.)

Avantages des const : tu peux contrôler la visibilité et la portée de
l'objet introduit de la même manière que pour les variables etc., car en
fait c'est exactement cela, une variable (const en C signifie «readonly»
bien plus que constante); et les constantes apparaissent nominativement
dans le débogueur.


Est-ce que ça dépend du type (entier, chaîne) de la constante ?



Oui. Comme en C une chaîne est un tableau (voir l'autre fil à propos de
printf), donc manipulée comme un pointeur, elle ne se comporte pas comme
une constante numérique ; d'un autre côté, certaines constructions du C
utilisent des constantes entières comme dimensions, et on ne peut alors
utiliser une variable, même marquée const, comme valeur de cette constante.


J'ai aussi vu des exemples comme

/* erreurs.h */
extern char * err_malloc;
extern char * err_realloc;
/* etc */

/* erreur.c */
#include "erreurs.h"
const char * err_malloc = "erreur d'allocation mémoire";
const char * err_realloc = "erreur d'extension mémoire";
/* etc */



Ce qui est quand même très laid de mon point de vue, mais moi je suis
complètement imbibé d'un style qui n'est plus moderne...

1º err_malloc n'est PAS une constante... il est parfaitement possible
d'écrire dans un autre coin du programme :
err_malloc = "tout va bien, billg aux commandes";
err_realloc = "mauvaise utilisation de strtol";
même si je ne pense pas que ce soit l'effet escompté...

2º techniquement ce code a un comportement indéfini (et d'ailleurs, un
compilateur normal va probablement râler) : la déclaration dans
erreurs.h annonce que la chaîne est modifiable, et le compilateur peut
prendre avantage de cette information, par exemple en supposant que la
chaîne est allouée dans l'espace réservé aux chaînes modifiables ; mais
la définition de erreurs.c va dans l'autre sens, et spécifie que les
contenus des chaînes ne sont pas modifiables, donc le même compilateur
devrait les alloués ailleurs, peut-être en mémoire en lecture seule (ROM)...

3º rien à l'utilisation ne permet de remarquer que err_malloc est une
chaîne constante ; c'est très différent d'utiliser ERR_MALLOC, ou mieux
encore MSGERR_MALLOC (ou MsgErr_malloc, si tu préfères un style plus
discret et qui ne permet pas de croire qu'il s'agisse d'une valeur du
pré-processeur).


Antoine
Avatar
mpg
Antoine Leca scripsit:

Le 08/05/2009 11:56, mpg écrivit :
Comment choisir entre une définition de macro ou une définition de
variable avec le qualificateur const ?



En 2009, le conseil normal, c'est de préférer const (lorsque c'est
possible).



Ok.

Là où cela risque de poser le plus de difficultés, c'est pour les
constantes entières (genre le debug.h de ton exemple), et il y a de
grandes chances que tu sois obligé d'utiliser des valeurs du
pré-processeur (#define), surtout pour les dimensions de tableaux.



En effet.

/* erreurs.h */
extern char * err_malloc;
extern char * err_realloc;
/* etc */

/* erreur.c */
#include "erreurs.h"
const char * err_malloc = "erreur d'allocation mémoire";
const char * err_realloc = "erreur d'extension mémoire";
/* etc */



Ce qui est quand même très laid de mon point de vue, mais moi je suis
complètement imbibé d'un style qui n'est plus moderne...

1º err_malloc n'est PAS une constante... il est parfaitement possible
d'écrire dans un autre coin du programme :
err_malloc = "tout va bien, billg aux commandes";
err_realloc = "mauvaise utilisation de strtol";
même si je ne pense pas que ce soit l'effet escompté...



Ah, j'imagine qu'il fallait écrire

const char * const err_malloc = "erreur d'allocation mémoire";

pour interdir ça.

2º techniquement ce code a un comportement indéfini (et d'ailleurs, un
compilateur normal va probablement râler) : la déclaration dans
erreurs.h annonce que la chaîne est modifiable, et le compilateur peut
prendre avantage de cette information, par exemple en supposant que la
chaîne est allouée dans l'espace réservé aux chaînes modifiables ; mais
la définition de erreurs.c va dans l'autre sens, et spécifie que les
contenus des chaînes ne sont pas modifiables, donc le même compilateur
devrait les alloués ailleurs, peut-être en mémoire en lecture seule (ROM)...



Donc en remplaçant par

extern const char * const err_malloc;

ça devient correct ? Ceci dit, c'est vrai que c'est un peu plus lourd
syntaxiquement qu'un define, comme ça.

3º rien à l'utilisation ne permet de remarquer que err_malloc est une
chaîne constante ; c'est très différent d'utiliser ERR_MALLOC, ou mieux
encore MSGERR_MALLOC (ou MsgErr_malloc, si tu préfères un style plus
discret et qui ne permet pas de croire qu'il s'agisse d'une valeur du
pré-processeur).



Oui, effectivement, il faut prévoir une convention pour repérer les
chaînes constants qui ne sont pas des macros.

--
Manuel Pégourié-Gonnard Institut de mathématiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/
Avatar
-ed-
On 8 mai, 13:56, mpg <mpg+ wrote:
Bonjour,

Comment choisir entre une définition de macro ou une définition de
variable avec le qualificateur const ?



Comme son nom l'indique, une "variable avec const" est une variable et
pas une expression constante. C'est donc un objet en mémoire qui
dispose d'une adresse et dont l'accès ne peut se fair qu'en lecture
seule. Mais ça reste une 'l-value' (possibilité d'être le membre de
gauche d'une expression d'affectation). Il est donc techniquement
possible de modifier la valeur en utilisant un cast et l'opérateur
d'affectation, mais le résultat est indéfini (il existe des solutions
donnant un résultat défini).

Par contre, une expression constante n'est pas une l-value et ne peut
donc pas être modifiée. (encore qu'une macro puisse être supprimée --
#undef -- et définie à nouveau...). Un enum est plus 'stable' (mais ne
peut générer qu'un entier).

J'ai bien lu la réponse 5.4 de la
FAQ, et je vois bien pourquoi « ça n'a rien à voir », sauf que je
n'arrive pas à percevoir en quoi ça doit influencer le choix entre le s
deux dans la pratique.

Par exemple, il me semble qu'on voit couramment des définitions comme

/* debug.h */
#define KPSE_DEBUG_STAT 0               /* stat calls */
#define KPSE_DEBUG_HASH 1               /* hash lookups */
#define KPSE_DEBUG_FOPEN 2              /* fopen/fclose cal ls */
/* etc */



Oui, ce sont clairement des expression constantes. On aurait pu
faire :

enum
{
KPSE_DEBUG_STAT = 0, /* stat calls */
KPSE_DEBUG_HASH = 1, /* hash lookups */
KPSE_DEBUG_FOPEN = 2, /* fopen/fclose calls */
KPSE_DEBUG_DUMMY
};

ou


enum
{
KPSE_DEBUG_STAT, /* stat calls */
KPSE_DEBUG_HASH, /* hash lookups */
KPSE_DEBUG_FOPEN, /* fopen/fclose calls */
KPSE_DEBUG_NB
};

si on cherche simplement à avoir des valeurs différentes à partir de
0.

J'ai aussi vu des exemples comme

/* erreurs.h */
extern char * err_malloc;
extern char * err_realloc;
/* etc */

/* erreur.c */
#include "erreurs.h"
const char * err_malloc = "erreur d'allocation mémoire";
const char * err_realloc = "erreur d'extension mémoire";
/* etc */



Déjà, c'est pas clair. Ca devrait être

/* erreurs.h */
extern char const * err_malloc;
extern char const * err_realloc;

Est-ce que ça dépend du type (entier, chaîne) de la constante ? Est -ce
que ça fait une différence technique ou bien est-ce une question de
style ?



Comme son nom ne l'indique pas, 'const' a le sens de 'readonly' . En
effet il signifie clairement 'variable à lecture seule'. C'est le cas
des chaines de caractères qui ne peuvent être modifiées. Il ne
signifie pas 'expression constante'.