fbpx
维基百科

未定义行为

计算机程序设计中,未定义行为(英語:undefined behavior)是指执行某种计算机代码所产生的结果,这种代码在当前程序状态下的行为在其所使用的语言标准英语Programming_language_specification中没有规定。常见于翻译器源代码存在某些假设,而执行时这些假设不成立的情况。

一些编程语言中,某些情况下存在未定义行为,以CC++最为著名[1]。在这些语言的标准中,规定某些操作的语义是未定义的,典型的例子就是程序错误的情况,比如越界访问数组元素。标准允许语言的具体实现做这样的假设:只要是符合标准的程序代码,就不会出现任何类似的行为。具体到 C/C++ 中,编译器可以选择性地给出相应的诊断信息,但没有对此的强制要求:针对未定义行为,语言实现作出任何反应都是正确的,类似于数字逻辑中的无关项英语Don't-care term。虽然编译器实现可能会针对未定义行为给出诊断信息,但保证编写的代码中不引发未定义行为是程序员自己的责任。这种假设的成立,通常可以让编译器对代码作出更多优化,同时也便于做更多的编译期检查和静态程序分析

有时候也可能存在对于未定义行为本身的限制性要求。例如,在CPU指令集说明中可能将某些形式的指令定为未定义,但如果该CPU支持内存保护,说明中很可能会还会包含一条兜底的规则,要求任何用户态的指令都不会让操作系统的安全性受损;这样一来,在执行未定义行为的指令时,就允许CPU破坏用户寄存器,但不允许发生诸如切换到监控模式的操作。

未指定行为英语unspecified behavior(unspecified behavior)不同,未定义行为强调基于不可移植或错误的程序构造,或使用错误的数据。一个符合标准的实现可以在假定未定义行为永远不发生(除了显式使用不严格遵守标准的扩展)的基础上进行优化,可能导致原本存在未定义行为(例如有符号数溢出)的程序经过优化后显示出更加明显的错误(例如死循环)。因此,这种未定义行为一般应被视为bug。

好处 编辑

如果某一操作在文档中被定为未定义行为,编译器就可以假设该操作在符合标准的程序中永远不会发生。这样,编译器就可以得到更多的信息,获得更多优化程序的机会。

例如这样的C语言代码:

int foo(unsigned char x) {  int value = 2147483600; /* 假设 int 是 32 位 */  value += x;  if (value < 2147483600)  bar();  return value; } 

因为 xunsigned char 不可能为负数,而C语言中有符号整数的溢出又是未定义行为,编译器就可以假设执行 if 语句时 value 不可能小于 2147483600。因为这里的 if 没有副作用,条件也永远不成立,所以编译器就可以直接忽略 if 语句和对函数 bar 的调用。于是,上述代码在语义上就等价于:

int foo(unsigned char x) {  int value = 2147483600;  value += x;  return value; } 

如果有符号整数的溢出有明确的「环绕」行为,那么这样的程序转化就是非法的。

代码越复杂,类似的优化就越难被人类发现。如果代码同时还有其它方面的优化,例如内联展开,就更难发现了。

让有符号整数溢出未定义还有另一个好处:存储、操作变量的值时,可以在比变量本身更大的寄存器中进行。假设源代码中变量的类型比原生寄存器的宽度要窄(比如常见的在64位机器上的int类型),那么编译器就可以在生成机器码时把这个变量当作64位有符号数,对代码的语义没有任何影响。反之,如果32位有符号整数的溢出有明确定义,那么在针对64位机器编译时,编译器就必须插入额外的逻辑确保行为符合预期,因为大多数机器码指令在溢出时行为与寄存器的宽度有关。[2]

更重要的一点是,有符号整数溢出的行为未定义,允许在编译期检查、静态程序分析、运行期检查时捕捉这类错误的情况;如果溢出行为有明确定义,就无法进行编译期检查。

C和C++的未定义行为的一些例子 编辑

尝试修改字符串字面量英语string literal会产生未定义行为:[3]

char * p = "wikipedia"; // C++11中错误,C++98/C++03不推荐使用 p[0] = 'W'; // 未定义行为 

防止这一点的方法之一是将它定义为数组而不是指针

char p[] = "wikipedia"; /* 正确 */ p[0] = 'W'; 

在C++可以使用标准模板库中的string类型,如下所示:

