bool 变量的减量是否定义在 ??

ale*_*why 4 c boolean increment undefined-behavior decrement

这个问题是关于\xd0\xa1的。假设我们有这样的代码:

\n
bool a = false;\na++;\nprintf("%d\\n", a);\na--;\nprintf("%d\\n", a);\n
Run Code Online (Sandbox Code Playgroud)\n

在我的 x86-64 linux 机器上显示:

\n
1\n0\n
Run Code Online (Sandbox Code Playgroud)\n

这对我来说并不意外。这段代码:

\n
bool a = false;\na++; a++;\nprintf("%d\\n", a);\na--; a--;\nprintf("%d\\n", a);\n
Run Code Online (Sandbox Code Playgroud)\n

有点令人惊讶,因为它打印了:

\n
1\n1\n
Run Code Online (Sandbox Code Playgroud)\n

这在其他一些架构上是一致的(我检查了x86和arm7)。

\n

C 标准规定 e++ 或 e-- 应分别视为 e+=1 或 e-=1。事实上,如果我们替换 a++; 其中a += 1;和一个——;其中a-= 1;输出保持不变。

\n

我查看了 x86-64 的程序集。gcc 使用“xor”指令进行减量:

\n
    b--; b--;\n    11e6:       80 75 ff 01             xor    BYTE PTR [rbp-0x1],0x1\n    11ea:       80 75 ff 01             xor    BYTE PTR [rbp-0x1],0x1\n    printf("%d\\n", b);\n    11ee:       0f b6 45 ff             movzx  eax,BYTE PTR [rbp-0x1]\n    11f2:       89 c6                   mov    esi,eax\n    11f4:       48 8d 05 19 0e 00 00    lea    rax,[rip+0xe19]        # 2014 <_IO_stdin_used+0x14>\n    11fb:       48 89 c7                mov    rdi,rax\n    11fe:       b8 00 00 00 00          mov    eax,0x0\n    1203:       e8 68 fe ff ff          call   1070 <printf@plt>\n
Run Code Online (Sandbox Code Playgroud)\n

而 clang 更喜欢使用“add”(!)和“and”来减少:

\n
    11c9:       04 01                   add    al,0x1\n    11cb:       24 01                   and    al,0x1\n    11cd:       88 45 fb                mov    BYTE PTR [rbp-0x5],al\n    11d0:       8a 45 fb                mov    al,BYTE PTR [rbp-0x5]\n    11d3:       24 01                   and    al,0x1\n    11d5:       0f b6 f0                movzx  esi,al\n    11d8:       48 8d 3d 36 0e 00 00    lea    rdi,[rip+0xe36]        # 2015 <_IO_stdin_used+0x15>\n    11df:       b0 00                   mov    al,0x0\n    11e1:       e8 4a fe ff ff          call   1030 <printf@plt>\n
Run Code Online (Sandbox Code Playgroud)\n

但结果是一样的。如果我理解正确的话,这些只是翻转最低有效位的不同方法。

\n

据我所知,没有一本教科书显示过这样的例子,所以我认为这不是广为人知的事实。也许这是我自己的无知,但我已经用 C 编程了一段时间,直到现在才知道这种奇怪的行为。

\n

完整源码在这里

\n

我的问题

\n
    \n
  1. bool变量的减量是根据C标准定义的吗?或者它是未定义的(或者可能是实现定义的)行为?

    \n
  2. \n
  3. 如果定义了布尔值的递增和递减,为什么 gcc 在给定 -Wall 标志时显示有关 a++ 和 a-- 的警告?

    \n
  4. \n
  5. \xd0\xa1 bool 变量的连续递减会将其值从 0 翻转到 1,然后再次翻转到 0,依此类推。这与增量的作用相反(它不翻转)。这是故意选择的、可移植的行为吗?

    \n
  6. \n
\n

ike*_*ami 5

它是已定义的(因此是可移植的[1])。

\n
\n

C17 \xc2\xa76.5.2.4 \xc2\xb62 [...] 作为副作用,操作数对象的值会递增(即,将适当类型的值 1 添加到其中)。[...]

\n
\n
\n

C17 \xc2\xa76.5.2.4 \xc2\xb623 postfix-- 运算符类似于 postfix++ 运算符,只不过操作数的值递减(即从中减去相应类型的值 1) 。

\n
\n
\n

C17 \xc2\xa76.5.3.1 \xc2\xb62 [...] 表达式++E 等价于(E+=1)。[...]

\n
\n
\n

C17 \xc2\xa76.5.3.1 \xc2\xb63 前缀--运算符类似于前缀++运算符,只不过操作数的值递减。

\n
\n
\n

C17 \xc2\xa76.5.16.2 \xc2\xb63 E1 op = E2形式的复合赋值等效于简单赋值表达式 E1 = E1 op (E2),只不过左值 E1 仅计算一次 [.. .]

\n
\n

(我可以继续证明加法执行整数提升,即true作为intis 1,作为falsean intis0等等。但你明白了。)

\n

这就是我们观察到的行为。

\n
bool a = false;\na++;                    # false\xe2\x87\x920, 0+1=1,  1\xe2\x87\x92true\nprintf("%d\\n", a);      #                            true\xe2\x87\x921\na++;                    # true\xe2\x87\x921,  1+1=2,  2\xe2\x87\x92true\nprintf("%d\\n", a);      #                            true\xe2\x87\x921\na--;                    # true\xe2\x87\x921,  1-1=0,  0\xe2\x87\x92false\nprintf("%d\\n", a);      #                            false\xe2\x87\x920\na--;                    # false\xe2\x87\x920, 0-1=-1, -1\xe2\x87\x92true\nprintf("%d\\n", a);      #                            true\xe2\x87\x921\n
Run Code Online (Sandbox Code Playgroud)\n

