在 bool 中设置额外的位使其同时为真和假

Setting extra bits in a bool makes it true and false at the same time

如果我得到一个 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如何同时等于true和false?

       value   bits 
       -----   ---- 
    T:   1     0001
after bit change
    T:   3     0011
T is true
T is false

当您使用其他语言(例如 fortran)调用函数时,可能会发生这种情况,其中 true 和 false 的定义与 C++ 不同。对于 Fortran,如果任何位不为 0,则值为真,如果所有位均为零,则值为假。

#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 大小的类型,取 1 或 0 作为可能的值。

如果您将其值设置为与允许值不同的任何值(在这种特定情况下,通过 charbool 设置别名并修改其位表示),您将违反规则语言,所以任何事情都可能发生。特别是,在标准中明确规定,“损坏的”bool 可能同时表现为 truefalse(或者既不表现为 true 也不表现为 false)同时:

Using a bool value in ways described by this International Standard as “undefined,” such as by examining the value of an uninitialized automatic object, might cause it to behave as if it is neither true nor 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 是没问题的,但对于你的“损坏的”它不会削减它。

请注意,这个奇怪的序列仅在低优化级别下生成;在更高级别,这通常会归结为 zero/nonzero 检查,而像您这样的序列可能会变成 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 用于“边界”代码,并将其编组作为 appropriately-sized 整数。它将在条件 & co 中工作。一样好。


关于问题 Fortran/interoperability 方面的更新

Disclaimer all I know of Fortran is what I read this morning on standard documents, and that I have some punched cards with Fortran listings that I use as bookmarks, so go easy on me.

首先,这种语言互操作性的东西不是语言标准的一部分,而是平台 ABI 的一部分。正如我们所说的Linuxx86-64,相关文档是the System V x86-64 ABI.

首先,没有任何地方指定 C _Bool 类型(在 3.1.2 注 † 中定义为与 C++ bool 相同)与 Fortran 有任何兼容性LOGICAL;特别是,在 9.2.2 table 9.2 指定“plain”LOGICAL 映射到 signed int。关于 TYPE*N 类型它说

The “TYPE*N” notation specifies that variables or aggregate members of type TYPE shall occupy N bytes of storage.

(同上)

没有为 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 还指定:

The values for type LOGICAL are .TRUE. implemented as 1 and .FALSE. implemented as 0.

因此,如果您的程序在 LOGICAL 值中存储 1 和 0 以外的任何内容,您已经超出了 Fortran 方面的规范!你说:

A fortran logical*1 has same representation as bool, but in fortran if bits are 00000011 it is true, in C++ it is undefined.

这最后一个说法是不正确的,Fortran 标准是 representation-agnostic,而 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 开关文档中进行了解释,其中还指定了一些关于 LOGICAL 和 cross-language 兼容性的有趣细节:

Specifies that integers with a non-zero value are treated as true, integers with a zero value are treated as false. The literal constant .TRUE. has an integer value of 1, and the literal constant .FALSE. has an integer value of 0. This representation is used by Intel Fortran releases before Version 8.0 and by Fortran PowerStation.

The default is fpscomp nologicals, which specifies that odd integer values (low bit one) are treated as true and even integer values (low bit zero) are treated as false.

The literal constant .TRUE. has an integer value of -1, and the literal constant .FALSE. has an integer value of 0. This representation is used by Compaq Visual Fortran. The internal representation of LOGICAL values is not specified by the Fortran standard. Programs which use integer values in LOGICAL contexts, or which pass LOGICAL values to procedures written in other languages, are non-portable and may not execute correctly. Intel recommends that you avoid coding practices that depend on the internal representation of LOGICAL values.

(强调)

现在,LOGICAL 的内部表示通常应该不是问题,因为据我所知,如果你“按规则”玩游戏并且不跨越语言界限,你就不是会注意到。对于符合标准的程序,INTEGERLOGICAL 之间没有“直接转换”;我看到你可以将 INTEGER 推入 LOGICAL 的唯一方法似乎是 TRANSFER,它本质上是 non-portable 并且没有提供真正的保证,或者 non-standard INTEGER <-> LOGICAL 赋值转换。

后一个 is documented by gfort to always result in nonzero -> .TRUE., zero -> .FALSE., and 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 是一个正整数 zero-extension (gfort),所以,为了遵守上面链接的文档中的合同,显然期望 LOGICAL 值为 0 或 1。

但总的来说,这些转换的情况是a bit of a mess,所以我会远离它们。


所以,长话短说:避免将 INTEGER 数据放入 LOGICAL 值中,因为即使在 Fortran 中也很糟糕,并确保使用正确的编译器标志来获取 ABI-compliant 布尔值的表示,以及与 C/C++ 的互操作性应该没问题。但为了更加安全,我只在 C++ 端使用普通 char

最后,根据我收集到的信息 from the documentation,在 ifort 中有一些内置支持与 C 的互操作性,包括布尔值;你可以尝试利用它。

当您违反与语言和编译器的约定时,就会发生这种情况。

您可能在某处听说过 "zero is false" 和 "non-zero is true"。当您坚持使用语言的参数,将 int 静态转换为 bool 或相反时,这种情况就成立。

当你开始搞乱位表示时,它就站不住脚了。在这种情况下,您将违反合同,并进入(至少)implementation-defined 行为领域。

千万不要那样做。

一个bool如何存储在内存中并不取决于你。这取决于编译器。如果要更改 bool 的值,请分配 true/false,或者分配一个整数并使用 C++ 提供的适当转换机制。


C++ 标准过去实际上给出了一个具体的 call-out 以这种方式使用 bool 是多么顽皮、坏和邪恶 ("Using a bool value in ways described by this document as 'undefined',such as by examining the value of an uninitialized automatic object, might cause it to behave as if it is neither true nor false."), 尽管它是 removed in C++20 for editorial reasons.