std::string s = "wikipedia"; /* 正确 */ s[0] = 'W'; 

除以零会导致未定义行为。根据 IEEE 754,float、double和long double类型的值除以零的结果是无穷大或NaN[4]

return x/0; // 未定义行为 

某些指针操作可能导致未定义行为:[5]

int arr[4] = {0, 1, 2, 3}; int* p = arr + 5; // 未定义行为 

到达返回数值的函数(除main函数以外)的结尾,而没有一个return语句,会导致未定义行为:

int f() { } /* 未定义行为 */ 

C程序设计语言》在第2.12节引用下面的代码作为未定义行为的例子:

printf("%d %d\n", ++n, power(2, n)); /* 未定义行为 */ 

以及

a[i] = i++; /* 未定义行为 */ 

标准库可能指定未定义行为,例如:

int x = 1; printf("%d\n", &x); /*未定义行为:%d预期int类型的实际参数*/ printf("%p\n", &x); /*未定义行为:%p预期void*类型的实际参数*/ printf("%p\n", (void*)&x); /*%p和void*类型的实际参数匹配,不在此引发未定义行为*/ 

参考资料 编辑

  1. ^ Lattner, Chris. What Every C Programmer Should Know About Undefined Behavior. LLVM Project Blog. LLVM.org. May 13, 2011 [May 24, 2011]. (原始内容于2014-10-30). 
  2. ^ . [2018-06-21]. (原始内容存档于2018-07-09). 
  3. ^ ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §2.13.4 String literals [lex.string] para. 2
  4. ^ ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §5.6 Multiplicative operators [expr.mul] para. 4
  5. ^ ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §5.7 Additive operators [expr.add] para. 5

外部链接 编辑

  • The Jargon File on "nasal demons" (页面存档备份,存于互联网档案馆),未定义行为的一个可能后果。

