根据对“未定义行为”的现代解释,编译器有权假定不会发生导致未定义行为“不可避免”的事件链,并且可以消除只适用于代码执行未定义行为的情况下的代码;这可能导致未定义行为的影响在时间上向后工作,并使本来可以观察到的行为无效。另一方面,在这样的情况下,除非程序终止,否则未定义的行为是不可避免的,但是在调用未定义的行为之前,程序可以并且确实终止,则程序的行为将保持完全定义。
在做出这一决定时,编译器需要考虑哪些终止原因?举几个例子:
在许多平台上,对像"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编译器以什么方式确定给定的操作是否会导致程序执行以它不知道的方式终止,在什么情况下允许在这些操作之前重新安排未定义的行为?
发布于 2015-04-24 20:37:58
这在intro.execution下的C++中得到了很好的描述。
执行格式良好的程序的一致性实现应产生与具有相同程序和相同输入的抽象机器相应实例的可能执行之一相同的可观察行为。但是,如果任何此类执行包含未定义的操作,则本国际标准不要求使用该输入执行该程序(甚至不要求执行第一个未定义操作之前的操作)。
人们普遍认为C具有相同的特性,因此C编译器可以在存在未定义行为的情况下类似地执行“时间旅行”。
重要的是,问题是是否存在显示未定义行为的抽象机器实例;通过首先终止程序执行来安排防止机器上的未定义行为并不重要。
如果导致程序以完全定义的方式终止自身,则可以防止未定义的行为(以及由此导致的时间移动),而抽象机器无法从中退出。例如,在第二个示例中,如果您用*p替换对(exit(0), 0)的访问,那么就不会发生未定义的行为,因为抽象机器不可能执行,exit将返回给调用者。但是,不管平台的特性如何,抽象机器都不必在访问insta地址时终止程序(实际上,抽象机器没有任何insta地址)。
https://stackoverflow.com/questions/29855620
复制相似问题