Si j'obtiens une variable bool
et que je mets son deuxième bit à 1, alors la variable est évaluée à vrai et faux en même temps. Compilez le code suivant avec gcc6.3 avec -g
option, (gcc-v6.3.0/Linux/RHEL6.0-2016-x86_64/bin/g++ -g main.cpp -o mytest_d
) et exécutez l'exécutable. Vous obtenez ce qui suit.
Comment T peut-il être à la fois vrai et faux?
value bits
----- ----
T: 1 0001
after bit change
T: 3 0011
T is true
T is false
Cela peut se produire lorsque vous appelez une fonction dans un langage différent (par exemple fortran) où la définition vraie et fausse est différente de C++. Pour fortran, si des bits ne sont pas 0, la valeur est vraie, si tous les bits sont nuls, la valeur est fausse.
#include <iostream>
#include <bitset>
using namespace std;
void set_bits_to_1(void* val){
char *x = static_cast<char *>(val);
for (int i = 0; i<2; i++ ){
*x |= (1UL << i);
}
}
int main(int argc,char *argv[])
{
bool T = 3;
cout <<" value bits " <<endl;
cout <<" ----- ---- " <<endl;
cout <<" T: "<< T <<" "<< bitset<4>(T)<<endl;
set_bits_to_1(&T);
bitset<4> bit_T = bitset<4>(T);
cout <<"after bit change"<<endl;
cout <<" T: "<< T <<" "<< bit_T<<endl;
if (T ){
cout <<"T is true" <<endl;
}
if ( T == false){
cout <<"T is false" <<endl;
}
}
/////////////////////////////////// // Fonction Fortran qui n'est pas compatible avec C++ lorsqu'elle est compilée avec ifort.
logical*1 function return_true()
implicit none
return_true = 1;
end function return_true
En C++, la représentation binaire (et même la taille) d'un bool
est définie par l'implémentation; il est généralement implémenté comme un type de taille char
prenant 1 ou 0 comme valeurs possibles.
Si vous définissez sa valeur sur quelque chose de différent de ceux autorisés (dans ce cas spécifique en aliasant un bool
par un char
et en modifiant sa représentation binaire), vous enfreignez les règles du langage, donc tout peut arriver. En particulier, il est explicitement spécifié dans la norme qu'un bool
"cassé" peut se comporter à la fois comme true
et false
(ou ni true
ni false
) en même temps:
L'utilisation d'une valeur
bool
de la manière décrite par la présente Norme internationale comme "non définie", par exemple en examinant la valeur d'un objet automatique non initialisé, pourrait le faire se comporter comme s'il n'était nitrue
nifalse
(C++ 11, [basic.fundamental], note 47)
Dans ce cas particulier, vous pouvez voir comment cela a abouti dans cette situation bizarre : le premier if
est compilé en
movzx eax, BYTE PTR [rbp-33]
test al, al
je .L22
qui charge T
dans eax
(avec l'extension zéro), et ignore l'impression si tout est nul; le prochain si est plutôt
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
Le test if(T == false)
est transformé en if(T^1)
, qui retourne juste le bit bas. Ce serait correct pour un bool
valide, mais pour votre "cassé", il ne le coupe pas.
Notez que cette séquence bizarre n'est générée qu'à de faibles niveaux d'optimisation; à des niveaux plus élevés, cela se résume généralement à un contrôle zéro/non nul, et une séquence comme la vôtre est susceptible de devenir ne seule branche test/conditionnelle . Vous obtiendrez de toute façon un comportement bizarre dans d'autres contextes, par exemple lors de l'addition des valeurs de bool
à d'autres entiers:
int foo(bool b, int i) {
return i + b;
}
foo(bool, int):
movzx edi, dil
lea eax, [rdi+rsi]
ret
où dil
est "de confiance" pour être 0/1.
Si votre programme est entièrement en C++, alors la solution est simple: ne cassez pas les valeurs de bool
de cette façon, évitez de jouer avec leur représentation binaire et tout ira bien; en particulier, même si vous affectez un entier à un bool
le compilateur émettra le code nécessaire pour vous assurer que la valeur résultante est un bool
valide, donc votre bool T = 3
est en effet sûr, et T
se retrouvera avec un true
dans ses tripes.
Si, au lieu de cela, vous devez interagir avec du code écrit dans d'autres langages qui ne partagent pas la même idée de ce qu'est un bool
, évitez simplement bool
pour le code "limite" et faites-en un marshalage approprié. entier de taille. Cela fonctionnera dans les conditionnels & co. tout aussi bien.
Clause de non-responsabilité tout ce que je sais de Fortran, c'est ce que j'ai lu ce matin sur des documents standard, et que j'ai des cartes perforées avec des listes Fortran que j'utilise comme signets, alors allez-y doucement avec moi.
Tout d'abord, ce genre de choses d'interopérabilité linguistique ne fait pas partie des normes linguistiques, mais de la plate-forme ABI. Comme nous parlons de Linux x86-64, le document pertinent est le System V x86-64 ABI .
Tout d'abord, il n'est spécifié nulle part que le type C _Bool
(Qui est défini comme étant identique à C++ bool
à la note 3.1.2 †) a une compatibilité avec Fortran LOGICAL
; en particulier, au 9.2.2, le tableau 9.2 spécifie que "plain" LOGICAL
est mappé sur signed int
. À propos des types TYPE*N
, Il est indiqué que
La notation "
TYPE*N
" Spécifie que les variables ou les agrégats de typeTYPE
doivent occuperN
octets de stockage.
(ibid.)
Il n'y a pas de type équivalent explicitement spécifié pour LOGICAL*1
, Et c'est compréhensible: ce n'est même pas standard; en effet, si vous essayez de compiler un programme Fortran contenant un LOGICAL*1
en mode compatible Fortran 95, vous obtenez des avertissements à ce sujet, à la fois par ifort
./example.f90(2): warning #6916: Fortran 95 does not allow this length specification. [1]
logical*1, intent(in) :: x
------------^
et par gfort
./example.f90:2:13:
logical*1, intent(in) :: x
1
Error: GNU Extension: Nonstandard type declaration LOGICAL*1 at (1)
donc les eaux sont déjà brouillées; donc, en combinant les deux règles ci-dessus, j'irais pour signed char
pour être sûr.
Cependant: l'ABI précise également:
Les valeurs pour le type
LOGICAL
sont.TRUE.
Implémentées comme 1 et.FALSE.
Implémentées comme 0.
Donc, si vous avez un programme qui stocke autre chose que 1 et 0 dans une valeur LOGICAL
, vous êtes déjà hors spécifications du côté Fortran! Vous dites:
Un fortran
logical*1
A la même représentation quebool
, mais dans fortran si les bits sont 00000011 c'esttrue
, en C++ il n'est pas défini.
Cette dernière affirmation n'est pas vraie, la norme Fortran est indépendante de la représentation et l'ABI dit explicitement le contraire. En effet, vous pouvez voir cela en action facilement en en vérifiant la sortie de gfort pour LOGICAL
comparaison :
integer function logical_compare(x, y)
logical, intent(in) :: x
logical, intent(in) :: y
if (x .eqv. y) then
logical_compare = 12
else
logical_compare = 24
end if
end function logical_compare
devient
logical_compare_:
mov eax, DWORD PTR [rsi]
mov edx, 24
cmp DWORD PTR [rdi], eax
mov eax, 12
cmovne eax, edx
ret
Vous remarquerez qu'il y a un cmp
droit entre les deux valeurs, sans les normaliser d'abord (contrairement à ifort
, qui est plus conservateur à cet égard).
Encore plus intéressant: indépendamment de ce que dit l'ABI, ifort utilise par défaut une représentation non standard pour LOGICAL
; ceci est expliqué dans la documentation du commutateur -fpscomp logicals
, qui spécifie également des détails intéressants sur LOGICAL
et la compatibilité entre les langues:
Spécifie que les entiers avec une valeur non nulle sont traités comme vrais, les entiers avec une valeur zéro sont traités comme faux. La constante littérale .TRUE. a une valeur entière de 1 et la constante littérale .FALSE. a une valeur entière de 0. Cette représentation est utilisée par les versions d'Intel Fortran avant la version 8.0 et par Fortran PowerStation.
La valeur par défaut est
fpscomp nologicals
, Qui spécifie que les valeurs entières impaires (bit faible un) sont traitées comme vraies et les valeurs entières paires (bit faible zéro) sont traitées comme fausses.La constante littérale .TRUE. a une valeur entière de -1 et la constante littérale .FALSE. a une valeur entière de 0. Cette représentation est utilisée par Compaq Visual Fortran. La représentation interne des valeurs LOGIQUES n'est pas spécifiée par la norme Fortran. Les programmes qui utilisent des valeurs entières dans des contextes LOGICAL, ou qui transmettent des valeurs LOGICAL à des procédures écrites dans d'autres langages, ne sont pas portables et peuvent ne pas s'exécuter correctement. Intel vous recommande d'éviter les pratiques de codage qui dépendent de la représentation interne des valeurs LOGIQUES.
(pas d'italique dans l'original)
Maintenant, la représentation interne d'un LOGICAL
ne devrait normalement pas poser de problème, car, d'après ce que je comprends, si vous jouez "selon les règles" et ne franchissez pas les frontières linguistiques, vous n'allez pas le remarquer. Pour un programme conforme standard, il n'y a pas de "conversion directe" entre INTEGER
et LOGICAL
; la seule façon dont je vois que vous pouvez pousser un INTEGER
dans un LOGICAL
semble être TRANSFER
, qui est intrinsèquement non portable et ne donne aucune garantie réelle, ou le non standard INTEGER
<-> LOGICAL
conversion lors de l'affectation.
Ce dernier est documenté par gfort pour toujours avoir pour résultat différent de zéro -> .TRUE.
, Zéro -> .FALSE.
, Et vous pouvez voir que dans tous les cas, du code est généré pour que cela se produise (même s'il s'agit d'un code alambiqué en cas d'ifort avec la représentation héritée), vous ne pouvez donc pas sembler pousser un entier arbitraire dans un LOGICAL
de cette manière.
logical*1 function integer_to_logical(x)
integer, intent(in) :: x
integer_to_logical = x
return
end function integer_to_logical
integer_to_logical_:
mov eax, DWORD PTR [rdi]
test eax, eax
setne al
ret
La conversion inverse pour un LOGICAL*1
Est une extension zéro entière simple (gfort), donc, pour respecter le contrat dans la documentation liée ci-dessus, il est clair que la valeur LOGICAL
sera 0 ou 1.
Mais en général, la situation pour ces conversions est n pe de n gâchis , donc je resterais loin d'eux.
Donc, pour faire court: évitez de mettre des données INTEGER
dans des valeurs LOGICAL
, car elles sont mauvaises même dans Fortran, et assurez-vous d'utiliser le bon indicateur de compilation pour obtenir la représentation conforme ABI pour les booléens et l'interopérabilité avec C/C++ devrait être correcte. Mais pour plus de sécurité, j'utiliserais simplement char
du côté C++.
Enfin, d'après ce que je comprends de la documentation , dans ifort, il existe un support intégré pour l'interopérabilité avec C, y compris les booléens; vous pouvez essayer d'en tirer parti.
C'est ce qui se produit lorsque vous violez votre contrat avec la langue et le compilateur.
Vous avez probablement entendu quelque part que "zéro est faux" et "non nul est vrai". Cela vaut lorsque vous vous en tenez aux paramètres du langage, convertissant statiquement un int
en bool
ou vice versa.
Il ne tient pas lorsque vous commencez à jouer avec les représentations binaires. Dans ce cas, vous rompez votre contrat et entrez dans le domaine (au moins) du comportement défini par l'implémentation.
Ne faites pas ça.
Ce n'est pas à vous de savoir comment un bool
est stocké en mémoire. C'est au compilateur. Si vous souhaitez modifier la valeur d'un bool
, affectez true
/false
, ou affectez un entier et utilisez les mécanismes de conversion appropriés fournis par C++.
Le standard C++ utilisé pour donner un rappel spécifique sur la façon dont l'utilisation de bool
de cette manière est vilain, mauvais et mauvais ( "Utilisation d'une valeur bool
d'une manière décrite par ce document comme 'non définie', comme en examinant la valeur d'un objet automatique non initialisé, pourrait le faire se comporter comme s'il n'était ni true
ni false
. "), même si c'était supprimé en C++ 20 pour des raisons éditoriales .