在 C++ 中,bool 的位表示(甚至大小)是由实现定义的;通常它被实现为char-sized 类型,取 1 或 0 作为可能的值。
如果您将其值设置为与允许值不同的任何值(在这种特定情况下,通过将 bool 别名为 char 并修改其位表示),您就违反了语言规则,所以任何事情都可以发生。特别是,标准中明确规定“损坏的”bool 可能同时表现为true 和false(或者既不是true 也不是false):
以本国际标准描述为“未定义”的方式使用bool 值,例如通过检查未初始化的自动对象的值,可能会导致其表现得既不是true 也不是false
(C++11,[basic.fundamental],注释 47)
在这种特殊情况下,you can see how it ended up in this bizarre situation:第一个 if 被编译为
movzx eax, BYTE PTR [rbp-33]
test al, al
je .L22
在eax 中加载T(扩展名为零),如果全为零则跳过打印;下一个 if 是
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
测试if(T == false) 转换为if(T^1),它只翻转低位。这对于有效的bool 来说是可以的,但对于您的“坏掉的”它并没有削减它。
请注意,这个奇怪的序列只在低优化级别生成;在更高的级别上,这通常会归结为零/非零检查,并且像您这样的序列可能会变成a single test/conditional branch。无论如何,在其他情况下,您都会遇到奇怪的行为,例如将 bool 值与其他整数相加时:
int foo(bool b, int i) {
return i + b;
}
becomes
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 列表的穿孔卡片用作书签,所以请放轻松。 p>
首先,这种语言互操作性不是语言标准的一部分,而是平台 ABI 的一部分。正如我们所说的Linux x86-64,相关文档是the 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 字节的存储空间。
(同上)
没有为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 明确表示相反。确实,您可以通过checking the output of gfort for LOGICAL comparison 轻松看到这一点:
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 switch 文档中有解释,它还指定了一些关于LOGICAL 和跨语言兼容性的有趣细节:
指定非零值的整数被视为真,零值的整数被视为假。字面常量 .TRUE。整数值为 1,文字常量为 .FALSE。整数值为 0。英特尔 Fortran 版本 8.0 之前的版本和 Fortran PowerStation 使用此表示。
默认为fpscomp nologicals,指定奇整数值(低位1)被视为真,偶整数值(低位0)被视为假。
字面常量 .TRUE。整数值为 -1,文字常量为 .FALSE。具有整数值 0。Compaq Visual Fortran 使用此表示。 Fortran 标准未指定 LOGICAL 值的内部表示。 在 LOGICAL 上下文中使用整数值或将 LOGICAL 值传递给用其他语言编写的程序的程序是不可移植的,并且可能无法正确执行。英特尔建议您避免使用依赖于 LOGICAL 值的内部表示的编码实践。
(强调)
现在,LOGICAL 的内部表示通常不成问题,因为据我所知,如果您“按规则”玩游戏并且不跨越语言界限,您将不会注意到。对于符合标准的程序,INTEGER 和 LOGICAL 之间没有“直接转换”;我看到您可以将INTEGER 推入LOGICAL 的唯一方法似乎是TRANSFER,它本质上是不可移植的并且不提供真正的保证,或者非标准的INTEGER LOGICAL分配转换。
gfort 的后一个is documented 总是导致非零 -> .TRUE.、零 -> .FALSE. 和 you can see 在所有情况下都会生成代码来实现这一点(即使它是复杂的代码如果 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。
但总的来说,这些转化的情况是a bit 的a mess,所以我会远离它们。
所以,长话短说:避免将 INTEGER 数据放入 LOGICAL 值,因为即使在 Fortran 中也很糟糕,并确保使用正确的编译器标志来获得符合 ABI 的布尔表示和互操作性使用 C/C++ 应该没问题。但为了更加安全,我只会在 C++ 端使用普通的char。
最后,根据我收集到的from the documentation,在 ifort 中有一些内置支持与 C 的互操作性,包括布尔值;您可以尝试利用它。