当前位置:  开发笔记 > 编程语言 > 正文

Java和C编译器的代码行为有何不同?

如何解决《Java和C编译器的代码行为有何不同?》经验,为你挑选了2个好方法。

我有这个Code,我在Java和C上运行它,但它们给了我两个不同的结果.是什么让他们以不同的方式运行.

x=10;y=10;z=10;
y-=x--;
z-=--x;
x-=--x-x--;

Java中X的输出值为:8,对于C,输出为6.

这两个编译器对增量选项的行为有何不同?



1> Pascal Cuoq..:

当你说这段代码的输出被认为是C程序时,你错了6.

被视为C程序,这是未定义的.你恰好用你的编译器得到6,但你也可能得到24,分段错误或编译时错误.

参见C99标准 6.5.2:

在前一个和下一个序列点之间,一个对象的存储值最多只能通过表达式的评估来修改一次.此外,先前的值应该是只读的,以确定要存储的值.71)

--x-x-- 本段明确禁止.

编辑:

Aaron Digulla在评论中写道:

它真的未定义吗?

您是否注意到我链接到C99标准并指出段落说明这是未定义的?

gcc -Wall(GCC 4.1.2)没有抱怨这个,我怀疑任何编译器都会拒绝这个代码.

该标准将某些行为描述为"未定义",因为并非所有C程序无意义的方法都可以在编译时可靠地检测到.如果你认为"没有警告"应该意味着一切都很好,你应该转用另一种语言而不是C.许多现代语言都有更好的定义.当我有一个选择时,我使用OCaml,但是有无数其他明确定义的语言.

有一个原因,它返回6,你应该能够解释它.

我没有注意到你为什么这个表达式被评估为6的解释.我希望你不要花太多时间写它,因为对我来说它返回0.

Macbook:~ pascalcuoq$ cat t.c
#include 

int main(int argc, char **argv)
{
  int y;
  printf("argc:%d\n", argc);
  y = --argc - argc--;
  printf("y:%d\n", y);
  return 0;
}
Macbook:~ pascalcuoq$ gcc t.c
Macbook:~ pascalcuoq$ ./a.out 1 2 3 4 5 6 7 8 9
argc:10
y:0

这是您认为我的编译器中存在错误的时间(因为它不会返回与您相同的错误).

Macbook:~ pascalcuoq$ gcc -v
Using built-in specs.
Target: i686-apple-darwin9
Configured with: /var/tmp/gcc/gcc-5490~1/src/configure --disable-checking -enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ --with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib --build=i686-apple-darwin9 --with-arch=apple --with-tune=generic --host=i686-apple-darwin9 --target=i686-apple-darwin9
Thread model: posix
gcc version 4.0.1 (Apple Inc. build 5490)

亚伦还写道:

作为工程师,您仍然可以解释为什么它会返回一个结果或另一个结果.

究竟!我给出了最简单的解释,为什么一个可能得到6:结果在C99中明确指定为未定义的行为,并且它也在早期标准中.

和:

最后,请显示一个警告此构造的编译器.

据我所知,没有编译器发出警告*(&x - 1),其中x被定义int x;.您是否声称此构造是有效的C并且优秀的工程师应该能够预测结果,因为没有编译器警告它?这个结构是未定义的,就像正在讨论的那个.

最后,如果您绝对需要警告以确定存在问题,请考虑使用Frama-C等验证工具.它需要做一些不在标准中的假设来捕获一些现有的实践,但是它正确地警告了--x-x--大多数其他未定义的C行为.



2> Aaron Digull..:

该术语如何评估?--x - x--对于Java和C ,右侧都评估为0,但它会更改x。所以问题是:如何-=工作?它x是在评估右侧(RHS)之前读取读数,然后减去RHS还是在评估RHS之后进行读数。那你有

tmp = x // copy the value of x
x = tmp - (--x - x--) // complicated way to say x = x

要么

tmp = (--x - x--) // first evaluate RHS, from left to right, which means x -= 2.
x = x - tmp // substract 0 from x

在Java中,这是规则:

