声明中的"未定义的行为"?

glg*_*lgl 6 c undefined-behavior

他们说,当有UB时,程序可以做任何想做的事情.

但如果我在一个声明中有UB,例如

signed char a = 0x40;
a <<= 2;
Run Code Online (Sandbox Code Playgroud)

或者甚至是一个未使用的(!)零大小的可变长度数组:

int l = 0;
char data[l];
Run Code Online (Sandbox Code Playgroud)

这是否可以忍受,因为只有结果是未定义的,或者这是"坏"吗?

我特别感兴趣的是这样的情况:

signed char a = 0x40;
a <<= 2;
switch (state) {
    case X: return
    case Y: do something with a; break;
    case Z: do something else with a; break;
}
Run Code Online (Sandbox Code Playgroud)

假设情况X涵盖了a未定义值的情况,而其他情况则使用了这种情况.如果我被允许按照我想要的方式计算并稍后进行区分,它会简化事情.

另一种情况是我前几天谈到的那个:

void send_stuff()
{
    char data[4 * !!flag1 + 2 * !!flag2];
    uint8_t cursor = 0;
    if (flag1) {
        // fill 4 bytes of data into &data[cursor]
        cursor += 4;
    }
    if (flag2) {
        // fill 2 bytes of data into &data[cursor]
        cursor += 2;
    }
    for (int i=0; i < cursor; i++) {
        send_byte(data[i])
    }
}
Run Code Online (Sandbox Code Playgroud)

如果两个标志data都未设置,我有一个长度为0 的"未定义"数组.但是由于我不读取或写入它,我不明白为什么以及它可能会如何伤害...

Kla*_*äck 4

未定义的行为意味着它不是由 C 规范定义的。它很可能是为特定编译器定义(或部分定义)的。

大多数编译器定义了无符号移位的行为。

大多数编译器定义是否允许零长度数组。

有时,您可以使用编译器标志更改行为,例如--pedantic将所有警告视为错误的 或 标志。

所以你的问题的答案是:

这取决于编译器。您需要检查特定编译器的文档。

当你使用符合 C 标准的 UB 的东西时,可以依赖特定的结果吗?

这取决于您正在编码的内容。如果它是针对特定嵌入式系统的代码,而移植到其他任何地方的可能性很低,那么无论如何,如果 UB 能带来丰厚的回报,就应该依赖它。但最佳实践是尽可能避免 UB。

编辑:

这是可以容忍的,因为只有结果是不确定的,或者这仍然是“坏”?

是的(实际上只有结果是未定义的才是正确的,但理论上,编译器制造商可以在不违反 C 规范的情况下终止程序),是的,这仍然很糟糕(因为它需要额外的测试来确保行为保持不变)进行更改后)。

如果行为未指定,那么您可以观察所得到的行为。最好是检查生成的汇编代码。

不过,您需要意识到,如果您进行任何更改,行为就会发生变化。可能改变行为的更改包括但不限于优化级别的更改以及编译器升级或补丁的应用。

编写编译器的人通常都是理性的人,这意味着在大多数情况下,程序将以对编译器开发人员来说最简单的方式运行。

最佳实践仍然是尽可能避免 UB。