[英]Setting extra bits in a bool makes it true and false at the same time
如果我得到一个bool
变量并将其第二位设置为1,则变量同时计算为true和false。 使用带-g
选项的gcc6.3编译以下代码( gcc-v6.3.0/Linux/RHEL6.0-2016-x86_64/bin/g++ -g main.cpp -o mytest_d
)并运行可执行文件。 你得到以下。
T如何同时等于真和假?
value bits
----- ----
T: 1 0001
after bit change
T: 3 0011
T is true
T is false
当您使用不同语言(例如fortran)调用函数时,可能会发生这种情况,其中true和false定义与C ++不同。 对于fortran,如果任何位不为0,则该值为true,如果所有位均为零,则该值为false。
#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;
}
}
/////////////////////////////////// //使用ifort编译时与C ++不兼容的Fortran函数。
logical*1 function return_true()
implicit none
return_true = 1;
end function return_true
在C ++中, bool
的位表示(甚至大小)是实现定义的; 通常它被实现为char
-sized类型,取1或0作为可能的值。
如果将其值设置为与允许值不同的任何值(在此特定情况下,通过char
将bool
别名化并修改其位表示),则会破坏语言规则,因此任何事情都可能发生。 特别是,在标准中明确规定,“破坏”的bool
可能同时表现为true
和false
(或既不是true
也不是false
):
以本国际标准描述的方式将
bool
值用作“未定义”,例如通过检查未初始化的自动对象的值,可能会使其表现为既不是true
也不是false
(C ++ 11,[basic.fundamental],注47)
在这种特殊情况下, 你可以看到它在这种奇怪的情况下是如何结束的 :第一个if
被编译到
movzx eax, BYTE PTR [rbp-33]
test al, al
je .L22
它在eax
中加载T
(零扩展),如果全部为零,则跳过打印; 相反,下一个是
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
测试if(T == false)
被转换为if(T^1)
,它只翻转低位。 这对于有效的bool
来说是好的,但是对于你的“破碎”它来说它不会削减它。
请注意,这个奇怪的序列仅在低优化级别生成; 在较高级别,这通常会归结为零/非零检查,并且像您这样的序列可能会成为单个测试/条件分支 。 无论如何,在其他情况下你会得到奇怪的行为,例如将bool
值与其他整数相加时:
int foo(bool b, int i) {
return i + b;
}
foo(bool, int):
movzx edi, dil
lea eax, [rdi+rsi]
ret
其中dil
被“信任”为0/1。
如果你的程序都是C ++,那么解决方案很简单:不要以这种方式破坏bool
值,避免弄乱它们的位表示,一切都会顺利; 特别是,即使你从一个整数分配给一个bool
,编译器也会发出必要的代码以确保结果值是一个有效的bool
,所以你的bool T = 3
确实是安全的,而T
最终会得到一个true
在它的胆量。
相反,如果你需要与其他语言编写的代码进行互操作,这些代码可能不同于bool
的相同概念,只需避免bool
代表“边界”代码,并将其编组为适当大小的整数。 它将在条件和合作。 同样好。
免责声明我所知道的Fortran就是我今天早上在标准文档上看到的内容,而且我有一些用Fortran列表打孔的卡片,我用作书签,所以请放轻松。
首先,这种语言互操作性的东西不是语言标准的一部分,而是ABI平台的一部分。 在我们讨论Linux x86-64时,相关文档是System V x86-64 ABI 。
首先,没有指定C _Bool
类型(在3.1.2注意†中定义为与C ++ bool
相同)与Fortran LOGICAL
有任何兼容性; 特别是在9.2.2表9.2中指定将“plain” LOGICAL
映射到signed int
。 关于TYPE*N
类型,它说
了“
TYPE*N
N ”表示法指定了变量或类型的骨料成员TYPE
应占据N
存储的字节。
(同上)
没有为LOGICAL*1
明确指定的等效类型,这是可以理解的:它甚至不是标准的; 事实上,如果您尝试在Fortran 95兼容模式下编译包含LOGICAL*1
的Fortran程序,您会收到有关它的警告
./example.f90(2): warning #6916: Fortran 95 does not allow this length specification. [1]
logical*1, intent(in) :: x
------------^
并且由gfort
./example.f90:2:13:
logical*1, intent(in) :: x
1
Error: GNU Extension: Nonstandard type declaration LOGICAL*1 at (1)
所以水已经糊里糊涂了; 所以,结合上面的两个规则,我会选择signed char
是安全的。
但是 :ABI还指定:
LOGICAL
类型的值为.TRUE.
实现为1和.FALSE.
实现为0。
所以,如果你有一个程序在LOGICAL
值中存储除1和0之外的任何东西, 那么你已经超出了Fortran方面的规范 ! 你说:
fortran
logical*1
具有与bool
相同的表示,但是如果位是00000011则在fortran中是true
,在C ++中它是未定义的。
最后的陈述不正确,Fortran标准是表示不可知的,而ABI明确地说相反。 事实上,通过检查gfort的输出以进行LOGICAL
比较,您可以轻松地看到这一点:
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
变
logical_compare_:
mov eax, DWORD PTR [rsi]
mov edx, 24
cmp DWORD PTR [rdi], eax
mov eax, 12
cmovne eax, edx
ret
您会注意到两个值之间存在直接的cmp
,而不是先将它们标准化(与ifort
不同,在这方面更为保守)。
更有趣的是:无论ABI说什么,ifort默认使用LOGICAL
的非标准表示; 这在-fpscomp logicals
交换机文档中进行了解释,该文档还指定了有关LOGICAL
和跨语言兼容性的一些有趣细节:
指定具有非零值的整数被视为true,具有零值的整数被视为false。 文字常量.TRUE。 整数值为1,文字常量为FALSE。 整数值为0.此表示形式由版本8.0之前的英特尔Fortran版本和Fortran PowerStation使用。
默认值为
fpscomp nologicals
,它指定奇数值(低位1)被视为true,偶数整数值(低位0)被视为false。文字常量.TRUE。 整数值为-1,文字常量为.FALSE。 整数值为0. Compaq Visual Fortran使用此表示形式。 Fortran标准未指定LOGICAL值的内部表示。 在LOGICAL上下文中使用整数值或将LOGICAL值传递给用其他语言编写的过程的程序是不可移植的,可能无法正确执行。 英特尔建议您避免使用依赖于LOGICAL值内部表示的编码实践。
(重点补充)
现在, LOGICAL
的内部表示通常不应该成为问题,因为从我收集的内容来看,如果你按照规则进行游戏并且不跨越语言边界,你就不会注意到。 对于符合标准的程序, INTEGER
和LOGICAL
之间没有“直接转换”; 我认为你可以将INTEGER
推入LOGICAL
的唯一方法似乎是TRANSFER
,它本质上是不可移植的,没有真正的保证,或者在分配时没有非标准的INTEGER
< - > LOGICAL
转换。
后者是记录由gfort到总是导致非零- > .TRUE.
,零 - > .FALSE.
, 你可以看到 ,在所有情况下生成的代码都是为了实现这一点(即使在带有遗留表示的ifort的情况下它是复杂的代码),所以你似乎无法以这种方式将任意整数推送到LOGICAL
中。
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
LOGICAL*1
的反向转换是直的整数零扩展(gfort),因此,为了遵守上面链接的文档中的合同,显然期望LOGICAL
值为0或1。
但总的来说,这些转换的情况有点 混乱 ,所以我只是远离它们。
所以,长话短说:避免将INTEGER
数据放入LOGICAL
值,因为即使在Fortran中它也很糟糕,并确保使用正确的编译器标志来获得布尔值的ABI兼容表示,并且与C / C ++的互操作性应该没问题。 但为了更安全,我只是在C ++方面使用plain char
。
当您违反与语言和编译器的合同时会发生这种情况。
你可能听说过“零是假的”,“非零是真的”。 当你坚持语言的参数,静态地将int
转换为bool
或反之亦然时,这就成立了。
当你开始搞乱位表示时它不成立。 在这种情况下,您违反合同,并进入(至少)实现定义的行为领域。
根本不要那样做。
这不取决于bool
如何存储在内存中。 这取决于编译器。 如果要更改bool
的值,请指定true
/ false
,或者指定一个整数并使用C ++提供的正确转换机制。
C ++标准实际上给出了一个特定的调用,告诉我们如何以这种方式使用bool
是顽皮的,坏的和邪恶的( “使用bool
值以本文档描述的方式为'undefined',例如通过检查一个值未初始化的自动对象,可能会使它表现得好像既不是true
也不是false
。“ ),尽管出于编辑原因它已在C ++ 20中被删除 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.