论文部分内容阅读
摘 要:C语言中提供的自增运算符能让程序的书写更加简便和灵活,但如果运用不当也会使 程序的运行结果与预期大相径庭,再者,现在的教程中并未从不同编译器的编译执行角度进行深入剖析,使得初学者对一些语句有诸多困惑。本文通过实验,利用不同的环境编写调试程序,并对运行结果进行反汇编分析,总结出自增运算符在不同编译环境中的运算规律以及在教学过程中的注意事项。
关键词:C语言 自增 反汇编
中图分类号:TN925 文獻标识码:A 文章编号:1674-098X(2017)09(c)-0143-03
Abstract: The auto-increasing operator in C language can make writing more convenient and flexible, but it can also make the result different with expected if used undeserved, moreover, the tutorials do not analyze deeply in the angle of different compliers’ comping and executing, and that makes beginners be confused with some codes. This article analyze the result with disassembling in different compliers, then conclude the rules of auto-increasing and notices in the teaching process.
Key Words: C language; Auto-increasing; Disassemble
C语言历经了几十年,成为经典的编程语言,凭借语句的精炼、语法的灵活、对系统环境要求低、拥有较高的执行效率而风靡整个世界,再经过历代改进,C语言发展至今非但未被淘汰,更是广泛的应用于各种生产环境中,并且早已成为计算机专业学生的入门语言。
作为语句精炼的语言,比早期的汇编语言简单了很多,一条语句能表示多个运算步骤或者多次寄存器变化,例如自增语句i++,能够在参加其他运算的同时对本身值进行增加1而又不用增加额外的语句。但是如此精炼的语句却在实际生产环境中会产生歧义导致严重的后果,这么说并不是说自增语句是洪水猛兽,而是由于C语言应用的场景太广泛,早期发展标准尚未成形,不同编译器厂商在语句执行顺序上尤其是复杂语句不尽相同,例如自增语句并列使用的时候,不同编译器对它的解析方式会有不同:(++i)+(++i)+(++i),在不同的编译器计算出的结果是不同的:在VC6.0中结果为10;在VC2012中结果为12;在TurboC中结果是9。那为什么会出现如此大的结果差异呢?首先了解一下自增运算符的运算规则。
1 自增运算符的运算规则
自增运算的规则:自增运算符是一元运算符,它的作用是使变量的值增1。运算符出现在变量的前面称为前缀运算符,运算符出现在变量的后面称为后缀运算符。前缀自增运算符在程序执行过程中遵循“先自增后运算”的规则,后缀自增运算符遵循“先运算后自增”的规则。同理,自减运算符也是一样[1]。下面用两个简单的实验来实际运行一下自增运算符:
实验1:
int main( ){
int a=8,b;
b=a++; /*后缀形式*/
printf(“a=%d, b=%d ”,a,b);
return 0;
}
实验2:
int main( ){
int a=8,b;
b = ++a; /*前缀形式*/
printf(“ a=%d,b=%d”,a,b);
return 0;
}
在实验1中,b = a++; 相当于如下两条语句:b = a; 和a=a+1; 即先将a的初始值8赋值给b,此时b的值即为8,然后a自增1。执行结果是:a=9,b=8。
在实验2中,b = ++a; 相当于如下两条语句:a = a+1; 和 b=a; 即a先做自增1操作,然后再将a自增后的值9赋值给b,此时b的值即为9。执行结果是:a=9,b=9。
也就是说对于自增符号++在变量后面的操作 a++,是先做其他运算,再对a进行自增运算;而自增符号++在变量前面的操作++a,是先对a进行自增运算,再做其他运算[2]。
上面这两种自增操作,是自增操作的最基础版本,而在很多教学资料中会出现类似于求 (i++)+(i++)+(i++) 、++i * ++i / i++ ; 、 (++i)+(++i)+(++i) 等等计算结果的题目分析,由于绝大多数资料中分析的方法不同,且都从语法的角度来进行解析,而非从编译器的操作流程入手,从而给出的解释各异,本文就针对两个不同的编译平台对这段程序进行分析。
在当前的C语言教学中,教师们最常用的编译器是微软公司出品的VC6.0,VS2012或者更新的版本,而由于编译器内部对C语言的语法解析有些不同,导致上例:当i=1的时候,(++i)+(++i)+(++i) 在不同的编译器上得到的结果完全不同,本文针对这个式子,在VC6.0 和 VS2012 两个平台上的具体计算过程做一个对比。
用来做分析的式子为:(++i)+(++i)+(++i),写一个简单的示例程序以使其能在上述两个编译器中能运行:
int main() { int i = 1;
int result = (++i)+(++i)+(++i);
printf(“result=%d\n”,result);
return 0;
}
运行结果:
在两款编译器中计算得到的结果分别是10和12。
接下来在两款编译器中分别启动单步调试,然后右键选择查看反汇编,我们能看到每一条语句在汇编中的执行顺序是如何的:
首先看示例程序在VC6.0中的解析过程,我们重点看 int result=(++i)+(++i)+(++i);这条语句下面的反汇编代码:
通过查看上面的反汇编代码,可以感受到一个普通的C语言语句:int result=(++i)+(++i)+(++i);在具体的运算过程中会执行多少操作,凝练了多少步骤,如果是纯粹的机器语言,步骤还会更多,从此可见C语言如此精炼。
言归正传,我们通过对上面反汇编代码的分析,能看出来编译器是将自增操作执行两次之后(变量i从1变到3),再对这两个数进行了相加运算,得到结果为6,之后再对变量i做了一次自增操作(变量i从3变到4),再同之前的结果6进行了相加运算,结果为10。
其中[ebp-4] 代表的是变量i,eax、ecx、edx是寄存器,运算过程简析:
1:将变量i的值存到寄存器eax中,此时寄存器eax值为1。
2:将寄存器eax中的值加1存到寄存器eax中,此时寄存器eax值为2 。
3:将寄存器eax中的值传递给变量i,此时变量i的值是2。
4:将变量i的值存放到寄存器ecx中,寄存器ecx此时为2。
5:将寄存器ecx中的值加1存到寄存器ecx中,此时寄存器ecx值为3。
6:将寄存器ecx中的值传递给变量i,此时变量i的值是3。
7:将变量i的值存到寄存器edx中,此时寄存器edx值为3。
8:将变量i的值同寄存器edx中的值相加,存放到寄存器edx中,此时寄存器edx值为6。
9:将变量i的值存到寄存器寄存器eax中,此时寄存器eax值为3。
10:将寄存器eax中的值加1存到寄存器eax中,此时寄存器eax值为4。
11:将寄存器eax中的值传递给变量i,此时变量i的值是4。
12:将变量i的值同寄存器edx中的值相加存放到寄存器edx中,此时寄存器edx中的值是10。
13:将寄存器 edx中的值传递到变量i中,变量i的值为10,最后再赋值给变量result,变量result的值就是10 。
接下来我们看VS2012中是如何解析的:
在int result=(++i)+(++i)+(++i);这条语句下面的反汇编代码如下:
通过上面反汇编代码的查看,能看出来编译器是将自增操作执行三次之后(变量i从1变到4),再对这三个数进行了相加运算,得到结果为12。
[i]代表的是变量i(同VC6.0中的[ebp–4]是相同的含义),eax、ecx、edx是寄存器,运算过程简析:
1:将变量i的值存到寄存器eax中,此时寄存器eax值为1。
2:将寄存器eax中的值加1存到eax中,此时寄存器eax值为2 。
3:将寄存器eax中的值传递给变量i,此时变量i的值是2。
4:将变量i的值存放到寄存器ecx中,寄存器ecx此时为2。
5:将寄存器ecx中的值加1存到寄存器ecx中,此时寄存器ecx值为3。
6:将寄存器ecx中的值传递给变量i,此时变量i的值是3。
7:将变量i的值存到寄存器edx中,此时寄存器edx值为3。
8:将寄存器edx中的值加1存到寄存器edx中,此时寄存器edx值为4。
9:将寄存器edx中的值传递给变量i,此时变量i的值是4。
10:将变量i的值传递给寄存器eax,此时寄存器eax值为4。
11:将变量i的值同寄存器eax中的值相加存放到寄存器eax中,此時寄存器eax的值是8。
12:将变量i的值同寄存器eax中的值相加存放到寄存器edx中,此时寄存器eax中的值是12。
13:将寄存器eax中的值传递到变量result中,变量result的值就是12。
2 结语
通过前面两个实验,我们能看出来,不同版本的编译器,在汇编的过程对程序语法做了不同的解析,这个是我们编程人员无法控制的,并且C语言最主要的应用领域就是硬件程序开发、驱动程序开发、编解码、算法优化方向的开发,在这些开发中,由于程序逻辑相对复杂,如果我们在编写程序的过程中过分简化程序行数,使用优先级不明确或者是不同编译器不明确的运算方法,会导致很严重的错误,类似于上例的程序,在程序出现异常的情况下,开发人员往往很难定位错误出现的位置以及原因,会极大的浪费时间,作为教师应该要求学生养成良好的编程习惯,用科学、严谨的态度对待遇到的问题,在熟练掌握基规律之后,编写程序之时,有选择地小心谨慎地使用自增(自减)运算符来简化程序,在易错的地方可用其他方法来代替,从而保证程序的执行万无一失[3]。
参考文献
[1] 张秀建.试析C语言中的自增自减运算符[J].电脑编程技巧与维护,2017(11):22-24,64.
[2] 阚钿玉.C语言中自增(自减)运算符号的应用于分析[J].现代计算机,2016(15):40-43.
[3] 唐婷,吕浩音.C语言自增(自减)运算符运算规律的探讨[J].陇东学院学报,2016(5):8-11.
关键词:C语言 自增 反汇编
中图分类号:TN925 文獻标识码:A 文章编号:1674-098X(2017)09(c)-0143-03
Abstract: The auto-increasing operator in C language can make writing more convenient and flexible, but it can also make the result different with expected if used undeserved, moreover, the tutorials do not analyze deeply in the angle of different compliers’ comping and executing, and that makes beginners be confused with some codes. This article analyze the result with disassembling in different compliers, then conclude the rules of auto-increasing and notices in the teaching process.
Key Words: C language; Auto-increasing; Disassemble
C语言历经了几十年,成为经典的编程语言,凭借语句的精炼、语法的灵活、对系统环境要求低、拥有较高的执行效率而风靡整个世界,再经过历代改进,C语言发展至今非但未被淘汰,更是广泛的应用于各种生产环境中,并且早已成为计算机专业学生的入门语言。
作为语句精炼的语言,比早期的汇编语言简单了很多,一条语句能表示多个运算步骤或者多次寄存器变化,例如自增语句i++,能够在参加其他运算的同时对本身值进行增加1而又不用增加额外的语句。但是如此精炼的语句却在实际生产环境中会产生歧义导致严重的后果,这么说并不是说自增语句是洪水猛兽,而是由于C语言应用的场景太广泛,早期发展标准尚未成形,不同编译器厂商在语句执行顺序上尤其是复杂语句不尽相同,例如自增语句并列使用的时候,不同编译器对它的解析方式会有不同:(++i)+(++i)+(++i),在不同的编译器计算出的结果是不同的:在VC6.0中结果为10;在VC2012中结果为12;在TurboC中结果是9。那为什么会出现如此大的结果差异呢?首先了解一下自增运算符的运算规则。
1 自增运算符的运算规则
自增运算的规则:自增运算符是一元运算符,它的作用是使变量的值增1。运算符出现在变量的前面称为前缀运算符,运算符出现在变量的后面称为后缀运算符。前缀自增运算符在程序执行过程中遵循“先自增后运算”的规则,后缀自增运算符遵循“先运算后自增”的规则。同理,自减运算符也是一样[1]。下面用两个简单的实验来实际运行一下自增运算符:
实验1:
int main( ){
int a=8,b;
b=a++; /*后缀形式*/
printf(“a=%d, b=%d ”,a,b);
return 0;
}
实验2:
int main( ){
int a=8,b;
b = ++a; /*前缀形式*/
printf(“ a=%d,b=%d”,a,b);
return 0;
}
在实验1中,b = a++; 相当于如下两条语句:b = a; 和a=a+1; 即先将a的初始值8赋值给b,此时b的值即为8,然后a自增1。执行结果是:a=9,b=8。
在实验2中,b = ++a; 相当于如下两条语句:a = a+1; 和 b=a; 即a先做自增1操作,然后再将a自增后的值9赋值给b,此时b的值即为9。执行结果是:a=9,b=9。
也就是说对于自增符号++在变量后面的操作 a++,是先做其他运算,再对a进行自增运算;而自增符号++在变量前面的操作++a,是先对a进行自增运算,再做其他运算[2]。
上面这两种自增操作,是自增操作的最基础版本,而在很多教学资料中会出现类似于求 (i++)+(i++)+(i++) 、++i * ++i / i++ ; 、 (++i)+(++i)+(++i) 等等计算结果的题目分析,由于绝大多数资料中分析的方法不同,且都从语法的角度来进行解析,而非从编译器的操作流程入手,从而给出的解释各异,本文就针对两个不同的编译平台对这段程序进行分析。
在当前的C语言教学中,教师们最常用的编译器是微软公司出品的VC6.0,VS2012或者更新的版本,而由于编译器内部对C语言的语法解析有些不同,导致上例:当i=1的时候,(++i)+(++i)+(++i) 在不同的编译器上得到的结果完全不同,本文针对这个式子,在VC6.0 和 VS2012 两个平台上的具体计算过程做一个对比。
用来做分析的式子为:(++i)+(++i)+(++i),写一个简单的示例程序以使其能在上述两个编译器中能运行:
int main() { int i = 1;
int result = (++i)+(++i)+(++i);
printf(“result=%d\n”,result);
return 0;
}
运行结果:
在两款编译器中计算得到的结果分别是10和12。
接下来在两款编译器中分别启动单步调试,然后右键选择查看反汇编,我们能看到每一条语句在汇编中的执行顺序是如何的:
首先看示例程序在VC6.0中的解析过程,我们重点看 int result=(++i)+(++i)+(++i);这条语句下面的反汇编代码:
通过查看上面的反汇编代码,可以感受到一个普通的C语言语句:int result=(++i)+(++i)+(++i);在具体的运算过程中会执行多少操作,凝练了多少步骤,如果是纯粹的机器语言,步骤还会更多,从此可见C语言如此精炼。
言归正传,我们通过对上面反汇编代码的分析,能看出来编译器是将自增操作执行两次之后(变量i从1变到3),再对这两个数进行了相加运算,得到结果为6,之后再对变量i做了一次自增操作(变量i从3变到4),再同之前的结果6进行了相加运算,结果为10。
其中[ebp-4] 代表的是变量i,eax、ecx、edx是寄存器,运算过程简析:
1:将变量i的值存到寄存器eax中,此时寄存器eax值为1。
2:将寄存器eax中的值加1存到寄存器eax中,此时寄存器eax值为2 。
3:将寄存器eax中的值传递给变量i,此时变量i的值是2。
4:将变量i的值存放到寄存器ecx中,寄存器ecx此时为2。
5:将寄存器ecx中的值加1存到寄存器ecx中,此时寄存器ecx值为3。
6:将寄存器ecx中的值传递给变量i,此时变量i的值是3。
7:将变量i的值存到寄存器edx中,此时寄存器edx值为3。
8:将变量i的值同寄存器edx中的值相加,存放到寄存器edx中,此时寄存器edx值为6。
9:将变量i的值存到寄存器寄存器eax中,此时寄存器eax值为3。
10:将寄存器eax中的值加1存到寄存器eax中,此时寄存器eax值为4。
11:将寄存器eax中的值传递给变量i,此时变量i的值是4。
12:将变量i的值同寄存器edx中的值相加存放到寄存器edx中,此时寄存器edx中的值是10。
13:将寄存器 edx中的值传递到变量i中,变量i的值为10,最后再赋值给变量result,变量result的值就是10 。
接下来我们看VS2012中是如何解析的:
在int result=(++i)+(++i)+(++i);这条语句下面的反汇编代码如下:
通过上面反汇编代码的查看,能看出来编译器是将自增操作执行三次之后(变量i从1变到4),再对这三个数进行了相加运算,得到结果为12。
[i]代表的是变量i(同VC6.0中的[ebp–4]是相同的含义),eax、ecx、edx是寄存器,运算过程简析:
1:将变量i的值存到寄存器eax中,此时寄存器eax值为1。
2:将寄存器eax中的值加1存到eax中,此时寄存器eax值为2 。
3:将寄存器eax中的值传递给变量i,此时变量i的值是2。
4:将变量i的值存放到寄存器ecx中,寄存器ecx此时为2。
5:将寄存器ecx中的值加1存到寄存器ecx中,此时寄存器ecx值为3。
6:将寄存器ecx中的值传递给变量i,此时变量i的值是3。
7:将变量i的值存到寄存器edx中,此时寄存器edx值为3。
8:将寄存器edx中的值加1存到寄存器edx中,此时寄存器edx值为4。
9:将寄存器edx中的值传递给变量i,此时变量i的值是4。
10:将变量i的值传递给寄存器eax,此时寄存器eax值为4。
11:将变量i的值同寄存器eax中的值相加存放到寄存器eax中,此時寄存器eax的值是8。
12:将变量i的值同寄存器eax中的值相加存放到寄存器edx中,此时寄存器eax中的值是12。
13:将寄存器eax中的值传递到变量result中,变量result的值就是12。
2 结语
通过前面两个实验,我们能看出来,不同版本的编译器,在汇编的过程对程序语法做了不同的解析,这个是我们编程人员无法控制的,并且C语言最主要的应用领域就是硬件程序开发、驱动程序开发、编解码、算法优化方向的开发,在这些开发中,由于程序逻辑相对复杂,如果我们在编写程序的过程中过分简化程序行数,使用优先级不明确或者是不同编译器不明确的运算方法,会导致很严重的错误,类似于上例的程序,在程序出现异常的情况下,开发人员往往很难定位错误出现的位置以及原因,会极大的浪费时间,作为教师应该要求学生养成良好的编程习惯,用科学、严谨的态度对待遇到的问题,在熟练掌握基规律之后,编写程序之时,有选择地小心谨慎地使用自增(自减)运算符来简化程序,在易错的地方可用其他方法来代替,从而保证程序的执行万无一失[3]。
参考文献
[1] 张秀建.试析C语言中的自增自减运算符[J].电脑编程技巧与维护,2017(11):22-24,64.
[2] 阚钿玉.C语言中自增(自减)运算符号的应用于分析[J].现代计算机,2016(15):40-43.
[3] 唐婷,吕浩音.C语言自增(自减)运算符运算规律的探讨[J].陇东学院学报,2016(5):8-11.