他们说拥有UB时,程序可以做任何想做的事。
但是如果我在一个声明中有UB,例如
signed char a = 0x40;
a <<= 2;
或者甚至是未使用的(!)零大小的可变长度数组:
int l = 0;
char data[l];
这是否可以忍受,因为只有结果是未定义的,或者这是&#34;坏&#34;然而?
我对这些情况特别感兴趣:
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;
}
假设案例X涵盖了a
的值未定义的情况,而其他案例则使用了这种情况。如果我被允许按照我想要的方式计算并稍后进行区分,这将简化事情。
另一种情况是I talked about the other day:
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])
}
}
如果两个标志都未设置,我有&#34; undefined&#34;长度为0的数组data
但是由于我不读或写,我不明白为什么以及它可能会受到什么伤害...
答案 0 :(得分:4)
未定义的行为意味着它未由C规范定义。 可以很好地为特定编译器定义(或部分定义)。
大多数编译器都定义了无符号移位的行为。
大多数编译器定义是否允许零长度数组。
有时您可以使用编译器标记更改bahaviour,例如--pedantic
或将所有警告视为错误的标记。
所以问题的答案是:
这取决于编译器。您需要查看特定编译器的文档。
当你使用某些东西时依赖特定的结果是否可以 UB根据C标准?
这取决于你编码的内容。如果它是特定嵌入式系统的代码,其中任何其他地方的可能性都很低,那么无论如何,如果它给出了很大的回报,那么依靠UB。但最佳做法是尽可能避免使用UB。
修改强>
这是否可以容忍,因为只有结果未定义,或者这是“坏”?
是(只有结果未定义在实践中是正确的,但理论上,编译器制造商可以在不破坏C规范的情况下终止程序)是的,它仍然很糟糕(因为它需要额外的测试来确保行为在做出改变之后保持不变。)
如果未指定行为,那么您可以观察您获得的行为。最好的方法是检查生成的汇编代码。
但是,您需要注意,如果您更改了任何内容,行为可能会发生变化。可能会更改行为的更改包括但不限于更改优化级别以及编译器升级或修补程序的应用程序。
编写编译器的人通常是理性的人,这意味着在大多数情况下,程序将以编译器开发人员最容易的方式运行。
最佳做法仍然是尽可能避免使用UB。
答案 1 :(得分:1)
您混淆了未定义和实现定义的行为。
将值移动多于它的位数是实现定义的。它会产生影响,您需要阅读编译器文档。例如,在某些体系结构上,它可能没有任何效果,而在其他体系结构上,它将保持为零。实现定义的行为在体系结构或编译器版本之间不可移植,不需要诊断,但在运行之间将保持一致。
但是,声明大小为0的数组是未定义的行为。编译器可以根据你没有做类似的事情来自由地进行优化,并生成在你这样做时不起作用的代码。如果你调用未定义的行为,编译器可以自由地做任何它喜欢的事情,并且你的程序可能今天工作,而不是明天,或者直到你在程序中的其他地方添加另一行或者....
未定义意味着 - 未定义。试图弄清楚它的行为方式或取决于所述行为的结果,没有任何里程。
答案 2 :(得分:1)
在send_stuff函数的上下文中,编译器可以自由地优化游标的计算:
uint8_t cursor = flag1 ? 4 + 2 * !!flag2 : 2;
虽然当flag1和flag2都为0时,这给游标提供了不同的结果,但这很好,因为这样会导致未定义的行为,因此在这种情况下允许它做任何想做的事情。
这是一个完全独立于机器的优化,所以即使你知道&#34;你总是使用相同的编译器在相同的架构上运行,你会发现有一天你做了一个看似无关的改变,编译器会得到一个不同的决定,关于什么是最佳代码看起来像你以前工作的代码是突然表现得与众不同。