Board logo

标题: 让我耿耿于怀的一道笔试题 (关于++和--) [打印本页]

作者: 默数悲伤    时间: 2006-4-11 13:17     标题: 让我耿耿于怀的一道笔试题 (关于++和--)

让我耿耿于怀的一道笔试题
来源:蚂蚁的 C/C++ 标准编程 作者:antigloss

    今天去一家公司笔试,迟到了半小时。幸好题目不难,都是很基础的题目,一路下来做得好不痛快。唯独有一道题让我耿耿于怀。题目大致如下:
        以下程序的输出结果是什么?
        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++ 则非法 (参见问题 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 在同一表达式的其它地方被引用, 这会导致无定义的结果, 无从判断该引用(左边的 a 中)是旧值还是新值。(注意, 尽管在 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.1、3.7 和 20.14。
4.8 那么, 对于 a = 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)。


作者: 无条件为你    时间: 2006-4-11 15:52     标题: 让我耿耿于怀的一道笔试题 (关于++和--)

如果让我回答这个问题,我也会说“输出不确定,不同的编译器可能导致不同的输出”。
类似问题我以前专门试过。对于楼主在文章开头给出的题目,我的编译器运行结果为  5 9




欢迎光临 黑色海岸线论坛 (http://bbs.thysea.com/) Powered by Discuz! 7.2