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

标签 c++ boolean undefined-behavior evaluation abi

如果我收到 bool变量并将其第二位设置为 1,然后变量同时评估为真和假。使用 gcc6.3 和 -g 编译以下代码选项( 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)调用函数时,可能会发生这种情况,其中真假定义与 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 -size 类型取 1 或 0 作为可能的值。
如果您将其值设置为与允许值不同的任何值(在这种特定情况下,将 bool 别名为 char 并修改其位表示),您就违反了语言规则,因此任何事情都可能发生。特别是,标准中明确规定“损坏的”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
加载 Teax (零扩展名),如果全部为零则跳过打印;下一个如果是
    movzx   eax, BYTE PTR [rbp-33]
    xor     eax, 1
    test    al, al
    je      .L23
测试if(T == false)转换为 if(T^1) ,它只翻转低位。这对于有效的 bool 来说没问题,但对于你的“ splinter ”,它并没有削减它。
请注意,这个奇怪的序列仅在低优化级别下生成;在更高级别,这通常会归结为零/非零检查,而像您这样的序列可能会变成 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对于“边界”代码,并将其编码为适当大小的整数。它将在条件 & co 中工作。一样好。

有关问题的 Fortran/互操作性方面的更新

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 的一部分。当我们谈论Linux x86-64时,相关文档是the System V x86-64 ABI .
首先,没有任何地方指定 C _Bool类型(定义为与 C++ bool 在 3.1.2 note † 中相同)与 Fortran LOGICAL 具有任何类型的兼容性;特别是,在 9.2.2 表 9.2 指定“普通”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 明确指定的等效类型,这是可以理解的:它甚至不是标准的;事实上,如果你试图编译一个包含 LOGICAL*1 的 Fortran 程序。在 Fortran 95 兼容模式下,您会收到有关它的警告,均通过 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 之外的任何内容值(value),您已经超出了 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 标准是表示不可知的,而 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 的有趣细节和跨语言兼容性:

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 的内部表示通常应该没有问题,因为,根据我的收集,如果您“按规则”玩并且不跨越语言界限,您就不会注意到。对于符合标准的程序,INTEGER 之间没有“直接转换”。和 LOGICAL ;我认为你可以推一个INTEGER的唯一方法成LOGICAL好像是TRANSFER ,本质上是不可移植的并且没有真正的保证,或者非标准 INTEGER <-> LOGICAL任务转换。
后者is documented通过 gfort 总是导致非零 -> .TRUE. , 零 -> .FALSE. , 和 you can see在所有情况下都会生成代码来实现这一点(即使在使用遗留表示的情况下它是复杂的代码),因此您似乎无法将任意整数插入 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),因此,为了履行上面链接的文档中的契约(Contract),显然需要 LOGICAL值为 0 或 1。
但总的来说,这些转换的情况是a bita mess ,所以我只是远离他们。

所以,长话短说:避免放置 INTEGER数据导入LOGICAL值,因为即使在 Fortran 中它也很糟糕,并确保使用正确的编译器标志来获得符合 ABI 的 boolean 值表示,并且与 C/C++ 的互操作性应该没问题。但为了更加安全,我只使用普通的 char在 C++ 方面。
最后,从我收集到的 from the documentation ,如果有一些内置支持与 C 的互操作性,包括 boolean 值;你可以尝试利用它。

关于c++ - 在 bool 中设置额外位使其同时为真和假,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56369080/

相关文章:

c++ - 如何在 Linux 中将子进程 stdout/err) 设置为父进程

c++ - 如何将 static_cast 与双重类型转换一起使用

ios - 如何管理 BOOL 变量?

c - 32 位无符号乘法在 64 位上导致未定义的行为?

c++ - 两包可变参数模板参数

c++ - 如果 C++ 中有 for 循环,是否可以将方法声明为内联方法

java - 在 Java/Android 中从哈希表中的对象分配 boolean 值

visual-c++ - c++线性公式简化库

c++ - try catch 类变量初始化的作用域

c++ - 与 memcpy 的数据竞争,未定义的行为?