(void)
?La première vient de l'écriture du code lui-même, les entrées/sorties, les allocations dynamiques, de nombreux appels de fonctions, des boucles, etc. Le programmeur a généralement peu intérêt à modifier son code, tout au plus pourra-t-il remplacer les petites fonctions le plus souvent appelées par des macros et tenter de limiter les boucles. On pourra aussi améliorer les entrées/sorties et les allocations si c'est possible. Il reste enfin les options de compilation sur lesquelles on peut jouer.
L'autre raison vient de la complexité théorique des algorithmes utilisés. Dans ce dernier cas, il faut chercher un meilleur algorithme. Cet aspect est développé par exemple dans www.enseignement.polytechnique.fr/profs/informatique/Jean-Jacques.Levy/poly/polyx-cori-levy.ps.gz
Il existe des outils de profilage de programmes. Il s'agit de compiler les sources avec une bibliothèque puis de lancer le programme. On lance alors un lociciel associé à la bibliothèque. Le résultat est un fichier où est détaillé le temps passé dans chaque fonction, le nombre d'appels, etc. Sur Unix-like, le projet GNU propose gprof (cf. 4.6).
Rappelons tout de même que la vitesse d'exécution d'un programme (hors problèmes d'algorithmique) est peu souvent critique, et qu'il est bien plus important de fournir un code lisible.
En fait un byte correspond à un caractère non-signé
(unsigned char
), lequel peut prendre plus (ou moins) de 8
bits. En principe, en français, on parle dans ce cas de
multiplet (et peut-être bientôt de
codet) comme traduction officielle de
byte dans ce sens.
(Merci à Antoine Leca).
setjmp()
/longjmp()
.
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
jmp_buf env;
long fact(long x) {
long i, n;
if (x < 0)
longjmp(env, 1);
for (n = 1, i = 2; i <= x; i ++)
n *= i;
return n;
}
long comb(long k, long n) {
if (k < 0 || n < 1 || k > n)
longjmp(env, 2);
return fact(n) / (fact(k) * fact(n - k));
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "pas assez d'arguments\n");
return EXIT_FAILURE;
}
if (setjmp(env)) {
fprintf(stderr, "erreur de calcul\n");
return EXIT_FAILURE;
}
printf("%ld\n", comb(strtol(argv[1], 0, 0),
strtol(argv[2], 0, 0)));
return 0;
}
Voilà un programme qui calcule le coefficient binomial des deux
arguments ; main()
appelle comb()
qui
appelle fact()
.
Ces fonctions vérifient un peu les arguments, et on voudrait
renvoyer une erreur en cas de problème ; mais :
setjmp()
sauvegarde l'état du programme au moment de
l'appel, et renvoie 0
.
longjmp()
remplace le contenu de la pile d'exécution
par la sauvegarde, et le programme se trouve à nouveau à l'endroit
de l'appel à setjmp()
. Celle-ci renvoie alors une
valeur passée en paramètre de longjmp()
(dans
l'exemple, 1
pour une erreur dans fact()
et
2
pour une erreur dans comb()
).
La méthode présentée ici est assez rustique. Il existe des mécanismes de POO5 en C bien plus évolués. Vous pouvez à ce sujet allez voir l'excellent document : ldeniau.home.cern.ch/ldeniau/html/oopc/oopc.html . Allez voir aussi le document suivant : cern.ch/Laurent.Deniau/html/exception/exception.html . Voir aussi la question 3.10.
#define STR_(a) #a
#define STR(a) STR_(a)
#define VERSION 4
printf("This is version " STR(VERSION) " of the program\n");
En effet, quelque chose comme :
#define STR(a) #a
#define VERSION a
printf("This is version " STR(VERSION) " of the program\n");
fait intervenir la concaténation des chaînes trop tôt ce qui fait que le résultat de cette dernière séquence renvoie :
This is version VERSION of the program
Les vrais identificateurs réservés sont :
if
, for
,
switch
, long
, ...
Ensuite, il y a les headers standards et la bibliothèque. Les headers sont libres d'utiliser des identificateurs commençant par un `_' et suivis d'une lettre minuscule, comme `_liste', mais c'est pour définir quelque chose qui a un « file scope », c'est-à-dire une portée globale à la « translation unit » (le fichier source C et les fichiers qu'il inclut). Donc, globalement, on ne doit pas s'en servir pour définir quelque chose qui a ce « file scope ».
Ça veut dire quoi ? Que les choses suivantes sont interdites :
typedef int _foo;
struct _bar {
int x;
char * y;
};
void _f(void);
En revanche, les choses suivantes sont autorisées :
void f(int _x[]) {
typedef int _foo;
struct _bar {
int x;
char *y;
};
extern void _g(void);
}
struct qux {
long _truc;
};
J'attire l'attention du public ébahi sur les quatre points suivants :
_foo
, toute utilisation d'une
facilité fournie par un header peut déclencher un « undefined
behaviour » (par exemple, l'ordinateur utilise spontanément son
modem pour téléphoner à la belle-mère du programmeur et l'inviter
à venir dîner à la maison).
extern void _g(void);
explicite le fait que la
restriction est sur le scope et pas le linkage. Bien entendu, il
faut que la fonction _g()
soit définie quelque part, avec
un external linkage, ce qui ne peut pas se faire en C
standard. Donc cette déclaration, quoique valide, doit avoir un
pendant dans une autre translation unit, qui ne peut pas être
fabriqué de façon standard. Pour compléter, rajoutons que la
définition n'a besoin d'exister que si on se sert effectivement de
la fonction. Donc on est en fait autorisé à faire une déclaration
inutile qui pourrait faire planter des implémentations non
conformes mais courantes. How useful.
<ctype.h>
peut déclarer n'importe quel
identificateur qui commence par "is" ou "to" suivi d'une lettre
minuscule. Ces identificateurs sont réservés pour ce qui est de
l'external linkage, ce qui veut dire que même si on n'inclut pas
<ctype.h>
, on ne doit pas définir, entre autres, de
variable globale "iszoinx" qui ne soit pas protégé par le mot clé
static
.
(void)
?(void)
.
La première utilisation est pour indiquer explicitement au
compilateur qu'une valeur est ignorée, comme au retour d'une
fonction.
Par exemple, il arrive souvent d'utiliser la fonction
printf()
sans utiliser ni même tester la valeur de
retour.
Ecrire l'appel à printf()
ainsi
(void)printf("%s\n", "Un message à la con") ;
est une manière de dire au compilateur, et aux lecteurs du code,
que je sais que printf()
renvoie une valeur, mais que
j'ai décidé de l'ignorer. Cela peut être utile pour des
utilitaires de vérification de code, comme lint.
La seconde utilisation est dans des définitions de macro. Voici un exemple :
#undef the_truc
#ifdef __TRUC__DEPENDANT__
# define the_truc(a) ((void)0)
#else
# define the_truc(a) /* du code */
#endif
Ainsi, the_truc(a)
est utilisable là ou une expression est
requise, comme ici :
i = (the_truc(a), 5) ;
Avec une définition de the_truc
comme ceci,
# define the_truc(a)
il y aurait une erreur à la compilation.
faq-fclc 5/3/2002 (8h 59:05)