Comment undefined sont __builtin_ctz(0) ou __builtin_clozapine(0)?
Fond
Pendant une longue période, gcc a été la fourniture de un certain nombre de builtin peu-se tourner les fonctions, en particulier le nombre de fuite et de 0 bits (également pour long unsigned
et long long unsigned
, qui ont des suffixes l
et ll
):
Intégré— Fonction:
int __builtin_clz (unsigned int x)
Renvoie la
nombre de 0 bits dansx
, en commençant par le bit de poids fort
position. Six
est 0, le résultat est indéfini.Intégré— Fonction:
int __builtin_ctz (unsigned int x)
Renvoie la
nombre de de fuite 0-bits dansx
, en commençant par le bit le moins significatif
position. Six
est 0, le résultat est indéfini.
Sur chaque ligne (avertissement: seuls x64) compilateur que j'ai testé, cependant, le résultat a été que les deux clz(0)
et ctz(0)
retourne le nombre de bits de la sous-jacentes builtin type, comme par exemple
#include <iostream>
#include <limits>
int main()
{
//prints 32 32 32 on most systems
std::cout << std::numeric_limits<unsigned>::digits << " " << __builtin_ctz(0) << " " << __builtin_clz(0);
}
Tentative de contournement
La dernière Clang SVN trunk dans std=c++1y
mode a fait toutes ces fonctions détendu C++14 constexpr
, ce qui fait d'eux des candidats à utiliser dans un SFINAE l'expression d'une fonction wrapper modèle autour de 3 ctz
/clz
les builtins pour unsigned
, unsigned long
, et unsigned long long
template<class T> //wrapper class specialized for u, ul, ull (not shown)
constexpr int ctznz(T x) { return wrapper_class_around_builtin_ctz<T>()(x); }
//overload for platforms where ctznz returns size of underlying type
template<class T>
constexpr auto ctz(T x)
-> typename std::enable_if<ctznz(0) == std::numeric_limits<T>::digits, int>::type
{ return ctznz(x); }
//overload for platforms where ctznz does something else
template<class T>
constexpr auto ctz(T x)
-> typename std::enable_if<ctznz(0) != std::numeric_limits<T>::digits, int>::type
{ return x ? ctznz(x) : std::numeric_limits<T>::digits; }
Le gain de ce hack est que les plates-formes qui donnent le résultat requis pour ctz(0)
pouvez omettre un supplément de conditionnel pour tester x==0
(qui pourrait sembler un micro-optimisation, mais lorsque vous êtes déjà au niveau du groupe builtin peu-se tourner les fonctions, il peut faire une grande différence)
Questions
Comment undefined est la famille de fonctions internes clz(0)
et ctz(0)
?
- peuvent-ils jeter un
std::invalid_argument
exception? - pour x64, seront-ils de l'actuel gcc distribution de retour de la taille de la underyling type?
- sont les ARM/x86 plates-formes différentes (je n'ai pas accès à tester ceux-ci)?
- est au-dessus de la SFINAE truc bien définie de façon à séparer ces plates-formes?
- Si vous pouvez obtenir vos mains sur le fichier
longlong.h
dans gcc/gmp/glibc, regardez pour la macro COUNT_LEADING_ZEROS_0...
Vous devez vous connecter pour publier un commentaire.
Malheureusement, même x86-64 implémentations peuvent différer - à partir d'Intel l'instruction de référence,
BSF
etBSR
, avec une source et l'opérande de la valeur de(0)
, les feuilles de la destination undefined, et définit laZF
(indicateur de zéro). De sorte que le comportement ne peut être cohérente entre les micro-architectures ou, disons, AMD et Intel. (Je crois que AMD feuilles de destination sans modification.)Le plus récent
LZCNT
etTZCNT
les instructions ne sont pas omniprésents. Les deux sont présents qu'à compter de l'architecture Haswell (Intel).ctz(0)
appel est déterministe et donne toujours la même réponse sur cette plate-forme (c'est à dire pas un comportement indéfini) de sorte que mes SFINAE hack réellement un sens?bsr
/bsf
avec un zéro à la source que de ne pas modifier la destination, chaque processeur Intel, je n'ai jamais été en mesure de tester (ou entendu parler) le fait aussi que. Intel n'a tout simplement pas de document de la sorte.La raison pour laquelle la valeur est undefined, c'est qu'il permet au compilateur d'utiliser des instructions du processeur pour lequel le résultat est indéfini, lorsque ces instructions sont le moyen le plus rapide pour obtenir une réponse.
Mais il est important de comprendre que les résultats sont non seulement pas défini; ils sont undeterministic. Il est valide, étant donné Intel instruction de référence, pour l'instruction de retour la bas 7 bits de l'heure actuelle, par exemple.
Et là où ça devient intéressant/dangereux: le compilateur écrivain peut prendre avantage de cette situation, à produire de plus petites code. Considérer cette non-template-spécialisation de la version de votre code:
Cela fonctionne bien sur un processeur/compilateur qui ont décidé de retourner #bits pour ctznz(0). Mais l'onu d'un processeur/compilateur qui décident de retourner pseudo-aléatoire des valeurs, le compilateur peut décider "j'ai le droit de retourner tout ce que je veux pour ctznz(0), et le code est plus petit si je retourne #bits, donc je vais". Ensuite, le code finit par appeler ctznz tout le temps, même si elle produit la mauvaise réponse.
Pour le dire d'une autre façon: le compilateur est pas défini les résultats ne sont pas garantis pas défini de la même manière que l'exécution du programme, des résultats indéfinis sont.
Il n'y a vraiment aucun moyen de contourner cela. Si vous devez utiliser __builtin_clozapine, avec un opérande source qui pourrait être de zéro, vous devez ajouter la vérification, tout le temps.