c语言math未定义,C语言中自增自减表达式的未定义行为

摘 要:自增自减运算符是C语言的一个特色,本文通过表达式中对一个变量进行多次自增或自减运算时产生的未定义行为进行了详细介绍,望帮助大家正确使用自增自减运算符。

关键词:C;自增自减运算符;未定义行为

中图分类号:TP312 文献标识码:A 文章编号:1674-7712 (2014) 02-0000-01

一、自增自减运算符

C语言提供了两个用于算术运算的单目运算符:自增(++)和自减(--)。有两种用法:前置运算(先增减后运算),后置运算(先运算后增减)。下类代码段:

在不同的编译器上运行会有不同的结果:

一种是:21,8,一种是:22,8,一种是24,8;

很奇怪吧,一样的表达式不同的编译器,运行结果会不同,虽然可以通过Debug反汇编分析出不同,那为什么会这样?哪个是正确答案?遗憾的是这样的题目是并没有答案,如果真的要给一个答案,可以是未定义行为。

二、C语言中未定义行为(Undefined behavior)

未定义行为是一个非常微妙的话题,在C中许多貌似合理的行为实际上都有着不确定的行为,并且这通常是程序中BUG的源头。未定义行为,就是C标准没有对其进行定义,程序员不能预测会发生什么事的计算机代码,编译器可以随意进行计算,简单说就是语言规格在定义时为了编译器工作的弹性和效率,会刻意不去规定某些规格,如果我们的程序依赖这些没有规定的特性时,就称之为未定义行为,这样的程序语句在不同的编译器上编译会得到不同的结果。另外由“实现”定义的行为,这里的实现指的是C的不同编译器,也是未定义行为。

C中的自增自减运算就属于未定义行为,因为C没有规定i++或者++i的加1动作到底发生在一个语句的那个时刻(序列点)执行,所以不同编译器在不同的序列点上执行加1动作就可能有不同的结果。尽管后缀前缀自增自减操作符++和--分别在输出其旧值之后和输出新值之前执行加1减1运算,但这里的“之后”常常被误解。因为没有任何保证确保后置前置自增自减会在输出变量原值之后以及输出新值之后会对表达式的其它部分进行计算之前以及之后立即进行,也不能保证变量的更新会在表达式完成之前的某个时刻进行,这样就导致结果的不同。这样导致未定义行为会包含多个不确定的副作用,上述表达式是有副作用的,因为在序列点之间,变量的值不能保证行为唯一性,也就是说第一次执行完++i,它在内存中的值可能改变也可能不变。

三、未定义行为的副作用

不确定副作用是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增,自减和赋值操作符的任何组合。C语言允许在一个表达式中使用一个以上的赋值类运算,包括赋值运算符、自增运算符、自减运算符等。这种灵活性使程序简洁,但同时也会引起副作用。副作用主要表现在:使程序费解,并易于发生误解或错误。除此之外,C中的任何的未定义行为都有可能(无论是编译期还是运行期)产生一些格式化你的硬盘的、做一些你完全想象不到的事情的代码,所以不要试图探究这些东西在你的编译器中是如何实现的(这与我们C教科书上的许多练习正好相反)。另外C/C++规定,任何依赖于特定计算顺序、依赖于在顺序点之间实现修改效果的表达式,其结果都没有保证,因此如果在任何完整表达式里存在对同一变量的多次引用,那么表达式里就不应该出现对这一变量的副作用,否则就不能保证得到预期结果,还有这不是在某个系统里试一试的问题,因为我们不可能试验所有可能的表达式组合形式以及所有可能的上下文。但这类表达式在教材中很多,难道只是未定义行为的副作用,再来研究一下这类表达式的合法性。

四、表达式的合法性

计算机科学家Brian W.Kernighan主编的《The C Programming Language》明确指出:自增与自减运算符只能作用于变量,不能应用于表达式以及整数,例如++(a+b)与10++,const int N=0;N++;都是非法的。

为什么?因为这些表达式实际上包括了一个赋值运算,a++等价于a=a+1;C语言中赋值运算符存在左值,只能变量可以作为左值,表达式以及整数不能作为左值,因为它们没有确定的内存地址。因此++(a+b)等价于(a+b)=(a+b)+1是不允许的,以此分析,(a++)+(a++)这个表达式中的后一项是非法的,表面上它只是一个a++,但是在第一个a++的作用下,a已经变成a=a+1;因此第二个的a++则成为(a+1)++了,这是非法的。一般地,在一个表达式中,对同一个变量进行多次自增或自减运算都是非法的。C语言之父Dennis M.Ritchie还提醒,编译器应在这种情况下会给出警告,事实上Linux平台下的编译器GCC确实会对此给出警告(VC++6.0没有):Warning:operation on‘a’may be undefined;

C++Primer解释:使用了未定义行为的程序都是错误的,即使程序能运行也只是巧合,未定义行为源于编译器不能检测到的错误或太麻烦以至无法检测的错误。然而,含有未定义行为的程序在某些编译器中可以正确执行,但并不能保证同一程序在不同编译器中甚至在当前编译器的后继版本中会继续正确运行,也不能保证程序在一组输入上正确运行并在另一组输入上也正确运行。总而言之,绝对不要写这种表达式,否则或早或晚会在某种环境中遇到麻烦。因此书写程序时,要尽可能的保持清晰易懂,代码除了供人阅读以外,简单清晰的程序也不容易出错。

五、未定义行为的使用

既然C、C++、底层虚拟机(LLVM)IR中都有未定义行为,那么肯定有意义,现代计算机是以二进制逻辑运算为基础,“对于确定的输入,其输出是确定的”,行为必然是确定的。但是,如果不为计算机的基础计算模型引入不确定性,人工智能(AI)不可能真正实现。另外为了使C应用程序获得更好的性能,可以通过优化编译器产生高性能,而未定义行为使优化成为可能。

六、结束语

在教科书以及等级考试中,对一个变量在表达式中多次自增自减很常见,本文针对表达式运行结果不同的疑惑进行了解答,详细介绍了表达式中涉及到的未定义行为,希望对大家有正确的指导意义。

参考文献:

[1]谭浩强.C语言程序设计[M].北京:清华大学出版社,2011.

[2]黄玉兰.有关C语言输出函数中的自增自减运算符在不同编译环境中的探讨[J].科技致富向导,2013(20).

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值