3

有一个代码片段,GCC 产生了我没想到的结果:

(我正在为目标 i686-linux-gnu 使用 gcc 版本 4.6.1 Ubuntu/Linaro 4.6.1-9ubuntu3)

[测试.c]

#include <stdio.h>

int *ptr;

int f(void)
{
    (*ptr)++;

    return 1;
}

int main()
{
    int a = 1, b = 2;

    ptr = &b;

    a = b++ + f() + f() ? b : a;

    printf ("b = %d\n", b);

    return a;
}

据我了解,函数调用有一个序列点。后增量应在 f() 之前进行。

参见 C99 5.1.2.3:“......称为序列点,之前评估的所有副作用都应完整,后续评估的副作用不应发生。”

对于这个测试用例,也许评估的顺序是不确定的,但最终的结果应该是一样的。所以我预计 b 的最终结果是 5。但是,在使用 'gcc test.c -std=c99' 编译此案例后,输出显示 b = 3。

然后我使用“gcc test.c -std=c99 -S”看看发生了什么:

        movl    $1, 28(%esp)
        movl    $2, 24(%esp)
        leal    24(%esp), %eax
        movl    %eax, ptr
        movl    24(%esp), %ebx
        call    f
        leal    (%ebx,%eax), %esi
        call    f
        addl    %esi, %eax
        testl   %eax, %eax
        setne   %al
        leal    1(%ebx), %edx
        movl    %edx, 24(%esp)
        testb   %al, %al
        je      .L3
        movl    24(%esp), %eax
        jmp     .L4
.L3:
        movl    28(%esp), %eax
.L4:
        movl    %eax, 28(%esp)

似乎 GCC 在 f() 之前使用评估值并在两次 f() 调用之后执行 '++' 操作。

我也用llvm-clang编译了这个case,结果显示b=5,也就是我所期望的。

我对后增量和序列点行为的理解是否不正确?或者这是 GCC461 的一个已知问题?

4

2 回答 2

4

除了 Clang,还有另外两个工具可以作为参考:Frama-C 的价值分析KCC。我不会详细介绍如何安装它们或将它们用于此目的,但它们可用于检查 C 程序的定义性——与编译器不同,它们旨在告诉您目标程序是否显示未定义行为。

他们有粗糙的边缘,但他们都认为在程序结束时b绝对应该没有未定义的行为:5

Mini:~/c-semantics $ dist/kcc ~/t.c
Mini:~/c-semantics $ ./a.out 
b = 5

这是一个比 Clang 认为的更强有力的论点(因为如果它是未定义的行为,Clang 仍然可以生成一个打印的程序b = 5)。

长话短说,您似乎在该版本的 GCC 中发现了一个错误。下一步是检查 SVN 以查看它是否仍然存在。

于 2012-08-21T19:57:51.300 回答
3

我前段时间报告了这个 GCC 错误,并在今年早些时候修复了它。见http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48814

于 2012-08-21T21:15:45.333 回答