BY4*_*408 40 c++ evaluation boolean abi undefined-behavior
如果我得到一个bool
变量并将其第二位设置为1,则变量同时评估为true和false。使用带有-g
选项(gcc-v6.3.0/Linux/RHEL6.0-2016-x86_64/bin/g++ -g main.cpp -o mytest_d
)的gcc6.3编译以下代码,然后运行可执行文件。您得到以下内容。
T如何同时等于真和假?
value bits
----- ----
T: 1 0001
after bit change
T: 3 0011
T is true
T is false
Run Code Online (Sandbox Code Playgroud)
当您使用不同的语言(例如fortran)调用函数时,可能会发生这种情况,其中对和错的定义与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;
}
}
Run Code Online (Sandbox Code Playgroud)
//////////////////////////////// //使用ifort编译时,Fortran函数与C ++不兼容。
logical*1 function return_true()
implicit none
return_true = 1;
end function return_true
Run Code Online (Sandbox Code Playgroud)
Mat*_*lia 64
在C ++中,a的位表示(甚至大小)bool
是实现定义的;通常将其实现为char
-size类型,将1或0作为可能的值。
如果将其值设置为与允许值不同的任何值(在此特定情况下,bool
通过对a 进行别名char
并修改其位表示形式),将破坏该语言的规则,因此任何事情都可能发生。特别是,在标准中明确规定,“中断” bool
可能同时表现为true
和false
(或都不true
是false
)。
bool
以本国际标准描述为“未定义”的方式使用值,例如通过检查未初始化的自动对象的值,可能导致其行为既不是也不true
是false
(C ++ 11,[基本],注释47)
在这种特殊情况下,您可以看到它是如何在这种奇怪的情况下结束的:第一个if
被编译为
movzx eax, BYTE PTR [rbp-33]
test al, al
je .L22
Run Code Online (Sandbox Code Playgroud)
其负载T
在eax
(零扩展),并跳过打印,如果这一切都为零; 下一个是
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
Run Code Online (Sandbox Code Playgroud)
测试if(T == false)
转换为if(T^1)
,仅翻转低位。对于有效的服务器来说bool
,这是可以的,但是对于您的“破损”服务器,它并不能解决问题。
注意,这个奇怪的序列仅在低优化级别下生成;在更高级别上,这通常可以归结为零/非零检查,并且像您这样的序列很可能会成为单个测试/条件分支。在其他情况下,无论如何,您都会得到奇怪的行为,例如,将bool
值求和为其他整数时:
int foo(bool b, int i) {
return i + b;
}
Run Code Online (Sandbox Code Playgroud)
foo(bool, int):
movzx edi, dil
lea eax, [rdi+rsi]
ret
Run Code Online (Sandbox Code Playgroud)
这里dil
是“信任”是0/1。
如果您的程序全部是C ++,则解决方案很简单:不要以bool
这种方式破坏值,避免弄乱它们的位表示,一切都会顺利进行;特别是,即使您从整数分配给,bool
编译器也会发出必要的代码,以确保结果值是有效bool
,因此您bool T = 3
的确是安全的,并且T
最终会冒出a true
。
如果相反,如果您需要与用其他语言编写的可能互不相同的代码互操作bool
,请避免bool
使用“边界”代码,并将其编组为适当大小的整数。它将在有条件的条件下工作。一样好。
免责声明我对Fortran的了解是我今天早上在标准文档上读到的,并且我有一些带有Fortran列表的打孔卡,我将它们用作书签,所以请放轻松。
首先,这种语言互操作性不是语言标准的一部分,而是平台ABI的一部分。当我们谈论Linux x86-64时,相关文档是System V x86-64 ABI。
首先,没有任何地方指定C _Bool
类型(bool
在3.1.2注†处定义为与C ++相同)与Fortran兼容LOGICAL
。特别是在9.2.2中,表9.2指定将“普通” LOGICAL
映射到signed int
。关于TYPE*N
类型说
“
TYPE*N
”符号指定类型的变量或聚合成员TYPE
应占用N
存储字节。
(同上)
没有为显式指定等效类型LOGICAL*1
,这是可以理解的:它甚至不是标准的。的确,如果您尝试编译包含LOGICAL*1
符合Fortran 95兼容模式的Fortran程序,则会收到有关该警告的警告,两者均由ifort发出。
./example.f90(2): warning #6916: Fortran 95 does not allow this length specification. [1]
logical*1, intent(in) :: x
------------^
Run Code Online (Sandbox Code Playgroud)
并通过努力
./example.f90:2:13:
logical*1, intent(in) :: x
1
Error: GNU Extension: Nonstandard type declaration LOGICAL*1 at (1)
Run Code Online (Sandbox Code Playgroud)
所以水已经浑浊了;因此,结合以上两个规则,我会signed char
很安全。
但是:ABI还指定:
类型的值
LOGICAL
被.TRUE.
实现为1和.FALSE.
0来实现。
因此,如果您有一个程序可以存储1和0以外的LOGICAL
值,那么您在Fortran方面已经超出规格了!你说:
fortran
logical*1
与相同bool
,但在fortran中,如果位为00000011,则为true
C,在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
Run Code Online (Sandbox Code Playgroud)
变成
logical_compare_:
mov eax, DWORD PTR [rsi]
mov edx, 24
cmp DWORD PTR [rdi], eax
mov eax, 12
cmovne eax, edx
ret
Run Code Online (Sandbox Code Playgroud)
您会注意到,cmp
两个值之间存在一条直线,而无需先对其进行规范化(与相比ifort
,这方面更保守)。
更有趣的是:无论ABI怎么说,默认情况下,ifort都使用非标准表示形式LOGICAL
;-fpscomp logicals
交换机文档中对此进行了解释,该文档还指定了一些有趣的细节LOGICAL
以及跨语言兼容性:
指定将具有非零值的整数视为true,将具有零值的整数视为false。文字常量.TRUE。的整数值为1,文字常量为.FALSE。具有0的整数值。此表示形式由8.0之前的Intel 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
将a 推入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
Run Code Online (Sandbox Code Playgroud)
integer_to_logical_:
mov eax, DWORD PTR [rdi]
test eax, eax
setne al
ret
Run Code Online (Sandbox Code Playgroud)
a的逆向转换LOGICAL*1
是一个整数零扩展(gfort),因此,为了兑现上面链接的文档中的合同,很显然,它的LOGICAL
期望值为0或1。
但在一般情况下,这些转换的情况是有点的一塌糊涂,所以我只希望远离他们。
因此,长话短说:避免将INTEGER
数据放入LOGICAL
值中,因为即使在Fortran中它也很糟糕,并确保使用正确的编译器标志来获取布尔值的ABI兼容表示形式,并且与C / C ++的互操作性应该很好。但是为了更加安全,我只char
在C ++方面使用纯文本。
最后,根据我从文档中收集到的信息,在ifort中提供了对与C互操作性的内置支持,其中包括布尔值。您可以尝试利用它。
Lig*_*ica 23
当您违反了语言和编译器的合同时,就会发生这种情况。
您可能在某处听说过“零为假”和“非零为真”。当您坚持使用该语言的参数,将静态转换int
为bool
或反之时,这一点成立。
当您开始弄乱位表示形式时,它不成立。在这种情况下,您将打破合同,并输入(至少)实现定义的行为的领域。
根本不要那样做。
如何将a bool
存储在内存中不取决于您。这取决于编译器。如果要更改bool
的值,请分配true
/ false
或分配一个整数,并使用C ++提供的适当转换机制。
C ++标准实际上用于明确指出以bool
这种方式使用是顽皮的,坏的和坏的(“ bool
以本文档描述为'undefined'的方式使用值,例如通过检查未初始化的auto对象,可能会导致它的行为好像既不是也不true
是false
。”),尽管出于编辑原因在C ++ 20中将其删除。