在C++中,
bool
的位表示(甚至大小)是由具体实现定义的;通常它实现为一个以1或0作为可能值的char型大小的类型。如果将其值设置为任何与允许的值不同的值(在这种情况下,通过别名将
bool
传递给
char
并修改其位表示),则会违反语言规则,因此可能发生任何事情。特别地,在标准中明确指定“破损”的
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
它将T
加载到eax
中(带有零扩展),如果全部为零,则跳过打印;相反,下一个是
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
测试
if(T == false)
被转换为
if(T^1)
,它只翻转低位。这对于有效的
bool
来说是可以的,但对于您的“损坏”的
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的了解仅限于今天早上读到的标准文档,以及我有一些Fortran清单的穿孔卡片,我用它们作为书签,所以请对我宽容。
首先,这种语言互操作性的东西并不是语言标准的一部分,而是平台ABI的一部分。因为我们在谈论Linux x86-64,所以相关文档是System V x86-64 ABI。
首先,没有任何地方指定C语言的_Bool
类型(在3.1.2注释†中定义为与C ++的bool
相同)与Fortran的LOGICAL
具有任何兼容性;特别是在9.2.2表9.2中指定“普通”LOGICAL
映射到signed int
。关于TYPE*N
类型,它说:
“
TYPE*N
” 表示变量或聚合类型的成员占用
TYPE
类型的
N
个字节的存储空间。
(ibid.)
对于LOGICAL*1
,没有明确指定的等效类型,这是可以理解的:它甚至不是标准的;实际上,如果您尝试在Fortran 95兼容模式下编译包含LOGICAL*1
的Fortran程序,则会收到关于它的警告,无论是通过ifort。
./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
具有相同的表示方式,但是在Fortran中,如果位是00000011,则为
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.`的整数值为1,字面常量`.FALSE.`的整数值为0。此表示法由Intel Fortran 8.0版本之前的发布版和Fortran PowerStation使用。
默认值为`fpscomp nologicals`,它指定奇数值(低位为1)被视为真,偶数值(低位为0)被视为假。
字面常量`.TRUE.`的整数值为-1,字面常量`.FALSE.`的整数值为0。此表示法由Compaq Visual Fortran使用。LOGICAL值的内部表示未由Fortran标准规定。
在LOGICAL上下文中使用整数值,或将LOGICAL值传递给其他语言编写的过程的程序是不可移植的,并且可能无法正确执行。Intel建议避免依赖LOGICAL值的内部表示的编码实践。
现在,一个 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++端使用普通的
char
。
最后,据我所知从文档,在ifort中有一些内置支持与C的互操作性,包括布尔值;您可以尝试利用它。
bool
只能为0或1,因此编译器在生成代码时可以做出这样的假设。 - Peter Cordes