在“未定义行为”的现代解释下,编译器有权假设不会发生导致未定义行为“不可避免”的事件链,并且可以消除仅在代码为的情况下适用的代码。要执行未定义的行为;这可能会导致未定义行为的影响及时向后工作,并使原本可以观察到的行为无效。另一方面,如果除非程序终止,但是在程序可以并且在调用未定义行为之前终止的情况下,未定义行为将是不可避免的,程序的行为将保持完全定义。
在做出此决定时,需要考虑编译器的原因是什么?举几个例子:
在许多平台上,对“getc”之类的函数的调用通常会返回(至少最终),但在某些情况下,编译器无法控制。如果有人有这样的计划:
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("Foo\n");
return 0;
}
else
{
int ch;
printf("You'd better type control-C!\n");
int ch = getc();
if (ch < 65)
return (ch-33) / (argc-3);
else
return INT_MAX + ch;
}
}
如果在argc
等于3的情况下调用程序,那么会定义行为,但是SIGINT阻止getc()
调用返回吗?当然,如果getc()
可能返回哪个值会导致定义的行为,则在编译器确定不会接收到此类输入之前,不会发生未定义的行为。如果没有值getc()
可以返回,这将避免未定义的行为,但是,如果getc()
被阻止返回任何值,整个程序是否会保持定义? getc()
的返回值与调用未定义行为的操作之间是否存在因果关系会影响事物(在上面的示例中,编译器无法知道任何特定形式的未定义行为会在不知道输入了什么字符的情况下发生,但任何可能的输入都会触发某种形式。)
同样,如果在平台上存在地址,如果读取,则指定导致程序立即终止,编译器指定易失性读取将触发硬件读取请求,并且该平台上的某些外部库指定它将返回指向这样一个地址的指针,这些因素是否意味着bar
在这个单独的例子中的行为:
int foo(int x)
{
char volatile * p = get_instant_quit_address();
if (x)
{ printf("Hey"); fflush(stdout); }
return *p / x; // Will cause UB if *p yields a value and x is zero
}
int bar(void)
{
return foo(0);
}
如果尝试读取* p实际上会立即终止程序执行而不产生值,那么将被定义(作为终止而没有打印任何东西)?在返回值之前,除法不能进行;因此,如果没有返回任何值,则不会除以零。
通过什么方式,C编译器可以确定给定的操作是否可能导致程序执行以其不知道的方式终止,以及在什么情况下允许在此类操作之前重新安排未定义的行为? / p>
答案 0 :(得分:3)
在 [intro.execution] :
中的C ++中有详细描述5 - 执行格式良好的程序的符合实现应该产生与具有相同程序的抽象机的相应实例的可能执行之一相同的可观察行为 和相同的输入。但是,如果任何此类执行包含未定义的操作,则此国际标准不要求使用该输入执行该程序的实现(甚至不是 关于第一次未定义操作之前的操作。)
通常认为C具有相同的特性,因此C编译器可以在存在未定义的行为时类似地执行"time travel"。
重要的是,请注意问题是是否存在表现未定义行为的抽象机实例;通过首先终止程序执行来安排防止机器上的未定义行为并不重要。
如果导致程序以抽象机无法摆脱的完全定义的方式终止自身, 可以防止未定义的行为(并导致时间旅行)。例如,在第二个示例中,如果用*p
替换(exit(0), 0)
的访问权限,则不会发生未定义的行为,因为exit
返回其调用者的抽象机器无法执行。但无论您的平台有什么特性,抽象机器都不必在访问insta-kill地址时终止您的程序(实际上,抽象机器没有任何insta-kill地址)。