在这篇文章中,我们将尝试涵盖许多模棱两可的问题,如下所示。
猜猜以下程序的输出。
上述所有程序的输出都是undefined或unspecified。不同编译器和不同机器的输出可能不同。这就像询问未定义的自动变量的值。
程序 1 中未定义行为的原因是,运算符 '+' 对其操作数没有标准定义的求值顺序。可以先执行 f1() 或 f2()。所以输出可能是“GeeksforGeeks”或“forGeeksGeeks”。
与运算符 '+' 类似,大多数其他类似的运算符,如 '-'、'/'、'*'、Bitwise AND &、Bitwise OR |、.. 等都没有标准定义的操作数求值顺序.
表达式的评估也可能产生副作用。例如,在上面的程序 2 中,p 的最终值是不明确的。根据表达式求值的顺序,如果 f1() 先执行,则 p 的值为 55,否则为 40。
程序 3 的输出也是未定义的。它可能是 64、72 或其他值。子表达式 i++ 会产生副作用,它会修改 i 的值,这会导致未定义的行为,因为 i 在同一表达式的其他地方也被引用。
与上述情况不同,在称为序列点的执行序列中的某些指定点处,先前评估的所有副作用都保证是完整的。序列点定义了计算机程序执行中的任何点,在该点处,可以保证先前评估的所有副作用都已执行,并且尚未执行后续评估的任何副作用。以下是 C 标准中列出的序列点:
— 以下运算符的第一个操作数的结尾:
a) 逻辑与 &&
b) 逻辑或 ||
c) 有条件的?
d) 逗号,
例如,以下程序的输出保证在所有编译器/机器上都是“52cxydhfor52cxydh”。
— 完整表达式的结尾。此类别包括以下表达式语句
a) 任何以分号结尾的完整语句,例如“a = b;”
b) return 语句
c) if、switch、while 或 do-while 语句的控制表达式。
d) for 语句中的所有三个表达式。
上面的序列点列表是部分的。我们将在下一篇关于序列点的文章中介绍所有剩余的序列点。
// PROGRAM 1
#include <stdio.h>
int f1() { printf ("Geeks"); return 1;}
int f2() { printf ("forGeeks"); return 1;}
int main()
{
int p = f1() + f2();
return 0;
}
// PROGRAM 2
#include <stdio.h>
int x = 20;
int f1() { x = x+10; return x;}
int f2() { x = x-5; return x;}
int main()
{
int p = f1() + f2();
printf ("p = %d", p);
return 0;
}
// PROGRAM 3
#include <stdio.h>
int main()
{
int i = 8;
int p = i++*i++;
printf("%dn", p);
}
// Following 3 lines are common in all of the below programs
#include <stdio.h>
int f1() { printf ("52cxydh"); return 1;}
int f2() { printf ("for52cxydh"); return 1;}
// PROGRAM 4
int main()
{
// Since && defines a sequence point after first operand, it is
// guaranteed that f1() is completed first.
int p = f1() && f2();
return 0;
}
// PROGRAM 5
int main()
{
// Since comma operator defines a sequence point after first operand, it is
// guaranteed that f1() is completed first.
int p = (f1(), f2());
return 0;
}
// PROGRAM 6
int main()
{
// Since ? operator defines a sequence point after first operand, it is
// guaranteed that f1() is completed first.
int p = f1()? f2(): 3;
return 0;
}