2017-01-19 2 views
12

Accidentellement, je l'ai écrit l'extrait suivant intéressant:comportement bizarre avec une manuelle programmée strlen

#include <iostream> 
#include <cstring> 

size_t strlen(const char* str) { 
    std::cout << "hello"; 
    return 0; 
} 

int main() { 
    return std::strlen("sdf"); 
} 

De façon inattendue pour moi, la sortie est « bonjour » dans GCC 5.1, ce qui signifie que mon strlen est appelé. Encore plus intéressant, si je supprime le return, c'est-à-dire remplacez principal avec juste un appel de std::strlen("sdf");, rien ne s'imprime!

J'ai aussi essayé Clang, pour lequel std::strlen appelle la fonction réelle qui calcule la longueur de la chaîne (et rien ne s'imprime). C'est ce que je m'attendais à voir.

Comment cela peut-il être expliqué? La définition de ma propre fonction strlen est-elle considérée comme un comportement non défini?

+3

Voir [noms réservés] et [depr.c.headers]. –

+0

Sur ma machine, votre exemple se sépare. (linux, gcc-version 6.2.1 20160830) Je ne m'attendais pas à ça. Je m'attendais à ce que le code imprime "bonjour" et quitte avec le code 0 au système d'exploitation. – DusteD

+0

Je reçois aussi un segfault avec votre exemple (gcc 5.4). Je suggérerais qu'il est extrêmement inhabituel et «risqué» de fournir une fonction 'strlen' dans l'espace de noms global, car cela surchargerait la version libc (avec le lien« C ») si vous avez inclus' 'd'abord, puisque naturellement outrepassent 'std :: strlen', qui peut être utilisé ailleurs dans la bibliothèque. Par exemple. 'std :: cout' semble générer un appel à' strlen'. – davmac

Répondre

12

Il n'y a rien d'intéressant ici, juste une surcharge de fonction et un peu de comportement indéfini. Vous avez surchargé la fonction de bibliothèque strlen() avec votre propre version. Puisque dans l'implémentation GCC de std::strlen est rien mais un appel de fonction de bibliothèque dans l'espace de noms std, vous obtenez le résultat que vous voyez.

Voici l'extrait pertinent de cstring:

namespace std _GLIBCXX_VISIBILITY(default) 
{ 
_GLIBCXX_BEGIN_NAMESPACE_VERSION 

    using ::strlen; 
    ... 

Et lorsque vous retirez la déclaration de retour, GCC permet d'optimiser loin l'appel tout à fait, car il sait que strlen est fonction sans effets secondaires, et il est en fait un nom réservé, qui ne doit pas être surchargé. Je suppose, compilateur pourrait vous donner un avertissement ici, mais hélas, il n'a pas, comme il n'est pas nécessaire de le faire.

+1

Pouvez-vous expliquer le comportement lorsque le «retour» est supprimé? L'appel est-il simplement optimisé (ce que je suppose que gcc peut faire parce que c'est une fonction de bibliothèque standard)? – Kevin

+0

@Kevin, oui, mais je vais élaborer dans la réponse. – SergeyA

+0

Il ne s'agit pas d'une surcharge de fonction. 'std :: strlen' a les paramètres' (char const *) '; une fonction ne peut pas être surchargée avec la même liste de paramètres. –

0

Vous avez défini strlen dans l'espace de noms std par défaut, remplaçant ainsi celui standard.

La raison pour laquelle parfois votre strlen est appelé et parfois le strlen standard est appelé, est probablement lié au fait que de nombreuses implémentations de strlen sont des macros au lieu de fonctions. Il peut même être implémenté en assembleur.

S'il s'agit d'une macro, la macro standard sera exécutée. De même, si vous supprimez le retour, l'appel de fonction peut être supprimé par l'optimiseur. Vous pouvez comparer avec -O0.

4

Selon C++ 14 [extern.names]/3, ::strlen est réservé:

Chaque nom de la bibliothèque standard C déclarée avec une liaison externe est réservée à la mise en œuvre pour être utilisé comme un nom avec lien externe "C", à la fois dans l'espace de noms std et dans l'espace de noms global.

et l'effet d'utiliser un nom réservé, [reserved.names]/2:

Si un programme déclare ou définit un nom dans un contexte où il est réservé, autrement que comme explicitement autorisés par cette clause, son comportement est indéfini.

Votre programme a donc un comportement indéfini.