一起来学习C语言的程序环境与预处理
1.程序的翻译环境和执行环境要支持c语言的实现会有不同的编译器出现而这些编译器都要遵循ANSI C都存在两种环境第1种是翻译环境在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境它用于实际执行代码。.obj为后缀的就是目标文件而一个项目中可能会有很多.c后缀的源文件分别处理后每经过编译器单独处理然后会生成对应的目标文件.obj然后总体经过连接器处理最终变成可执行程序。目标文件最后还要加上链接库整体一起通过链接器链接变成可执行程序.链接库在编写代码的时候会有一些不属于我们自己写的函数如printf这些函数是自带的库里面包含的这些库就叫链接库补函数的声明与定义里面的静态库从源文件生成可执行程序的这一个过程就叫做翻译环境2.gcc C语言编译器来演示编译过程2.1编译预编译→编译→汇编预编译预处理文本操作1.头文件的包含#include——预编译指令将包含的头文件给展开2.删除注释注释被空格替换3.#define定义符号的替换2.2编译生成.s的文件把c语言代码转换成汇编代码1.语法分析2.词法分析3.语义分析4.符号汇总——汇总的是全局符号《程序员的自我修养》——通俗地讲解代码编译过程的细节汇编生成了test.o把汇编代码转换成二进制指令形成符号表框内是十六进制是地址链接最终将.o文件链接成.exe可执行程序1.合并段表2.符号表的合并和重定位像Add一开始地址为默认0和另一个.c文件内的Add地址的为0x200会重新定位符号表的意义多个目标文件进行链接的时候会通过符号表查看来自外部的符号是否真实存在2.3运行环境1.程序必须载入内存中。在有操作系统的环境中一般这个由操作系统完成。在独立的环境中程序的载入必须由手工安排电焊好伐也可能是通过可执行代码置入只读内存来完成。2.程序的执行便开始。接着便调用main函数。3.开始执行程序代码。这个时候程序将使用一个运行时堆栈stack也就是之前博客中写到的函数栈帧的创建与销毁存储函数的局部变量和返回地址。程序同时也可以使用静态static内存存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。4.终止程序。正常终止main函数也有可能是意外终止。3详解预处理3.1预定义符号__DATE____FILE____LINE____TIME____STDC__ //如果编译器遵循ANSI C其值为1否则未定义作用记录日志可记录在哪个文件在哪个日期在什么时候在哪个文件在哪一行12345678910#define _CRT_sECURE_NO_WARNINGS 1#include stdio.hintmain(){printf(%s\n, __FILE__);printf(%s\n, __TIME__);printf(%d\n, __LINE__);printf(%s\n, __DATE__);return0;}预处理符号的应用12345678910111213141516171819202122//预处理符号的应用——写日志#define _CRT_SECURE_NO_WARNINGS 1#include stdio.h#include string.h#include errno.hintmain(){inti 0;FILE* pf fopen(test.txt,w);if(NULL pf){printf(error is%s\n,strerror(errno));return0;}for(i 0; i 5; i){fprintf(pf,%s\t%s\t%s\t%d\ti%d\n, __FILE__, __DATE__, __TIME__, __LINE__, i);}fclose(pf);pf NULL;return0;}3.2#define3.2.1#define定义标识符两种用法12#define MM 100#define reg register——关键字替换#define末尾有时候可以加分号有时又不可以加上分号不可以加上分号的情况1234567891011//不可以加上分号的情况#define _CRT_SECURE_NO_WARNINGS 1#define MAX(x,y) ((x)(y)?x:y);#include stdio.hintmain(){inta 5;intb 3;printf(%d\n, MAX(a, b));return0;}因为加上分号会使得宏在替换的时候也带上分号所以在调用在一些函数内部的时候会出现错误。综上当我们定义宏的时候最好不要加分号在末尾。3.2.2 #define定义宏这里也是将全部参数给替换掉在预处理的时候就替换掉了不信的话可以在解决方案处右击点击属性后选择预处理然后就可以在debug里面发现又应该.i文件点开后就可以发现这里已经被替换掉了。1234#define Max(x,y) ((x)(y)?(x):(y))// Max-宏的名字// x和y-宏的参数// ((x)(y)?(x):(y))-宏的内容ps在定义宏的内容的时候最好每个参数都要加上小括号然后最后整体加上小括号否则如果传入参数不是单独一个值而是表达式的时候会产生一些没有意料到的优先级计算改变Tips宏后面的参数的小括号一定要紧挨着宏的名3.2.3 #define替换规则1.先看宏的参数内是不是有define的符号优先替换掉define符号2.对于宏参数名被他们的值替换注意1.宏的参数里可以出现其他#define定义的符号但不可以递归2.当define扫描预处理时字符串常量的内容并不被搜索也就是说字符串里面的东西是不会被宏预处理的3.2.4 #和###相当于把宏的参数放进字符串中变成所对应的字符串123456789101112// #的用法#define _CRT_SECURE_NO_WARNINGS 1#define print(x) printf(the value of #x is %d\n,x)#include stdio.hintmain(){inta 5;intb 4;print(a);print(b);return0;}##可以把两边分离片段合成一个符号1234567#define CAT(C,num) C##numintmain(){intClass10410000;printf(%d\n,CAT(Class,104));return0;}3.2.5带副作用的宏参数123456789101112#define MAX(x,y) ((x)(y)?(x):(y))intmain(){inta3;intb5;intmMAX(a,b);//宏的参数是直接替换进去所以替换完之后为intm((a)(b)?(a):(b));//会出现错误printf(%d\n,m);printf(%d %d\n,a,b);return0;}3.2.6宏和函数对比宏的优点1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹2.更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以 用于来比较的类型。宏的缺点1.每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。2.宏是没法调试的。3.宏由于类型无关也就不够严谨。4.宏可能会带来运算符优先级的问题导致程容易出现错可从以下方面比较宏与函数的区别代码长度——宏如果不是特别小的话每一次使用的时候都要替换成宏的定义可能会导致最终代码特别长大幅增长程序长度而函数每次都只调用那一段代码执行速度——宏只需要执行一行的代码而函数拥有调用函数执行代码返回参数这三步操作所以相对来说会慢一些操作符优先级——由于宏是不经过计算直接将参数传进去的所以在传参后可能会有优先级的不同导致结果与我们想要的最终结果有出入除非加上括号。相对的函数参数只在函数调用的时候求值一次会比较容易猜测结果。带有副作用的参数——参数可能被替换到宏体中的多个位置所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次结果更容易控制。