基本上, 未定义最能让具体编译器天马行空, 为所欲为. 包括编译失败. 这可能是最省事的做法.
举例说不可移植的特性, 或错误的程序结构(gcc的__attribute__((***)), 或是在表达式中允许块结构, 算不算是), 或是错误的数据(这个想不出例子). 对出现这些情况, 而本标准又对具体实现未作要求的. 算作未定义行为.
举例是整型溢出.
标准中给定了2种或更多的选择, 除此之外标准没有额外地要求什么情况该选择哪种行为.
这里把unspecified的翻译为"不确定的", 我觉得有些不妥, 这里的解释说, unspecified, 跟未定义类似, 但无需提供文档. 这加剧了我的一些迷惑, 原因是这个C FAQ应该也是相当的权威. "类似"是个模糊的词, 不中心解惑这个问题. 另外, 至少unspecified 在一些明确的方面和未定义不同, 那就是, 实现者的行为上限是编译失败(包括编译失败), 虽然我想不出一个编译器所能引起的比编译失败更严重的后果, 但正如它所说, 未定义行为比你想象的还要未定义, 或许它可以让电脑死机, 引发太阳黑子.
该书中的最后陈述乍一看很有建设性. 那就是, 不管这三种行为谁是谁, 你全都要避免踩到雷区. 要求也不小, 你要能记住语言中所有的 implement-defined, unspecified, undefined的行为. 不容易.
但是, 看了The New C standard之后, 就会知道, 要避免所有的unspecified行为几乎不可能, 比如b+c 表达中, b和c谁先被读取就是未指定的. 该书中说:
A blanket guideline recommendation prohibiting the use of any construct whose behavior is unspecified would be counterproductive.
光是 unspecified 的行为, 我统计了一下, 50条(统计完这个我发现<>中就有这么一句话, 所以我只统计了这一个, undefined的个数是直接从该书引用了, 190个. )
从这50条分析来看, 并非所有的未指定行为, 都是在语言标准中给了可选方案, 让实现者做选择就可以了, 比如对静态变量的初始化的方式和时机. 标准中就没有给出任何建议的方案.
下面我把C99 标准中的这几页PDF 抽出来, 单存成一个文件. 有兴趣的可以自己看看.
|
文件: | C99_unspecified_behavior.pdf |
大小: | 53KB |
下载: | 下载 |
|
关于这三个概念, 在C-A reference manual中怎么解释, 不幸的是, 我搜遍了这本书的PDF文件, 找到的多处unspecified, 都不是解释这个概念本身的. 也许这本书中没有对此的解释.
现在, 除了C-A reference manual一书, 我们还有The New C Standard(这本电子书可以在网上免费下载.), 一本逐句解释ANSI C标准, 长度超1600页的大部头经典.
读了相关的解释, 果然受用, 整个事态逐渐明朗了.
这本书的注释澄清了下面的几个问题:
我一开始以为的 implement-defined行为是三者之中最稳定可靠的, 这种想法是不对的, unspecified行为有多种原因, 一种原因是虽然某些行为的细节是unspecified的, 但整个程序的输出对于所有可能的unspecified行为却可能不受影响, 有些行为如果要明确指定, 则需要描述当时的上下文环境, 控制流的分析, 表达式的树结构, 代码优化算法, 所以虽然理论上可以对推测出象求值顺序这样问题, 但这么做是很不现实的.
另一个澄清的问题是, unspecified与undefined的一个重要区别是, 前者是针对合法的程序, 后者则是针对非法的程序.
The New C standard, 名字有些误导, 它不是在定义另一个新的C语言标准, 而是全景式地解读既有的C99标准. 强烈推荐给拿C当回事的程序员放在手边参考. 个人认为有了这本书, 普通人读ANSI C标准不再是那么痛苦的事了.