形式为E1 op = E2的复合赋值表达式等效于E1 =(T)(((E1)op(E2))),其中T是E1的类型,只是E1仅被评估一次。(请参阅15.26.2复合分配运算符)

这意味着将复制的值,因此前减量和后减量均无效。您的C编译器可能使用其他规则。

对于C,本文可能会有所帮助:

道理是,编写依赖于评估顺序的代码对于任何语言而言都是不良的编程习惯。

[编辑] Pascal Cuoq(见下文)坚持认为标准说结果不确定。这可能是正确的:我盯着他复制出来的那部分超出标准几分钟,却听不懂那句话的意思。我想我并不孤单:)因此,我去看了为我的硕士论文开发的C解释器如何工作。它不符合标准,但我知道它是如何工作的。猜猜,我是一个海森堡式的人:我可以任意精确地选择一个,但不能同时选择两者;)无论如何。

解析此构造时,将获得以下解析树:

        +---- (-=) ----+
        v     -=       v
        x        +--- (-) ----+
                 v            v
              PREDEC x    POSTDEC x

该标准指出,修改x3次(一次在左侧,两次在两个递减操作中),则x未定义。好的。但是编译器是确定性程序,因此当它接受某些输入时,它将始终产生相同的输出。大多数编译器的工作原理相同。我认为我们都同意,任何C编译器实际上都会接受此输入。我们可以期待什么输出?答案:6或8。

    x-x0x的任何值。

    --x-x0对于x的任何值,因为它可以被写为--x, x-x

    x-x--这是0因为负运算符的结果是在递减后计算的。

因此,如果前减量对结果没有影响,而后减量也没有影响。而且,两个运算符之间没有推断(以与in相同的表达式使用它们a = --y - x--不会改变其行为)。结论:所有和任何C编译器将返回0--x - x--(当然,除了那些越野车)。

这使我们有了我最初的假设:RHS 对结果没有影响,它总是求值0但会修改 x。那么问题是如何-=实施?有很多因素在这里起作用:

    CPU是否具有本机运算符-=?基于寄存器的CPU可以(实际上,它们只有这样的运算符。要做的是a+b,它们必须复制a到寄存器中,然后才能复制到寄存器中+=b),基于堆栈的CPU则不需要(它们将所有值压入堆栈,然后使用使用最高堆栈元素作为操作数的运算符)。

    值是保存在堆栈还是寄存器中?(问第一个问题的另一种方法)

    哪些优化选项处于活动状态?

要进一步讲,我们必须看一下代码:

#include 

int main() {
        int x = 8;
        x -= --x - x--;
        printf("x=%d\n", x);
}

编译后,我们得到分配的汇编代码(x86代码):

    .loc 1 4 0
    movl    $8, -4(%rbp)    ; x = 8
    .loc 1 5 0
    subl    $1, -4(%rbp)    ; x--
    movl    $0, %eax        ; tmp = 0
    subl    %eax, -4(%rbp)  ; x -= tmp
    subl    $1, -4(%rbp)    ; x--
    .loc 1 6 0
    movl    -4(%rbp), %esi  ; push `x` into the place where printf() expects it

第一movlx8该装置-4(%rbp)x。如您所见,编译器实际上注意到了这一点x-x并对其进行了优化0(即使没有任何优化选项)。我们还有两个预期的--操作,这意味着结果必须始终为6

那么谁是对的?我们俩都是。当Pascal说标准没有定义这种行为时,他是对的。但这并不意味着它是随机的。代码的所有部分都有明确定义的行为,因此总和的行为不能突然变得不确定(除非有其他遗漏,但在这种情况下则不能如此)。因此,即使标准不解决此问题,它仍然是确定性的。

对于基于堆栈的CPU(没有任何寄存器),结果应为8,因为它们将x在开始评估右侧之前复制的值。对于基于寄存器的CPU,它应该始终为6。

士气:标准始终是正确的,但是如果您必须理解,请查看代码;)


该程序不是有效的C语言,其行为*语言未定义*。
@Aaron:我希望找到在某些情况下返回不同内容的C编译器非常容易。我什至希望它会因优化级别而有所不同。
推荐阅读
wurtjq
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有