C"观察到的行为和QUOT;在UB和的上下文中,不确定的行为" [英] C "observable behavior" in the context of UB "undefined behavior"

查看:116
本文介绍了C"观察到的行为和QUOT;在UB和的上下文中,不确定的行为"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(问题最初是由评论提示这个答案下在此生产者 - 消费者实现有竞争条件?但正在这里要求严格从C语言的角度来看,没有任何并发​​或涉及多线程)。

考虑这个最小code:

 的#define BUFSIZ 10
烧焦的buf [BUFSIZ];无效F(INT * PN)
{
    BUF [* PN] ++;
    * PN =(* PN + 1)%BUFSIZ;
}诠释的main()
{
    INT N = 0;
    F(&安培; N);
    返回N;
}

问:像C的作为假设的规则允许编译器重写code如下:

 无效F(INT * PN)
{
    INT N = * PN;
    * PN =(* PN + 1)%BUFSIZ;
    BUF [N] ++;
}

在一方面,以上也不会改变作为写入的程序的可观察行为

在另一方面,˚F可以被调用使用无效索引,可能从另一个翻译单位:

  INT克()
{
    INT N = -1001;
    F(&安培; N);
}

在后一种情况下,在code的这两个变种访问越界 - 数组元素时将调用UB。但是,原来的code会离开 * PN 的值被传递到˚F(= -1001)而改写code只会修改后步入UB-土地 * PN (为 0

会出现这样的差异计入可观察,或回到实际的问题,有没有在C标准的任何会特别允许或preclude这种类型的code重写/优化?


解决方案

  1. 如果你的程序的任何部分,是未定义行为,那么整个程序的行为是不确定的。换句话说,程序的行为是任何构造,其行为是不确定的前,甚至不确定的。 (这是必要的,以允许编译器执行依赖于被定义行为的某些优化。)


  2. 由于要根据在没有不确定的执行模型,由于挥发,我相信这是可能的,所指示的内存更新的顺序将重新排序,因为观察到的行为,才能保证既不是变量声明行为。


  3. 看得见的行为(标准C)被定义在与教派; 5.1.2.3为:


      

        
    • 访问volatile对象是严格按照抽象机的规则进行评估。

    •   
    • 在程序终止,写入到文件中的所有数据将是相同的结果,根据所述抽象语义,该程序的执行将产生

    •   交互式设备的
    • 输入和输出动力为7.21.3规定应采取的地方。这些要求的目的是使无缓冲或行缓冲输出尽快出现,以确保提示信息实际出现之前,
        一个程序等待输入。

    •   

    这个名单不包括未定义行为(如陷阱或信号)的潜在响应,即使在白话来讲段错误通常是可观察的。在讨论的特定例子不涉及任何这三点。 (该UB可能无法成功终止prevent程序,基本上空隙中观察到的行为的第二个点。)所以在问题code的具体情况,重新排序不会改变任何可观察到的行为,可以清楚地来执行。


  4. 我的声明中表示,执行的不确定的行为反应不限于执行其酝酿不确定的行为创造了意见线程多很多的争议比我预想的组件严格按照,因为它是一个相当知名的功能C.现代的这可能是值得上的作文: //blog.regehr.org/archives/226相对=nofollow>未定义 行为,从中我引述如下:(第三部分)


      

    更具体地,当一个程序死亡由于执行了非法操作,例如除以零或解引用一个空指针,这被认为是侧 - 影响?答案当然是不。…因为碰撞诱导操作不是一边实现,编译器可以相对于其他操作重新排序,


    作为一个可能更有趣的例子(从评论螺纹拍摄),如果一个程序产生输出的几行,然后故意执行一个明确的除数为零,人们可能会想到,编译并运行该程序会产生以任何方式不确定它响应除以零响应之前输出。然而,检测到除以零和编译器可以证明程序的控制流保证它的执行将完全有权到产生错误信息的在转换时的,并拒绝生成可执行图像。

    另外,如果不能证明控制流达到除数为零,将有权假定除以零不可能发生,因此,删除所有code明确领导到除数为零(包括调用输出函数)已经死了code。

    以上两者契合成例如响应在与教派未定义行为的清单; 3.4.3:从与联合国predictable结果完全无视的情况下,…要终止翻译或执行(与发行诊断消息)。


(Question was originally prompted by comments under this answer to Are there race conditions in this producer-consumer implementation? but is being asked here strictly from the C language perspective, without any concurrency or multi-threading involved.)

Consider this minimal code:

#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; 
}

Question: would the C "as-if" rules allow the compiler to rewrite the code as follows?

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

On one hand, the above would not change the observable behavior of the program as written.

On the other hand, f could get called with an invalid index, possibly from another translation unit:

int g()
{
    int n = -1001;
    f(&n);
}

In this latter case, both variants of the code would invoke UB when accessing the out-of-bounds array element. However, the original code would leave *pn at the value being passed into f (= -1001) while the rewritten code would step into UB-land only after modifying *pn (to 0).

Would such a difference count as "observable" or, back to the actual question, is there anything in the C standard that would specifically allow or preclude this type of code rewrite/optimization?

解决方案

  1. If any part of your program has undefined behaviour, then the behaviour of the entire program is undefined. In other words, the behaviour of the program is undefined even "before" any construct whose behaviour is undefined. (This is necessary to allow the compiler to perform certain optimizations which depend on behaviour being defined.)

  2. Given that neither variable is declared as volatile, I believe it is possible that the order of memory updates would be reordered as indicated, since observable behaviour is only guaranteed to be according to the execution model in the absence of undefined behaviour.

  3. "Observable behaviour" (in Standard C) is defined in §5.1.2.3 as:

    • Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.
    • At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced.
    • The input and output dynamics of interactive devices shall take place as specified in 7.21.3. The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input.

    This list does not include any potential response to undefined behaviour (such as a trap or signal), even though in vernacular terms a segfault is normally observable. The particular example in the question does not involve any of these three points. (The UB could prevent the program from successfully terminating, which basically voids the second point in observable behaviour.) So in the particular case of the code in the question, reordering would not alter any observable behaviour and could clearly be performed.

  4. My statement that the implementation's response to undefined behaviour is not limited to execution strictly following the component which engenders undefined behaviour created a lot more controversy in the comments thread than I expected, since it is a fairly well-known feature of modern C. It may be worth reviewing John Regehr's useful essay on undefined behaviour, from which I quote: (in the third part)

    More specifically, when a program dies due to performing an illegal operation such as dividing by zero or dereferencing a null pointer, is this considered to be side effecting? The answer is definitely "no." … Since crash-inducing operations are not side effecting, the compiler can reorder them with respect to other operations,

    As a possibly more interesting example (taken from the comments thread), if a program produces several lines of output, and then deliberately executes an explicit divide-by-zero, one might expect that compiling and running the program would produce the output before responding in whatever undefined way it responds to the divide-by-zero. However, a compiler which detected the divide-by-zero and could prove that the control flow of the program guaranteed its execution would be perfectly entitled to produce an error message at translation time and refuse to produce an executable image.

    Alternatively, if it could not prove that control flow reached the divide-by-zero, it would be entitled to assume that the divide-by-zero could not happen, and therefore remove all code unequivocally leading up to the divide-by-zero (including the calls to the output function) as dead code.

    Both of the above fit into the list of example responses to undefined behaviour in §3.4.3: "from ignoring the situation completely with unpredictable results, … to terminating a translation or execution (with the issuance of a diagnostic message)."

这篇关于C"观察到的行为和QUOT;在UB和的上下文中,不确定的行为"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