回首页 回首页 ◎ 设为首页  
◎ 收藏本站  
◎ 给我留言  
  
  首 页  C/C++教程  C++之父的FAQ  C/C++动向  C/C++源代码  C/C++误区  Unix/Linux  下载中心  乱七八糟  蚂蚁的Blog  
  当前位置:首 页 >> 乱七八糟 >> 乱七八糟 >> 让我耿耿于怀的一道笔试题
最 近 更 新
让我耿耿于怀的一道笔试题
原来可以这样出书、写书?
[转] 骇人听闻的 CSP
[转] C/C++术语中英对照
[转]也谈选书
李开复给计算机系大学生..
最 新 推 荐
热 门 排 行
李开复给计算机系大学生..
让我耿耿于怀的一道笔试题
[转] C/C++术语中英对照
[转] 骇人听闻的 CSP
原来可以这样出书、写书?
[转]也谈选书
站 内 搜 索

Web stdcpp.cn
关键词

搜索方式

搜索范围

精确匹配
广 告

让我耿耿于怀的一道笔试题


来源:蚂蚁的 C/C++ 标准编程 作者:antigloss 等级:一般
发布于2006-04-07 18:54 被读5450次 【字体:

    今天去一家公司笔试,迟到了半小时。幸好题目不难,都是很基础的题目,一路下来做得好不痛快。唯独有一道题让我耿耿于怀。题目大致如下:

        以下程序的输出结果是什么?

        main()
        {
            int x = 1, y = 2;

            x = x++ + y++;
            y = ++x + ++y;

            printf("%d %d", x, y);
        }

我们暂且不论这道题忘了包含头文件 stdio.h,main 函数的函数头写的也不符合当今标准。我们只讨论其中两个语句:

            x = x++ + y++;
            y = ++x + ++y;

这两个语句是标准中没定义的,所以我的回答大致是“输出不确定,不同的编译器可能导致不同的输出,因为这两个语句是标准未定义的。”

    我不知道出题者是故意考我是否知道这点,还是他/她以为这两个语句是正确的,因为国内的书几乎都把这个当成正确的来说。如果他/她认为是正确的,那我的回答就不符合他/她的期望了。无论如何,我不后悔我那样回答这道题目,因为我的回答是正确的。而且,如果他/她本来认为那两个语句是正确的,也许我的回答会纠正他/她长期以来的一个误解。不过话说回来,我想没几个人会在实际编程中写这样的语句,他/她大概一定也不会这样写。顺便提一下,我用 Dev-C++ 编译运行这道题的输出是 5 9 。也许您用别的编译器编译运行的输出会是 4 8 。也有可能是任何其它输出。

    如果您不知道这两个语句为何是标准没定义的,请参考以下资料。

==========================================================================

以下内容引自《C 语言常见问题集》 原著:Steve Summit 翻译:朱群英, 孙 云

http://c-faq-chn.sourceforge.net/ccfaq/index.html

http://www.eskimo.com/~scs/C-faq/top.html

==========================================================================

4.3 对于代码  int i = 3; i = i++; 不同编译器给出不同的结果, 有的为 3, 有的为 4, 哪个是正确的?

    没有正确答案;这个表达式无定义。参见问题 3.1, 3.7 和  11.32。 同时注意, i++ 和 ++i 都不同于 i+1。如果你要使 i 自增 1, 使用 i=i+1, i+=1, i++ 或 ++i, 而不是任何组合, 参见问题 3.10

4.7 我怎样才能理解复杂表达式?``序列点" 是什么?

    序列点是一个时间点(在整个表达式全部计算完毕之后或在 ||、  &&、 ? : 或逗号 运算符处, 或在函数调用之前), 此刻尘埃落定, 所有的副作用都已确保结束。 ANSI/ISO C 标准这样描述:

在上一个和下一个序列点之间, 一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。

第二句话比较费解。它说在一个表达式中如果某个对象需要写入, 则在同一表达式中对该对象的访问应该只局限于直接用于计算将要写入的值。这条规则有效地限制了只有能确保在修改之前才访问变量的表达式为合法。例如 i = i+1 合法, 而 a[i] = i++ 则非法 (参见问题 3.1)。

参见下边的问题 3.8

==========================================================================

上面这段翻译得不太好,令人费解。英语好的可以点击下面这个地址看英文原版的。

http://c-faq.com/expr/seqpoints.html

==========================================================================

12.35 有人说 i = i++ 的行为是未定义的, 但是我刚在一个兼容 ANSI  的编译器上测试, 得到了我希望的结果。

    面对未定义行为的时候, 包括范围内的实现定义行为和未确定行为, 编译器可以做任何实现, 其中也包括你所有期望的结果。但是依靠这个实现却不明智。参加问题 7.4, 11.31, 11.32 和 11.34

