Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/72.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C ";“可观察到的行为”;在UB的上下文中;“未定义的行为”;_C_Language Lawyer - Fatal编程技术网

C ";“可观察到的行为”;在UB的上下文中;“未定义的行为”;

C ";“可观察到的行为”;在UB的上下文中;“未定义的行为”;,c,language-lawyer,C,Language Lawyer,(这个问题最初是由本答案下的注释提出的,但这里严格从C语言的角度提出,不涉及任何并发或多线程。) 考虑以下代码: #define BUFSIZ 10 char buf[BUFSIZ]; void f(int *pn) { buf[*pn]++; *pn = (*pn + 1) % BUFSIZ; } int main() { int n = 0; f(&n); return n; } 问题:C“as-if”规则是否允许编译器按如下方式重写代

(这个问题最初是由本答案下的注释提出的,但这里严格从C语言的角度提出,不涉及任何并发或多线程。)

考虑以下代码:

#define BUFSIZ 10
char buf[BUFSIZ];

void f(int *pn)
{
    buf[*pn]++;
    *pn = (*pn + 1) % BUFSIZ;
}

int main()
{
    int n = 0;
    f(&n);
    return n; 
}
问题:C“as-if”规则是否允许编译器按如下方式重写代码

void f(int *pn)
{
    int n = *pn;
    *pn = (*pn + 1) % BUFSIZ;
    buf[n]++;
}
一方面,上述内容不会改变所编写程序的可观察行为

另一方面,
f
可能会被无效索引调用,可能来自另一个翻译单元:

int g()
{
    int n = -1001;
    f(&n);
}
在后一种情况下,代码的两个变体在访问越界数组元素时都会调用UB。然而,原始代码将在传递到
f
(=-1001)的值处离开
*pn
,而重写的代码只有在修改
*pn
(到
0
)后才会进入UB land

这种差异会被认为是“可观察的”吗?或者,回到实际问题,C标准中是否有任何东西特别允许或排除这种类型的代码重写/优化

  • 如果程序的任何部分有未定义的行为,则整个程序的行为是未定义的。换句话说,程序的行为是未定义的,甚至在任何行为未定义的构造之前。(这是允许编译器根据定义的行为执行某些优化所必需的。)

  • 鉴于这两个变量均未声明为易失性变量,我相信内存更新的顺序可能会按指示重新排序,因为只有在没有未定义行为的情况下,才能保证可观察行为符合执行模型

  • “可观察行为”(标准C)在§5.1.2.3中定义为:

    • 对易失性对象的访问严格按照抽象机器的规则进行评估
    • 在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同
    • 交互设备的输入和输出动态应按照7.21.3的规定进行。这些要求的目的是尽可能快地显示无缓冲或行缓冲输出,以确保提示消息实际出现在 等待输入的程序
    该列表不包括对未定义行为(如陷阱或信号)的任何潜在响应,即使用方言来说,通常可以观察到断层。问题中的具体例子不涉及这三点中的任何一点。(UB可能会阻止程序成功终止,这基本上使可观察行为的第二点无效。)因此,在问题代码的特定情况下,重新排序不会改变任何可观察行为,并且可以清楚地执行

  • 我声明,实现对未定义行为的响应不限于严格按照产生未定义行为的组件执行,这在评论线程中引起了比我预期的更多的争议,因为这是现代C语言的一个相当著名的特征。也许值得回顾一下约翰·雷格尔的《有用的关于》,我引述如下:(在第三部分)

    更具体地说,当程序由于执行非法操作(如除以零或取消引用空指针)而死亡时,这是否被认为是副作用?答案肯定是“否”…由于崩溃诱导操作不会产生副作用,编译器可以根据其他操作对它们重新排序

    作为一个可能更有趣的例子(取自注释线程),如果一个程序生成几行输出,然后故意执行一个显式的除以零,那么人们可能会认为编译和运行该程序将在以任何未定义的方式响应除以零之前生成输出。然而,如果编译器检测到除法为零,并且能够证明程序的控制流保证了程序的执行,那么它完全有权在翻译时生成错误消息,并拒绝生成可执行映像

    或者,如果它不能证明控制流达到了被零除,它将有权假设被零除不可能发生,并因此明确地将导致被零除的所有代码(包括对输出函数的调用)作为死代码删除

    上述两种情况均符合§3.4.3中未定义行为的示例响应列表:“从完全忽略情况并产生不可预测的结果,……到终止翻译或执行(发出诊断信息)。”


  • 这根本不是事实。输入可能导致未定义的行为。我可以从一个程序中获得部分输出,然后将该输出传递到宇宙中一个因因果关系而断开的部分。之后,我可以给程序一个导致UB的输入。已经获得的有效输出不能被撤销(这在物理上是不可能的;我们已经将其传达给了一个因果分离的时空区域)。也就是说,即使UB导致了一些灾难性事件,比如超新星爆炸,前面的结果是不会被破坏的。@kaz:我认为时间旅行是不可能的,不管C标准是否允许。尽管如此,该标准免除了执行行为未定义程序的所有责任。当然,这种自由延伸到遵守宇宙的物理定律,我认为输入导致的UB很可能不会追溯到程序执行,但这不是标准所要求的,即使是常识所要求的……无论如何