关于C编译器对--运算编译的研究

时间:2022-07-27 02:08:31

关于C编译器对--运算编译的研究

摘要:针对不同编译器对C语言中--运算编译的不同,分析研究了Turbo C 2.0编译器和Visual C++ 6.0编译器对C语言的--运算的编译方式,介绍了这两个编译器汇编代码的查看方式,并通过对不同情况下两种编译器对--运算的编译例子进行研究,明确指出这两种编译器对--运算有不同的编译方式,并在例子中对所存在的不同进行了部分说明。

关键词:C语言;C编译器;--运算;编译

中图分类号:TP312文献标识码:A文章编号:1009-3044(2010)18-5093-03

Study on C Compilers to -- Operation Compilation

WANG Bao-sheng, HE Jin-zhi

(Computer Science and Technology Department, Nanyang Institute of Technology, Nanyang 473004, China)

Abstract: According to difference of the compiler to C operation compilation in C language, the paper analysis and studies the methods of Turbo C 2.0 compiler and Visual C++ 6.0 compiler about the compilation of -- operation in C language, introduces the way of viewing the assembly codes of both the two compilers. By studying examples of both the two compilers to -- operation compilation in different circumstances, specifies that both the two compilers have different modes of compilation to -- operation, and describes some of the difference in the examples between the two compilers.

Key words: C programming language; C compiler; -- operation; compilation

C语言是高校计算机课程的重要部分,同时也是全国计算机等级考试的一部分。目前C语言的学习者越来越多,对C语言的了解也越来越清楚。然而在C语言教学中发现很多学生对其中的自加自减运算符感到迷惑,特别是二者出现前置后置混合使用时。同时,C语言教学中常用的两个编译器,即Turbo C 2.0编译器和Visual C++ 6.0编译器,对自加自减运算的编译顺序有不同,更加重了学生对此部分内容的不解。因此,本文着重分析上述两种编译器如何对自减运算进行编译,对于自加运算读者可参考文中自减运算进行分析。

1 C语言自加和自减运算

C语言的自减运算符为--,有前置和后置两种用法。前置用法指将运算符放于变量的前面,其功能是在使用变量前,先使变量的值自减1,之后再使用变量的值。后置用法指将运算符放于变量的后面,其功能是先使用变量原来的值参加表达式运算,之后再使变量的值自减1。例如下面的C语句:

inti, j;

i=4; j=--i;/*先将i的值自减1,再将i的新值赋给j,结果i为3,j为3*/

i=4; j=i--;/*先将i的原值赋给j,再将i自减1,结果i为3,j为4*/

很显然,不管是前置用法还是后置用法,对变量本身的影响都是相同的即自减1,受影响的是所在表达式的值。表达式中仅包含单个自减运算时Turbo C 2.0编译器和Visual C++ 6.0编译器的解释相同,但当表达式中出现不止一个自减运算时,Turbo C 2.0编译器和Visual C++ 6.0编译器的解释就可能不同。

2 两种编译器中汇编代码的查看

计算机程序如何运行依赖于该语言的编译器,因此通过对编译器的编译结果进行分析是了解编译器如何对运算进行编译的有效方法。

Turbo C 2.0编译器提供有许多工具,其中的Tcc.exe就是一个C语言的编译器,可以将代码编译成目标文件,并能自动调用tlink链接生成可执行文件。Tcc.exe有多种命令行参数,其中使用-S可以生成汇编代码。例如Tcc.exe CS yourfilename.c将生成yourfilename.c源文件的汇编代码文件yourfilename.ASM,进而可对该文件进行汇编代码分析。

Visual C++ 6.0编译器有汇编窗口,可以以单步运行方式运行程序,之后选择View/Debug Window/Disassembly命令,进入汇编窗口查看对应于源程序的汇编代码。

3 自减运算的单一前置或后置

当出现多个单一前置或多个单一后置时,如何编译又分不同情况。例如下面的程序:

main( )

{int a, k;

a=4;k=(a--)+(a--)+(a--);printf("%d",k); /* 运算1 */

a=4;printf("%d", (a--)+(a--)+(a--));/* 运算2 */

a=4;k=(--a)+ (--a)+ (--a);printf("%d",k);/* 运算3 */

a=4;printf("%d", (--a)+ (--a)+ (--a)); /* 运算4 */

}

