奇怪的C++布尔铸造行为(true!= true)

Szt*_*upY 10 c++ casting boolean unions

请阅读内部大学主题:

#include <iostream>
using namespace std;

union zt
{
 bool b;
 int i;
};

int main()
{
 zt w;
 bool a,b;
 a=1;
 b=2;
 cerr<<(bool)2<<static_cast<bool>(2)<<endl;                      //11
  cerr<<a<<b<<(a==b)<<endl;                                      //111
 w.i=2;
 int q=w.b;
 cerr<<(bool)q<<q<<w.b<<((bool)((int)w.b))<<w.i<<(w.b==a)<<endl; //122220
 cerr<<((w.b==a)?'T':'F')<<endl;                                 //F
}
Run Code Online (Sandbox Code Playgroud)

所以a,b并且w.b都被宣布为bool.a分配1,b分配2,并将内部表示w.b更改为2(使用a union).

这样,所有的a,b并且w.btrue,但aw.b不会是平等的,所以这可能意味着宇宙坏了(true!=true)

我知道这个问题更具理论性而不是实际问题(程序员不想改变a的内部表示bool),但这里有一些问题:

  1. 这个可以吗?(这是用g ++ 4.3.3测试的)我的意思是,如果编译器知道在布尔比较期间任何非零值可能意味着是真的吗?
  2. 你知道这个角落案件可能成为一个真正的问题吗?(例如,从流中加载二进制数据时)

编辑:

三件事:

  1. bool并且int有不同的尺寸,没关系.但是,如果我使用char而不是int.或者当sizeof(bool)==sizeof(int)

  2. 如果可能的话,请回答我提出的两个问题.我实际上也对第二个问题的答案感兴趣,因为在我看来,在嵌入式系统(可能是8位系统)中,这可能是一个真正的问题(或不是).

  3. 新问题:这是否真的是未定义的行为?如果是,为什么?如果没有,为什么?对规范中的布尔比较运算符没有任何假设吗?

CB *_*ley 17

如果您读取的联盟成员与编写的最后一个成员的成员不同,那么您将获得未定义的行为.编写一个int成员然后读取union的bool成员可能会导致程序中任何后续点发生任何事情.

唯一的例外是结合是结构的联合,并且所有结构包含共同的初始序列,在这种情况下可以读取公共序列.

  • 再次,未定义的行为.您无法将任意位模式读入C++变量并期望它们"正常工作". (5认同)

Gre*_*ill 9

通常,在bool为编译器分配任意值时,会为您转换它:

int x = 5;
bool z = x; // automatic conversion here
Run Code Online (Sandbox Code Playgroud)

编译器生成的等效代码看起来更像:

bool z = (x != 0) ? true : false;
Run Code Online (Sandbox Code Playgroud)

但是,编译器只会执行一次此转换.假设bool变量中的任何非零位模式等同于true,特别是对于像那样的逻辑运算,这是不合理的.生成的汇编代码很笨重.

可以这么说,如果你正在使用union数据结构,你知道你正在做什么,你有能力混淆编译器.


Mar*_*ork 9

  1. 这个可以吗?(这是用g ++ 4.3.3测试的)我的意思是,如果编译器知道在布尔比较期间任何非零值可能意味着是真的吗?

任何非零的整数值(或非NULL的指针)都表示true.但是当比较整数和bool时,bool在比较之前被转换为int.

  1. 你知道这个角落案件可能成为一个真正的问题吗?(例如,从流中二进制加载数据时)

这始终是一个真正的问题.

  1. 这个可以吗?

    我不知道规格是否具体说明了这一点.编译器可能总是创建如下代码:((a!= 0)&&(b!= 0))|| ((a == 0)&&(b == 0))比较两个布尔值时,虽然这可能会降低性能.

    在我看来,这不是一个错误,而是一个未定义的行为.虽然我认为每个实现者都应告诉用户如何在其实现中进行布尔比较.

如果我们按你的最后一个代码样本,a和b都是bool并通过分别指定1和2来设置为true(Noe 1和2消失,它们现在只是真的).

所以打破你的表达:

a!=0      // true (a converted to 1 because of auto-type conversion)
b!=0      // true (b converted to 1 because of auto-type conversion)

((a!=0) && (b!=0)) => (true && true)  // true ( no conversion done)

a==0      // false (a converted to 1 because of auto-type conversion)
b==0      // false (b converted to 1 because of auto-type conversion)

((a==0) && (b==0)) => (false && false) // false ( no conversion done)

((a!=0) && (b!=0)) || ((a==0) && (b==0)) => (true || false) => true
Run Code Online (Sandbox Code Playgroud)

所以我总是希望上面的表达式能够很好地定义并且总是正确的.

但我不确定这是如何适用于您原来的问题.将整数赋给bool时,整数将转换为bool(如上所述).true的实际表示不是由标准定义的,可以是适合bool的任何位模式(您可能不会假设任何特定的位模式).

当将bool与int进行比较时,首先将bool转换为int然后进行比较.

  1. 任何现实世界的案例

    如果有人从文件中读取二进制数据到具有bool成员的结构中,那么我脑海中唯一会出现这种情况.问题可能会上升,如果文件是用其他程序编写的,而该程序将2而不是1写入bool的位置(可能是因为它是用另一种编程语言编写的).

    但这可能意味着糟糕的编程实践.

以二进制格式写入数据是不可移植的,不需要知识.
每个对象的大小都存在问题.
表示存在问题:

  • 整数(有结束)
  • 浮点(表示未定义((通常取决于底层硬件))
  • Bool(标准未定义二进制表示)
  • 结构(成员之间的填充可能不同)

有了这些,您需要了解底层硬件和编译器.不同的编译器或编译器的不同版本甚至具有不同优化标志的编译器可能对于所有上述内容具有不同的行为.

联盟的问题

struct X
{
    int  a;
    bool b;
};
Run Code Online (Sandbox Code Playgroud)

当人们提到写'a'然后从'b'读取是不确定的.
原因:因为我们不知道在这个硬件上如何表示'a'或'b'.写入'a'将填写'a'中的位,但这如何反映'b'中的位.如果你的系统使用1字节bool和4字节int,高内存中低内存最高字节中的最低字节,那么将1写入'a'将把1放入'b'.但那么你的实现如何代表一个博尔?真实的代表是1还是255?如果你在'b'中放一个1并且对于所有其他使用的真实它会使用255会发生什么?

因此,除非您了解硬件和编译器,否则行为将是意外的.

因此,这些用途尚未定义,但标准不允许.他们被允许的原因是,你可能已经做了研究,发现您的系统上使用这个特殊的编译器,你可以通过使这些假设做了一些优化freeky.但要注意,假设中的任何更改都会破坏您的代码.

另外,在比较两种类型时,编译器会在比较之前进行一些自动转换,请记住在比较之前将两种类型转换为相同的类型.对于整数和布尔之间比较布尔被转换成整数,然后对其他整数进行比较(转换false转换0和真实的1).如果要转换的对象都是bool,则不需要转换,并且使用布尔逻辑进行比较.