未定义行为, 在计算机程序设计中, 英語, undefined, behavior, 是指执行某种计算机代码所产生的结果, 这种代码在当前程序状态下的行为在其所使用的语言标准, 英语, programming, language, specification, 中没有规定, 常见于翻译器对源代码存在某些假设, 而执行时这些假设不成立的情况, 一些编程语言中, 某些情况下存在, 以c和c, 最为著名, 在这些语言的标准中, 规定某些操作的语义是未定义的, 典型的例子就是程序错误的情况, 比如越界访问数组元素, 标准允. 在计算机程序设计中 未定义行为 英語 undefined behavior 是指执行某种计算机代码所产生的结果 这种代码在当前程序状态下的行为在其所使用的语言标准 英语 Programming language specification 中没有规定 常见于翻译器对源代码存在某些假设 而执行时这些假设不成立的情况 一些编程语言中 某些情况下存在未定义行为 以C和C 最为著名 1 在这些语言的标准中 规定某些操作的语义是未定义的 典型的例子就是程序错误的情况 比如越界访问数组元素 标准允许语言的具体实现做这样的假设 只要是符合标准的程序代码 就不会出现任何类似的行为 具体到 C C 中 编译器可以选择性地给出相应的诊断信息 但没有对此的强制要求 针对未定义行为 语言实现作出任何反应都是正确的 类似于数字逻辑中的无关项 英语 Don t care term 虽然编译器实现可能会针对未定义行为给出诊断信息 但保证编写的代码中不引发未定义行为是程序员自己的责任 这种假设的成立 通常可以让编译器对代码作出更多优化 同时也便于做更多的编译期检查和静态程序分析 有时候也可能存在对于未定义行为本身的限制性要求 例如 在CPU的指令集说明中可能将某些形式的指令定为未定义 但如果该CPU支持内存保护 说明中很可能会还会包含一条兜底的规则 要求任何用户态的指令都不会让操作系统的安全性受损 这样一来 在执行未定义行为的指令时 就允许CPU破坏用户寄存器 但不允许发生诸如切换到监控模式的操作 和未指定行为 英语 unspecified behavior unspecified behavior 不同 未定义行为强调基于不可移植或错误的程序构造 或使用错误的数据 一个符合标准的实现可以在假定未定义行为永远不发生 除了显式使用不严格遵守标准的扩展 的基础上进行优化 可能导致原本存在未定义行为 例如有符号数溢出 的程序经过优化后显示出更加明显的错误 例如死循环 因此 这种未定义行为一般应被视为bug 目录 1 好处 2 C和C 的未定义行为的一些例子 3 参考资料 4 外部链接好处 编辑如果某一操作在文档中被定为未定义行为 编译器就可以假设该操作在符合标准的程序中永远不会发生 这样 编译器就可以得到更多的信息 获得更多优化程序的机会 例如这样的C语言代码 int foo unsigned char x int value 2147483600 假设 int 是 32 位 value x if value lt 2147483600 bar return value 因为 x 是 unsigned char 不可能为负数 而C语言中有符号整数的溢出又是未定义行为 编译器就可以假设执行 if 语句时 value 不可能小于 2147483600 因为这里的 if 没有副作用 条件也永远不成立 所以编译器就可以直接忽略 if 语句和对函数 bar 的调用 于是 上述代码在语义上就等价于 int foo unsigned char x int value 2147483600 value x return value 如果有符号整数的溢出有明确的 环绕 行为 那么这样的程序转化就是非法的 代码越复杂 类似的优化就越难被人类发现 如果代码同时还有其它方面的优化 例如内联展开 就更难发现了 让有符号整数溢出未定义还有另一个好处 存储 操作变量的值时 可以在比变量本身更大的寄存器中进行 假设源代码中变量的类型比原生寄存器的宽度要窄 比如常见的在64位机器上的int类型 那么编译器就可以在生成机器码时把这个变量当作64位有符号数 对代码的语义没有任何影响 反之 如果32位有符号整数的溢出有明确定义 那么在针对64位机器编译时 编译器就必须插入额外的逻辑确保行为符合预期 因为大多数机器码指令在溢出时行为与寄存器的宽度有关 2 更重要的一点是 有符号整数溢出的行为未定义 允许在编译期检查 静态程序分析 运行期检查时捕捉这类错误的情况 如果溢出行为有明确定义 就无法进行编译期检查 C和C 的未定义行为的一些例子 编辑尝试修改字符串字面量 英语 string literal 会产生未定义行为 3 char p wikipedia C 11中错误 C 98 C 03不推荐使用 p 0 W 未定义行为 防止这一点的方法之一是将它定义为数组而不是指针 char p wikipedia 正确 p 0 W 在C 可以使用标准模板库中的string类型 如下所示 std string s wikipedia 正确 s 0 W 除以零会导致未定义行为 根据 IEEE 754 float double和long double类型的值除以零的结果是无穷大或NaN 4 return x 0 未定义行为 某些指针操作可能导致未定义行为 5 int arr 4 0 1 2 3 int p arr 5 未定义行为 到达返回数值的函数 除main函数以外 的结尾 而没有一个return语句 会导致未定义行为 int f 未定义行为 C程序设计语言 在第2 12节引用下面的代码作为未定义行为的例子 printf d d n n power 2 n 未定义行为 以及 a i i 未定义行为 标准库可能指定未定义行为 例如 int x 1 printf d n amp x 未定义行为 d预期int类型的实际参数 printf p n amp x 未定义行为 p预期void 类型的实际参数 printf p n void amp x p和void 类型的实际参数匹配 不在此引发未定义行为 参考资料 编辑 Lattner Chris What Every C Programmer Should Know About Undefined Behavior LLVM Project Blog LLVM org May 13 2011 May 24 2011 原始内容存档于2014 10 30 存档副本 2018 06 21 原始内容存档于2018 07 09 ISO IEC 2003 ISO IEC 14882 2003 E Programming Languages C 2 13 4 String literals lex string para 2 ISO IEC 2003 ISO IEC 14882 2003 E Programming Languages C 5 6 Multiplicative operators expr mul para 4 ISO IEC 2003 ISO IEC 14882 2003 E Programming Languages C 5 7 Additive operators expr add para 5外部链接 编辑The Jargon File on nasal demons 页面存档备份 存于互联网档案馆 未定义行为的一个可能后果 取自 https zh wikipedia org w index php title 未定义行为 amp oldid 78389555, 维基百科,wiki,书籍,书籍,图书馆,

文章

,阅读,下载,免费,免费下载,mp3,视频,mp4,3gp, jpg,jpeg,gif,png,图片,音乐,歌曲,电影,书籍,游戏,游戏。