4.1 为什么这样的代码:  a[i] = i++; 不能工作?

    子表达式 i++ 有一个副作用 --- 它会改变 i 的值 --- 由于  i 在同一表达式的其它地方被引用, 这会导致无定义的结果, 无从判断该引用(左边的 a[i] 中)是旧值还是新值。(注意, 尽管在 K&R 中建议这类表达式的行为不确定, 但 C 标准却强烈声明它是无定义的, 参见问题 11.32

4.2 使用我的编译器,下面的代码  int i=7; printf("%d\n", i++ * i++); 返回 49?不管按什么顺序计算, 难道不该打印出56吗?

    尽管后缀自加和后缀自减操作符 ++ 和 -- 在输出其旧值之后才会执行运算, 但这里的``之后"常常被误解。没有任何保证确保自增或自减会在输出变量原值之后和对表达式的其它部分进行计算之前立即进行。也不能保证变量的更新会在表达式 ``完成" (按照 ANSI C 的术语, 在下一个 ``序列点" 之前, 参见问题 3.7) 之前的某个时刻进行。本例中, 编译器选择使用变量的旧值相乘以后再对二者进行自增运算。

    包含多个不确定的副作用的代码的行为总是被认为未定义。(简单而言, ``多个不确定副作用" 是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增, 自减和赋值操作符的任何组合。这是一个粗略的定义; 严格的定义参见问题 3.7, ``未定义" 的含义参见问题 11.32。) 甚至都不要试图探究这些东西在你的编译器中是如何实现的 (这与许多 C 教科书上的弱智练习正好相反); 正如  K&R 明智地指出, ``如果你不知道它们在不同的机器上如何实现, 这样的无知可能恰恰会有助于保护你。" 

4.4 这是个巧妙的表达式:  a ^= b ^= a ^= b  它不需要临时变量就可以交换 a 和 b 的值。

    这不具有可移植性。它试图在序列点之间两次修改变量 a, 而这是无定义的。例如,有人报告如下代码: 

    int a = 123, b = 7654;
    a ^= b ^= a ^= b;

在 SCO 优化 C 编译器 (icc) 下会把 b 置为 123, 把 a 置为 0。参见问题 3.13.7 和 20.14

4.8 那么, 对于 a[i] = i++; 我们不知道 a[] 的哪一个分量会被改写,但 i 的确会增加 1, 对吗?

    不一定!如果一个表达式和程序变得未定义, 则它的所有方面都会变成未定义。参见问题 3.2, 3.3, 11.32 和  11.35。 

12.32 人们好像有些在意实现定义 (implementation-defin-ed)、未明确  (unspecified) 和无定义 (undefined) 行为的区别。它们的区别到底在哪里?

    简单地说: 实现定义意味着实现必须选择某种行为并提供文档。未明确意味着实现必须选择某种行为但不必提供文档。未定义意味着任何事情都可能发生。标准在任何情况下都不强加需求; 前两种情况下, 它有时建议一组可能的行为 (也可能要求从中选择一种)。

    注意既然标准对无定义行为没有强制要求, 编译器就绝对可以做任何事情。特别地, 对程序其它部分的正常运行没有任何保证; 参见问题 3.2, 有一个相对简单的例子。

    如果你对书写可移植代码有兴趣, 你可以忽略它们的区别, 因为通常你都希望避免依赖三种行为中的任何一种。参见问题 3.8 和 11.34

    第四种不那么严格定义的行为是 ``场景特定" (locale-specific)。

本文版权归 蚂蚁的 C/C++ 标准编程 以及 作者 antigloss 共同所有,转载请注明原作者和出处。谢谢。



相关专题:暂无相关专题

上一篇:原来可以这样出书、写书?
下一篇:无相关文章

共有评论 49 条 网友评分 1分 查看全部评论

游客:sally
评分:5
老公最棒了 !
游客:sallyy
评分:0
x = x++ + y++;是先赋值再自增还是先自增再赋值?
游客:antigloss
评分:5
不用管它是什么,反正是标准未定义的,不要这么写就行了。
游客:bsacsa
评分:0
先赋值在自增
游客:dimensioll
评分:0
去看看编译器的原理,而不是拿点外文资料当圣旨。
游客:antigloss
评分:1
编译器是编译器,标准是标准,别混为一谈。标准未定义的东西我们至少不能说它是正确的,更不能确定其结果。对于标准没定义的东西,编译器可以任意选择它喜欢的实现方式。

我不知道你有没有认真看我引用的资料,如果你觉得不合理,能否请你用事实反驳它,而不是抛出一句“去看编译原理”?既然你说到编译原理,那能否请你用编译原理分析一下 x = x++ + y++; 是否正确,它的结果应该是多少?谢谢。
游客:dimensioll
评分:0
#x=x+++y++;\n\t
mov *x,%edi
mov %edi,%esi
add $0x1,%esi
mov %esi,*x
mov *y,%esi
mov %esi,%ebx
add $0x1,%ebx
mov %ebx,*y
add %esi,%edi
mov %edi,*x #END
注意寄存器%edi的使用;
当然结果是不唯一的,我只是想说明知道原因比较好;
我没其他意思,楼上的老兄,对不起了。
游客:antigloss
评分:0
你说的这个似乎叫做汇编代码,不叫做编译原理吧。

其实标准是在考虑了编译原理和不同的系统才制定出来的。标准对某些东西不作定义或者不明确要求,是为了给予编译器最大的自由度选择合适的实现,使得其编译出来的代码运行效率最高。

正如我一再强调的,对于标准没定义的东西,编译器可以任意选择它喜欢的实现方式。所以你能说 x = x++ + y++; 正确吗?它被某个编译器编译可能是一个结果,被另外的编译器编译可能是另外的结果。退一万步来说,就当它是正确的,就当标准定义了它,可是这种风格可取吗?你会使用这种羞涩难懂的代码吗?我看不会吧。

查看全部评论

【发表评论】 评分:1分 2分 3分 4分 5分


验证码:

Powered By Www.Xydw.COM Ver1.14 管理
Copyright © 2005-2006 蚂蚁的 C/C++ 标准编程 All Right Reserved. XCMS
粤ICP备06014124号   站长:Antigloss