当以Turbo c 2.0编译器编译后运行结果为:12936。其解释方式如下:

1)对运算1:因赋值表达式右式的均为自减后置,则先一次性取3个子表达式(a--)的值顺次进行加运算,即((4+4)+4)=12,之后再统一进行a的三次自减操作。则a最终为1,输出结果12。

2)对运算2:格式输出函数的实参为一个加运算表达式,求值时从左向右进行,因为是自减后置,每个子表达式在将a的原值作为子表达式值后直接进行自减操作,所以表达式的值为((4+3)+2)=9。则a最终仍为1,输出结果为9。

3)对运算3:因赋值表达式右式的均为自减前置,则先对a顺次进行3次自减操作变为1,之后再进行3个a的算术加。则a最终为1,输出结果为3。

4)对运算4:格式输出函数的实参为一个加运算表达式,求值时从左向右进行,因为是自减前置,每个子表达式在将a的进行自减操作后将其新值作为子表达式值,所以表达式的值为((3+2)+1)=6。则a最终仍为1,输出结果为9。

当以Visual C++ 6.0编译器编译后运行结果为:121255。其解释方式如下:

1)对运算1:因赋值表达式右式的均为自减后置,则先一次性取3个子表达式(a--)的值顺次进行加运算,即((4+4)+4)=12,之后再统一进行a的三次自减操作。则a最终为1,输出结果12。

2)对运算2:格式输出函数的实参为一个加运算表达式,求值时从左向右进行,而编译器将算术加的优先级视为高于后置自减,所以仍先一次性取3个子表达式(a--)的值顺次进行加运算,即((4+4)+4)=12,之后再统一进行a的三次自减操作。则a最终仍为1,输出结果为12,运算过程与运算1类似。

3)对运算3:因赋值表达式右式的均为自减前置,而编译器将算术加的优先级视为低于前置自减,同时运算从左向右逐次进行,则先对a顺次进行2次自减操作变为2,之后求出前两个子表达式之和为4,之后再进行a的第3次自减变为1,之后再与之前的和相加得5,即((2+2)+1)=5。则a最终为1,输出结果为5。

4)对运算4:格式输出函数的实参为一个加运算表达式,求值时从左向右进行,因为是自减前置,同时运算从左向右逐次进行,则先对a顺次进行2次自减操作变为2,之后求出前两个子表达式之和为4,之后再进行a的第3次自减变为1,之后再与之前的和相加得5,即((2+2)+1)=5。则a最终为1,输出结果为5,运算过程与运算3类似。

4 自减的前置和后置混合使用

当同时出现前置和后置时,如何编译又分不同情况。例如下面的程序:

main( )

{int a, k;

a=4;k=(a--)+(--a)+(--a);printf("%d",k);/* 运算5 */

a=4;printf("%d", (a--)+(--a)+(--a)); /* 运算6 */

a=4; printf("%d%d%d%d", ++a, --a, a++, a-- );/* 运算7 */

a=8; printf("%d%d", (a--)+(--a)+(--a), (a--)+(--a)+(--a) );/* 运算8 */

}

当以Turbo c 2.0编译器编译后运行结果为:6743341019。其解释方式如下:

1)对运算5:对赋值表达式k=(a--)+(--a)+(--a),编译器按照自减前置优先级高于子表达式求值,子表达式求值高于自减后置的顺序进行。所以先进行两次a的自减操作使a的值变为2,之后将a的值分别作为三个子表达式的值进行求和并赋给k,则k的值为6,之后再进行一次a的后置自减使a的值变为1。则a最终为1,输出结果为6。

2)对运算6:格式输出函数的实参为一个加运算表达式,顺次将两两子表达式进行相加,即(((a--)+(--a))+(--a)),对每一个子表达式求值时其中包括的自减操作不论前置、后置均进行完才进行下一个子表达式的求值。所以先求子表达式(a--)的值为4并将a的值自减变为3,之后子表达式(--a)求值时先a自减变为2,再将新值2和上一个子表达式值4相加得6,之后进行第3个子表达式(--a)的求值,a先自减变为1,再将新值1和前两个子表达式之和6相加得7,即对应值为(((4)+(2))+(1))。则a最终为1,输出结果为7。

