在将一个C源程序转换为可执行程序的过程中.docx
- 文档编号:5231418
- 上传时间:2022-12-14
- 格式:DOCX
- 页数:9
- 大小:23.52KB
在将一个C源程序转换为可执行程序的过程中.docx
《在将一个C源程序转换为可执行程序的过程中.docx》由会员分享,可在线阅读,更多相关《在将一个C源程序转换为可执行程序的过程中.docx(9页珍藏版)》请在冰豆网上搜索。
在将一个C源程序转换为可执行程序的过程中
在将一个C源程序转换为可执行程序的过程中
C初学者可能对预处理器没什么概念,这是情有可原的:
一样的C编译器都将预处理,汇编,编译,连接过程集成到一起了.编译预处理往往在后台运行.在有的C编译器中,这些过程统统由一个单独的程序来完成,编译的不同时期实现这些不同的功能.能够指定相应的命令选项来执行这些功能.有的C编译器使用分别的程序来完成这些步骤.可单独调用这些程序来完成.在gcc中,进行编译预处理的程序被称为CPP,它的可执行文件名为cpp.
编译预处理命令的语法与C语言的语法是完全独立的.例如:
你能够将一个宏扩展为与C语法格格不入的内容,但该内容与后面的语句结合在一个若能生成合法的C语句,也是能够正确编译的.
(一)预处理命令简介
--------------------------------------------------------------------------------
预处理命令由#(hash字符)开头,它独占一行,#之前只能是空白符.以#开头的语句确实是预处理命令,不以#开头的语句为C中的代码行.常用的预处理命令如下:
#define 定义一个预处理宏
#undef 取消宏的定义
#include 包含文件命令
#include_next 与#include相似,但它有着专门的用途
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 判定某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判定某个宏是否未被定义
#elif 若#if,#ifdef,#ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else 与#if,#ifdef,#ifndef对应,若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif #if,#ifdef,#ifndef这些条件命令的终止标志.
defined 与#if,#elif配合使用,判定某个宏是否被定义
#line 标志该语句所在的行号
# 将宏参数替代为以参数值为内容的字符窜常量
## 将两个相邻的标记(token)连接为一个单独的标记
#pragma 讲明编译器信息
#warning 显示编译警告信息
#error 显示编译错误信息
(二)预处理的文法
--------------------------------------------------------------------------------
预处理并不分析整个源代码文件,它只是将源代码分割成一些标记(token),识别语句中哪些是C语句,哪些是预处理语句.预处理器能够识别C标记,文件名,空白符,文件结尾标志.
预处理语句格式:
#commandname(...)token(s)
1,command预处理命令的名称,它之前以#开头,#之后紧随预处理命令,标准C承诺#两边能够有空白符,但比较老的编译器可能不承诺如此.若某行中只包含#(以及空白符),那么在标准C中该行被懂得为空白.整个预处理语句之后只能有空白符或者注释,不能有其它内容.
2,name代表宏名称,它可带参数.参数能够是可变参数列表(C99).
3,语句中能够利用"\"来换行.
e.g.
#defineONE1/*ONE==1*/
等价于:
#defineONE 1
#defineerr(flag,msg)if(flag)\
printf(msg)
等价于:
#defineerr(flag,msg)if(flag)printf(msg)
(三)预处理命令详述
--------------------------------------------------------------------------------
1,#define
#define命令定义一个宏:
#defineMACRO_NAME(args)tokens(opt)
之后显现的MACRO_NAME将被替代为所定义的标记(tokens).宏可带参数,而后面的标记也是可选的.
对象宏
不带参数的宏被称为"对象宏(objectlikemacro)"
#define经常用来定义常量,现在的宏名称一样为大写的字符串.如此利于修改这些常量.
e.g.
#defineMAX100
inta[MAX];
#ifndef__FILE_H__
#define__FILE_H__
#include"file.h"
#endif
#define__FILE_H__中的宏就不带任何参数,也不扩展为任何标记.这经常用于包含头文件.
要调用该宏,只需在代码中指定宏名称,该宏将被替代为它被定义的内容.
函数宏
带参数的宏也被称为"函数宏".利用宏能够提升代码的运行效率:
子程序的调用需要压栈出栈,这一过程如果过于频繁会耗费掉大量的CPU运算资源.因此一些代码量小但运行频繁的代码如果采纳带参数宏来实现会提升代码的运行效率.
函数宏的参数是固定的情形
函数宏的定义采纳如此的方式:
#definename(args)tokens
其中的args和tokens差不多上可选的.它和对象宏定义上的区别在于宏名称之后不带括号.
注意,name之后的左括号(必须紧跟name,之间不能有空格,否则这就定义了一个对象宏,它将被替换为以(开始的字符串.但在调用函数宏时,name与(之间能够有空格.
e.g.
#definemul(x,y)((x)*(y))
注意,函数宏之后的参数要用括号括起来,看看那个例子:
e.g.
#definemul(x,y)x*y
"mul(1,2+2);"将被扩展为:
1*2+2
同样,整个标记串也应该用括号引用起来:
e.g.
#definemul(x,y)(x)*(y)
sizeofmul(1,2.0)将被扩展为sizeof1*2.0
调用函数宏时候,传递给它的参数能够是函数的返回值,也能够是任何有意义的语句:
e.g.
mul(f(a,b),g(c,d));
e.g.
#defineinsert(stmt)stmt
insert(a="1";b="2";)相当于在代码中加入a="1";b="2".
insert(a="1",b="2";)就有咨询题了:
预处理器会提示出错:
函数宏的参数个数不匹配.预处理器把","视为参数间的分隔符.
insert((a=1,b="2";))可解决上述咨询题.
在定义和调用函数宏时候,要注意一些咨询题:
1,我们经常用{}来引用函数宏被定义的内容,这就要注意调用那个函数宏时的";"咨询题.
example_3.7:
#defineswap(x,y){unsignedlong_temp=x;x="y";y=_tmp}
如果如此调用它:
"swap(1,2);"将被扩展为:
{unsignedlong_temp=1;1=2;2=_tmp};
明显后面的;是余外的,我们应该如此调用:
swap(1,2)
尽管如此的调用是正确的,但它和C语法相悖,可采纳下面的方法来处理被{}括起来的内容:
#defineswap(x,y)\
do{unsignedlong_temp=x;x="y";y=_tmp}while(0)
swap(1,2);将被替换为:
do{unsignedlong_temp=1;1=2;2=_tmp}while(0);
在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.
2,有的函数宏是无法用do-while(0)来实现的,因此在调用时不能带上";",最好在调用后添加注释讲明.
eg_3.8:
#defineincr(v,low,high)\
for((v)=(low),;(v)<=(high);(v)++)
只能以如此的形式被调用:
incr(a,1,10)/*increaseaform1to10*/
函数宏中的参数包括可变参数列表的情形
C99标准中新增了可变参数列表的内容.不光是函数,函数宏中也能够使用可变参数列表.
#definename(args,...)tokens
#definename(...)tokens
"..."代表可变参数列表,如果它不是仅有的参数,那么它只能显现在参数列表的最后.调用如此的函数宏时,传递给它的参数个数要许多于参数列表中参数的个数(余外的参数被丢弃).
通过__VA_ARGS__来替换函数宏中的可变参数列表.注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情形.
e.g.
#ifdefDEBUG
#definemy_printf(...)fprintf(stderr,__VA_ARGS__)
#else
#definemy_printf(...)printf(__VA_ARGS__)
#endif
tokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表.
注意在使用#define时候的一些常见错误:
#defineMAX=100
#defineMAX100;
=,;的使用要值得注意.再确实是调用函数宏是要注意,不要多给出";".
注意:
函数宏对参数类型是不敏锐的,你不必考虑将何种数据类型传递给宏.那么,如何构建对参数类型敏锐的宏呢?
参考本章的第九部分,关于"##"的介绍.
关于定义宏的另外一些咨询题
(1)宏能够被多次定义,前提是这些定义必须是相同的.那个地点的"相同"要求先后定义中空白符显现的位置相同,但具体的空白符类型或数量可不同,例如原先的空格可替换为多个其他类型的空白符:
可为tab,注释...
e.g.
#defineNULL0
#defineNULL /*nullpointer*/ 0
上面的重定义是相同的,但下面的重定义不同:
#definefun(x)x+1
#definefun(x)x+1或:
#definefun(y)y+1
如果多次定义时,再次定义的宏内容是不同的,gcc会给出"NAMEredefined"警告信息.
应该幸免重新定义函数宏,不管是在预处理命令中依旧C语句中,最好对某个对象只有单一的定义.在gcc中,若宏显现了重定义,gcc会给出警告.
(2)在gcc中,可在命令行中指定对象宏的定义:
e.g.
$gcc-Wall-DMAX=100-otmptmp.c
相当于在tmp.c中添加"#defineMAX100".
那么,如果原先tmp.c中含有MAX宏的定义,那么再在gcc调用命令中使用-DMAX,会显现什么情形呢?
---若-DMAX=1,则正确编译.
---若-DMAX的值被指定为不为1的值,那么gcc会给出MAX宏被重定义的警告,MAX的值仍为1.
注意:
若在调用gcc的命令行中不显示地给出对象宏的值,那么gcc给予该宏默认值
(1),如:
-DVAL==-DVAL=1
(3)#define所定义的宏的作用域
宏在定义之后才生效,若宏定义被#undef取消,则#undef之后该宏无效.同时字符串中的宏可不能被识别
e.g.
#defineONE1
sum=ONE+TWO /*sum=1+TWO*/
#defineTWO2
sum=ONE+TWO /*sum=1+2 */
#undefONE
sum=ONE+TWO /*sum=ONE+2*/
charc[]="TWO" /*c[]="TWO",NOT"2"!
*/
(4)宏的替换能够是递归的,因此能够嵌套定义宏.
e.g.
#defineONENUMBER_1
#defineNUMBER_11
inta=ONE/*a=1*/
2,#undef
#undef用来取消宏定义,它与#define对立:
#undefname
如够被取消的宏实际上没有被#define所定义,针对它的#undef并可不能产生错误.
当一个宏定义被取消后,能够再度定义它.
3,#if,#elif,#else,#endif
#if,#elif,#else,#endif用于条件编译:
#if常量表达式1
语句...
#elif常量表达式2
语句...
#elif常量表达式3
语句...
...
#else
语句...
#endif
#if和#else分别相当于C语句中的if,else.它们按照常量表达式的值来判别是否执行后面的语句.#elif相当于C中的else-if.使用这些条件编译命令能够方便地实现对源代码内容的操纵.
else之后不带常量表达式,但若包含了常量表达式,gcc只是给出警告信息.
使用它们能够提升代码的可移植性---针对不同的平台使用执行不同的语句.也经常用于大段代码注释.
e.g.
#if0
{
一大段代码;
}
#endif
常量表达式能够是包含宏,算术运算,逻辑运算等等的合法C常量表达式,如果常量表达式为一个未定义的宏,那么它的值被视为0.
#ifMACRO_NON_DEFINED==#if0
在判定某个宏是否被定义时,应当幸免使用#if,因为该宏的值可能确实是被定义为0.而应当使用下面介绍的#ifdef或#ifndef.
注意:
#if,#elif,#else之后的宏只能是对象宏.如果name为名的宏未定义,或者该宏是函数宏.那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.
4,#ifdef,#ifndef,defined.
#ifdef,#ifndef,defined用来测试某个宏是否被定义
#ifdefname或#ifndefname
它们经常用于幸免头文件的重复引用:
#ifndef__FILE_H__
#define__FILE_H__
#include"file.h"
#endif
defined(name):
若宏被定义,则返回1,否则返回0.
它与#if,#elif,#else结合使用来判定宏是否被定义,乍一看看起来它显得余外,因为差不多有了#ifdef和#ifndef.defined用于在一条判定语句中声明多个判别条件:
#ifdefined(VAX)&&defined(UNIX)&&!
defined(DEBUG)
和#if,#elif,#else不同,#indef,#ifndef,defined测试的宏能够是对象宏,也能够是函数宏.在gcc中使用"-Wundef"选项可不能显示宏未定义的警告信息.
5,#include,#include_next
#include用于文件包含.在#include命令所在的行不能含有除注释和空白符之外的其他任何内容.
#include"headfile"
#include
#include预处理标记
前面两种形式大伙儿都专门熟悉,"#include预处理标记"中,预处理标记会被预处理器进行替换,替换的结果必须符合前两种形式中的某一种.
实际上,真正被添加的头文件并不一定确实是#include中所指定的文件.#include"headfile"包含的头文件所以是同一个文件,但#include包包含的"系统头文件"可能是另外的文件.但这不值得被注意.感爱好的话能够查看宏扩展后到底引入了哪些系统头文件.
关于#include"headfile"和#include的区别以及如何在gcc中包含头文件的详细信息,参考本blog的GCC笔记.
有关于#include,我们对#include_next不太熟悉.#include_next仅用于专门的场合.它被用于头文件中(#include既可用于头文件中,又可用于.c文件中)来包含其他的头文件.而且包含头文件的路径比较专门:
从当前头文件所在名目之后的名目来搜索头文件.
例如:
头文件的搜索路径一次为A,B,C,D,E.#include_next所在的当前头文件位于B名目,那么#include_next使得预处理器从C,D,E名目来搜索#include_next所指定的头文件.
可参考cpp手册进一步了解#include_next
6,预定义宏
标准C中定义了一些对象宏,这些宏的名称以"__"开头和结尾,同时差不多上大写字符.这些预定义宏能够被#undef,也能够被重定义.
下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:
__LINE__ 当前语句所在的行号,以10进制整数标注.
__FILE__ 当前源文件的文件名,以字符串常量标注.
__DATE__ 程序被编译的日期,以"Mmmddyyyy"格式的字符串标注.
__TIME__ 程序被编译的时刻,以"hh:
mm:
ss"格式的字符串标注,该时刻由asctime返回.
__STDC__ 如果当前编译器符合ISO标准,那么该宏的值为1
__STDC_VERSION__ 如果当前编译器符合C89,那么它被定义为199409L,如果符合C99,那么被定义为199901L.
我用gcc,如果不指定-std=c99,其他情形都给出__STDC_VERSION__未定义的错误信息,咋回事呢?
__STDC_HOSTED__ 如果当前系统是"本地系统(hosted)",那么它被定义为1.本地系统表示当前系统拥有完整的标准C库.
gcc定义的预定义宏:
__OPTMIZE__ 如果编译过程中使用了优化,那么该宏被定义为1.
__OPTMIZE_SIZE__ 同上,但仅在优化是针对代码大小而非速度时才被定义为1.
__VERSION__ 显示所用gcc的版本号.
可参考"GCCthecompletereference".
要想看到gcc所定义的所有预定义宏,能够运行:
$cpp-dM/dev/null
7,#line
#line用来修改__LINE__和__FILE__.
e.g.
printf("line:
%d,file:
%s\n",__LINE__,__FILE__);
#line100"haha"
printf("line:
%d,file:
%s\n",__LINE__,__FILE__);
printf("line:
%d,file:
%s\n",__LINE__,__FILE__);
显示:
line:
34,file:
1.c
line:
100,file:
haha
line:
101,file:
haha
8,#pragma,_Pragma
#pragma用编译器用来添加新的预处理功能或者显示一些编译信息.#pragma的格式是各编译器特定的,gcc的如下:
#pragmaGCCnametoken(s)
#pragma之后有两个部分:
GCC和特定的pragmaname.下面分别介绍gcc中常用的.
(1)#pragmaGCCdependency
dependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时刻戳.如果指定文件比当前文件新,则给出警告信息.
e.g.
在demo.c中给出如此一句:
#pragmaGCCdependency"temp-file"
然后在demo.c所在的名目新建一个更新的文件:
$touchtemp-file,编译:
$gccdemo.c会给出如此的警告信息:
warning:
currentfileisolderthantemp-file
如果当前文件比指定的文件新,则不给出任何警告信息.
还能够在在#pragma中给添加自定义的警告信息.
e.g.
#pragmaGCCdependency"temp-file""demo.cneedstobeupdated!
"
1.c:
27:
38:
warning:
extratokensatendof#pragmadirective
1.c:
27:
38:
warning:
currentfileisolderthantemp-file
注意:
后面新增的警告信息要用""引用起来,否则gcc将给出警告信息.
(2)#pragmaGCCpoisontoken(s)
若源代码中显现了#pragma中给出的token(s),则编译时显示警告信息.它一样用于在调用你不想使用的函数时候给出出错信息.
e.g.
#pragmaGCCpoisonscanf
scanf("%d",&a);
warning:
extratokensatendof#pragmadirective
error:
attempttousepoisoned"scanf"
注意,如果调用了poison中给出的标记,那么编译器会给出的是出错信息.关于第一条警告,我还不明白如何幸免,用""将token(s)引用起来也不行.
(3)#pragmaGCCsystem_header
从#pragmaGCCsystem_header直到文件终止之间的代码会被编译器视为系统头文件之中的代码.系统头文件中的代码往往不能完全遵循C标准,因此头文件之中的警告信息往往不显示.(除非用#warning显式指明).
(这条#pragma语句还没发觉用什么大的用处)
由于#pragma不能用于宏扩展,因此gcc还提供了_Pragma:
e.g.
#definePRAGMA_DEP#pragmaGCCdependency"temp-file
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 一个 源程序 转换 可执行 程序 过程