“\xe2\x87\x92”表示整数提升或隐式转换为bool.

\n

它发出警告是因为这很奇怪。加法和减法不是布尔运算。并且还有更清晰的替代方案(至少对于前缀增量和后缀增量,或者如果您丢弃返回的值)。从上面的内容中,我们得出一些 bool 对象的以下等价物b

\n
    \n
  • ++b相当于b = true.
  • \n
  • --b相当于b = !b.
  • \n
\n
\n
    \n
  1. 只要您有 C 编译器,它就是可移植的。@Weather Vane 指出在 MSVC 中无条件--b生成false,但众所周知,MSVC 实际上并不是 C 编译器。
  2. \n
\n


Eri*_*hil 5

\n
    \n
  1. bool变量的减量是根据C标准定义的吗?或者它是未定义的(或者可能是实现定义的)行为?
  2. \n
\n
\n

它被定义了。

\n

就本答案而言,bool是 类型_Bool。(它是在 中的宏中定义的<stdbool.h>,但程序可以以不同的方式定义它。)

\n

a++a--在 C 2018 6.5.2.4 中进行了规定,其中第 2 段表示:

\n
\n

\xe2\x80\xa6 作为副作用,操作数对象的值会递增(即,将适当类型的值 1 添加到其中)。有关约束、类型和转换以及操作对指针\xe2\x80\xa6 的影响的信息,请参阅加法运算符和复合赋值的讨论

\n
\n

第 3 段说 postfix--类似于 postfix ++。请注意对加法运算符的引用以获取有关转换的信息。加法运算符在 6.5.6 中指定,其中第 4 段表示:

\n
\n

如果两个操作数都具有算术类型,则对它们执行通常的算术转换。

\n
\n

所以我们有 abool a和 \xe2\x80\x9c 的值 1 适当的类型。\xe2\x80\x9d \xe2\x80\x9c适当的类型\xe2\x80\x9d 没有正式定义,但我们可以假设它是boolint,无论哪种方式,结果都是相同的。通常的算术转换对于许多读者来说都很熟悉,但它们在 6.3.1.8 中指定,主要在第 1 段中。通常的算术转换首先考虑浮点类型,但此处不适用。整数操作数的第一条规则是:

\n
\n

\xe2\x80\xa6 对两个操作数执行整数提升\xe2\x80\xa6

\n
\n

整数提升在 6.3.1.1 中指定,第 2 段告诉我们 a boolorint操作数被转换为int。然后继续通常的算术转换规则:

\n
\n

\xe2\x80\xa6 如果两个操作数具有相同的类型,则不需要进一步转换。

\n
\n

因此,在转换aint和 1 后int,转换停止,并且 的增量a++计算为a转换为int加 1 转换为int,因此根据是a从 0 还是 1 开始,这会产生 1 或 2。

\n

然后,正如上面 6.5.2.4 2 所说,我们关注复合赋值的讨论。这里的含义是a++;相当于a += 1;。C 2018 6.5.16.2 3 表示这相当于a = a + 1;. 我们已经弄清楚了a + 1,所以任务仍然a存在。这在 6.5.16.1 中有规定,其中第 2 段说:

\n
\n

\xe2\x80\xa6 右操作数的值被转换为赋值表达式的类型,并替换左操作数指定的对象中存储的值。

\n
\n

因此,加法结果 1 或 2 被转换bool并存储在 中a。6.3.1.2 告诉我们有关转换为bool

\n
\n

当任意标量值转换为 时_Bool,如果该值比较等于 0,则结​​果为 0;否则,结果为 1。

\n
\n

因此,将 1 或 2 转换为bool1。因此,a++;被完全定义并将 1 存储在 中a

\n
\n
    \n
  1. 如果定义了布尔值的递增和递减,为什么 gcc 在给定 -Wall 标志时显示有关 a++ 和 a-- 的警告?
  2. \n
\n
\n

C 标准允许实现发出额外的诊断信息,常见的诊断类别是由 C 标准完全定义的代码,但程序员很少正常使用,因此它的使用可能表明拼写错误或其他错误。由于a++总是将 a 设置bool为 1,a = 1代码会更清晰、更常见,因此a++在某种程度上可能是错误而不是有意的代码,因此值得进行诊断,特别是在-Wall需要时。

\n

同样,a--;也是不寻常的。如果意图是翻转 a boola = !a;则更常见且更熟悉。

\n
\n
    \n
  1. \xd0\xa1 bool 变量的连续递减会将其值从 0 翻转到 1,然后再次翻转到 0,依此类推。这与增量的作用相反(它不翻转)。这是故意选择的、可移植的行为吗?
  2. \n
\n
\n

从某种意义上说,C 的规则是经过深思熟虑的,委员会在过去几十年里已经反复仔细地考虑了这一点,并且这种行为是由上面讨论的规则产生的,并指出:

\n

a--将 转换boolint. 然后我们从 0 或 1 开始,bool减去 1,得到一个int值 \xe2\x88\x921 或 0。然后int将其转换为bool,产生 1 或 0,并将其存储在 中a

\n

由于这是完全指定的并且严格符合 C 代码,因此它可以跨符合 C 标准的编译器移植。(我不断言任何 Microsoft 产品与 C 标准兼容。)

\n

然而,我怀疑这些规则的设计目的是导致值a--;翻转bool。这更有可能是规则整体设计的结果。

\n