3)对运算7:此时格式输出函数的输出项列表有四个子表达式,Turbo c 2.0编译器按照从右至左的顺序进行求值,对每一个子表达式求值时其中包括的自减操作不论前置、后置均进行完才进行下一个子表达式的求值。所以先进行(a--)得第4个输出值为4并将a置为3,之后进行(a++)得第3个输出值为3并将a置为4,再之后进行(--a)将a置为3并求得第2个输出值为3,最后进行(++a)将a置为4并求得第1个输出值为4。则最终a为4,输出结果为4334。

4)对运算8:此运算和运算9情况类似,两个子表达式((a--)+(--a)+(--a)) 求值时其中包括的自减操作不论前置、后置均进行完才进行下一个子表达式的求值,而每个子表达式求值和上述运算6一样。所以第2个输出项的对应值为((8)+(6)+(5))并将a置为5,第1个输出项的对应值为((5)+(3)+(2))并将a置为2。则最终a为2,输出结果为1019。

当以Visual C++ 6.0编译器编译后运行结果为:8843441420。其解释方式如下:

1)对运算5:对赋值表达式的右式,编译器首先从左到右两两子表达式进行加运算,每次运算中前置自减优先级高于子表达式求值,而后置自减则放于整体赋值之后统一进行,即k=(((a--)+(--a))+(--a))。对((a--)+(--a))先进行一次a的前置自减使a的值变为3,之后将a的新值分别作为两个子表达式的值进行加运算得和为6,此时不进行a的后置自减而是继续求下一个子表达式,第3个子表达式(--a)先进行a的前置自减使a变为2并将该值作为子表达式的值与前面两个子表达式之和6相加得8并赋给k,之后再进行a的后置自减使a的值变为1。即k对应值为(((3)+(3))+(2)),a最终为1,输出结果为8。

2)对运算6:格式输出函数的实参为一个加运算表达式,但Visual C++ 6.0编译器求值时和对运算5的求值相似,即总体按(((a--)+(--a))+(--a))进行。对((a--)+(--a))先进行一次a的前置自减使a的值变为3,之后将a的新值分别作为两个子表达式的值进行加运算得和为6,此时不进行a的后置自减而是继续求下一个子表达式,第3个子表达式(--a)先进行a的前置自减使a变为2并将该值作为子表达式的值与前面两个子表达式之和6相加得8并作为实参值输出,之后再进行a的后置自减使a的值变为1。即实参值为(((3)+(3))+(2)),a最终为1,输出结果为8。

3)对运算7:此时格式输出函数的输出项列表有四个子表达式,Visual C++ 6.0编译器按照从右至左的顺序进行求值,每次运算中前置自减优先级高于子表达式求值,而后置自减则放于各个输出项的值确定之后统一进行。所以先进行(a--)得第4个输出值为4而a不变,之后进行(a++)得第3个输出值为4而a仍保持原值,再之后进行(--a)将a置为3并求得第2个输出值为3,最后进行(++a)将a置为4并求得第1个输出值为4。则最终a为4,输出结果为4344。

4)对运算8:此运算和运算9情况类似,两个子表达式((a--)+(--a)+(--a)) 求值时每次运算中前置自减优先级高于子表达式求值,而后置自减则放于各个输出项的值确定之后统一进行,而每个子表达式求值和上述运算6一样。所以第2个输出项的对应值为((7)+(7)+(6))并将a置为6,第1个输出项的对应值为((5)+(5)+(4)),之后统一进行两次(a--)置a值为2。则最终a为2,输出结果为1420。

5 结论

通过研究Turbo c 2.0编译器和Visual C++ 6.0编译器的汇编代码可以看到,两种编译器对自加自减运算的编译是不同的。所以对于C语言的学习者而言,在学习过程中应对所使用的编程环境加以区分,并掌握对这两种编译器的汇编代码的查看方法以帮助自己加深对编译器的理解。

参考文献:

[1] 刘克成.C语言程序设计[M].北京:中国铁道出版社,2007:42-50.

[2] 谭浩强.C程序设计[M].北京:清华大学出版社,1998:30-35.

[3] 张瑞红,余志超.关于C编译器对++运算编译的研究[J].内蒙古民族大学学报:自然科学版,2006,21(4):142-144.

上一篇:探究高校计算机专业的课程整合 下一篇:基于USB的伺服电机的通信研究