Je reviens un peu sur cette fameuse fonction à optimiser ou à ne pas optimiser.
- Si la fonction est déclarée et documentée dans son .h, en bref si elle n'est pas
static pour les quelques (voire une seule) fonctions qui l'utilisent, il est normal de la sécuriser un minimum dans la tradition de C. A moins éventuellement d'en faire une unsafe_str_cpy(), par exemple.
A noter que la sécurisation en C ne peut pas aller bien loin. Pour ce genre de pointeur on peut juste vérifier qu'il n'est pas à NULL. Donc en gros considérer que le programmeur utilisateur sera assez "tête en l'air" pour ne se conformer à la documentation de la fonction, voire à l'évidence, et assez rigoureux (ou ses outils) pour être certain que tout pointeur non autrement initialisé ou libéré sera à NULL. On va donc très certainement avoir une foultitude de tests de pointeurs à NULL, redondants, mais si c'était un
vrai problème, ça se saurait.
Un petit truc me chagrine quand même: le retour char*. On connaît l'utilité de renvoyer un paramètre pointeur non constant, dans l'écriture plus naturelle du code qu'elle autorise, dans strcat() par exemple. Mais ici, le fait de mettre le retour à NULL quand src OU dest est à NULL rend le machin difficile à utiliser. Ou alors quelque chose m'échappe. Peut-être pourrait-on envisager une enum
1 2 3 4 5
| enum STR_CPY_RETURN {
STR_CPY_SUCESS = 0,
STR_CPY_DSTBAD = 1,
STR_CPY_SRCBAD = 2
}; |
ou autre, dans laquelle on peut lire la cause de l'erreur par masquage.
- Pour ce qui est de l'optimisation, il faut avant tout qu'elle ait un sens. C'est à dire que les nanosecondes gagnées dans le test à NULL soient visibles dans le contexte.
Nota: à mon avis, du code "bêtement trop lent" est le plus souvent "bête" avant d'être "trop lent".
Ici le contexte pourrait être un ou des tableaux de char*,
correctement initialisés à des tailles suffisantes. Ensuite, des centaines ou plus de copies de chaînes, parce qu'on a une bonne raison de swapper les chaînes et non les pointeurs. Plus les chaînes seront nombreuses et courtes, plus l'optimisation se fera sentir.
C'est là qu'on sera tenté par
1 2 3 4
| void str_cpy(char * dest, char * src)
{
while (*dest++ = *src++);
} |
Mais ce sera déjà impérativement:
1 2 3 4
| static void str_cpy(char * dest, char * src)
{
while (*dest++ = *src++);
} |
Mais à ce moment-là, l'appel de fonction, s'il n'est pas optimisé par le compilateur, deviendra plus coûteux qu'un test à NULL une fois dans la fonction. Autant le suggérer au compilateur, suggestion valant commentaire à la relecture du code:
1 2 3 4
| static inline void str_cpy(char * dest, char * src)
{
while (*dest++ = *src++);
} |
Notez que pour faire taire un warning "mérité" et rendre votre code lisible, sans changer le code généré (quelques microsecondes de plus pour la compilation, vous devriez au moins faire:
1 2 3 4
| static inline void _str_cpy(char* dest, const char* src)
{
while ((*dest++ = *src++) != '\0');
} |
Mais maintenant, à quoi sert la fonction par rapport à une macro, ou tout simplement un copié-collé du while() ? D'autant qu'alors ce code apparaîtrait directement dans le bloc où les pointeurs auront déjà été validés.
Une voie d'optimisation serait, en fonction du contexte, de copier un nombre fixe de char, c'est à dire le plus souvent "un peu trop". Mais ça peut aller bien plus vite, dans certains cas, en particulier si ce nombre peut être connu avant la compilation. Et pourquoi pas multiple de sizeof(int).
Partager