一、程序环境
1.编译环境
各位帅哥美女,我又来了,我一直想写这篇关于 程序环境和预处理 的博客,不要问我为什么没写,问就是没时间,现在给你一巴掌让你进入正题:
相信每一位敲过代码的同志在敲完代码准备运行时的画面是这样的:首先看一眼自己写完的代码,然后心里想着”这么无懈可击的代码一定可以实现我要的结果”,随后按下运行键,看着屏幕上滚动的信息,随即弹出运行窗口,满怀期待的看着它,漆黑的窗口承载着数不清的希望,有的人会因为这个窗口激动地手舞足蹈,感叹自己是个天才,而有的人会因为这个窗口抓耳挠腮、怀疑人生,关掉窗口再看一遍自己的代码,在调试无果后甚至会伤及可怜的键盘(盘盘那么可爱,你为什么要.....)。
扯远了,不知道大家有没有想过,从我们写好代码到运行,这中间到底发生了什么呢?
直接上图!
详细来说,编译又分为如下几阶段:
①预编译阶段: 预处理指令 --------------------------------------- 结果存储在(.i)文件中
②编译阶段: 语法分析、词义分析、符号汇总 ------- 结果存储在(.s)文件中
③汇编阶段: 形成符号表、汇编指令 -> 二进制指令… -> 结果存储在(.o)文件中
2.运行环境
(1).程序必须要载入内存中,而载入内存的方式可以简单分为两种:
①我们可以选择在有操作系统的环境,操作系统会将程序载入内存中。②我们选择使用独立的环 境时,必须要手动去将程序载入内存,或者可以编写可执行代码将程序放入只读内存中。
(2).程序开始执行后就开始调用main函数
(3).执行程序代码,这个时候会调用堆栈,用来存储函数的参数和返回地址,也会在静态内存区开辟空间用来存储静态的局部变量,存储在静态区的变量在整个执行过程中将一直保存它的值。
(4).程序终止:①正常运行完终止②意外终止
二、预处理
大家应该都见过这东西:#define xxx ,这个就是预定义符号
除此之外,还有一些内置的预定义符号:
①__FILE__ 当前被编译的源文件
②__LINE__ 当前被编译的源文件的行号
③__TIME__ 当前被编译的源文件的时间
④__DATE__ 当前被编译的源文件的日期
⑤__STDC__ 当前被编译的源文件是否遵循ANSI C,遵循值为1,否则未定义
1.#define定义标识符
语法:
#define name stuff ----- 将neme替换成stuff(在预编译阶段完成)
2.#define定义宏
#define允许将参数替换到文本中,这种实现方式成为定义宏或者宏。
语法:
#define name(parament-list) stuff
其中parement-list可以是一个由’ , ’(逗号)隔开的符号表,也可能只是一个符号,不管是二者中的哪一个,都可能会出现在stuff中。注意:name与右边的 ( 不能有空白格,否则会被当成stuff中的一部分
总结一下#define作用:我想大家也大概明白了,#define就是起到一个替换的作用。#define定义标识符时其实就是将name替换成stuff。#define定义宏跟函数有点类似,将参数传过去然后进行替换,不同的是,函数传参需要类型,而宏不用。
3.使用#define的注意事项
①用#define定义标识符和宏时要不要在末尾加上’ ; ’?
答:按照语法来说是可以的,但是最好不要这样做,因为在C语言中, ";"代表语句结束,而我们用#define就是想达到一种替换效果,而如果我们在#define定义时加了";",这就使我们在编写代码时简介的改变了编写时的语法习惯,所以最好不要再末尾加’ ; ’。
例:
#define MY_NAME “songsong”;
int main()
{
char* p = MY_NAME;//我们习惯的书写格式,这里在编译时就出现语法错误了
return 0;
}
②#define定义宏要加上( )
答:一定要加!
也许很多人在会将#define定义的宏与函数认为是同一种东西,这里可以很明确的告诉大家,并不是,尽管用法相同,长相相同,但是①从内存的角度说完全是两回事,#define定义的宏是在编译之前就进行处理了,也就是预处理阶段,而函数则是在编译时开辟堆栈②从性能来说也有区别,#define定义宏只是进行一个替换,而函数则是进行运算。这也是为什么#define定义宏加()的原因,因为它只起到了一个替换的作用。
例:
#define add(x) x + x // 正确: #define add(x) (x+x)
int main()
{
int sum = 0;
sum = 5*add(10); //原意:5*(10+10)=100
//实际:5*10+10=60
return 0;
}
因此,大家能看出()的重要性了吧!
③#define定义宏最好不要使参数自增
#define fun(a,b) ((a+1)*(b+1))
int main()
{
int x = 3;
int y = 4;
int z = fun(x++,y);//注意这里,不仅没有将x+1的值代入进去计算,反而还使x值发生
//变化。
return 0;
}
#define宏 总结:
①在调用宏进行替换时,首先对宏的参数进行检查,若参数含有宏替换,那么它们首先被替换。
②替换后直接插入到文本位置。
③宏不能递归
4.#和##
值得一提的是,当#define定义宏的参数部分是字符串时,那么字符串中对应的参数就不会进行替换。
①#
作用:将参数转换成对应的字符串
例:
修改前:
#define PRINT(type,value) printf(“the value is” type”\n”,value)
修改后:
#define PRINT(type,value) printf(“the”# value “is”type”\n”,value)
int main()
{
int i = 0;
//PRINT(“%d”,i+3);//printf(“the value is””%d””\n”,i+3),可以看到value并没有被替换
PRINT(“%d”,i+3); // printf(“the””i+3””is””%d””\n”,value) // 将i+3替换成字符串
return 0;
}
②##
作用:将##两侧的符号合并成为一个符号
例:
#define add(x,y) (sum##x = x+y)
int main()
{
int a = 5;
int b = 3;
add(3,5);//sum##3 = 3+5 ---- sum3 = 3+5
reuturn 0;
}
5.宏和函数相比较
6.#undef
若需要将已经宏定义的名字进行重定义,那么首先需要移除这个名字,我们可以直接将原指令删除,然后重新#define,但是这样做太low了,我们只需要用#undef就可以将这个名字原本的定义移除。
7.条件编译指令:
①.#if:
#if 常量表达式 //常量表达式由预处理器求值
#elif 常量表达式
#else 常量表达式
#endif
例:
#define symbol...
#if symbol
#endif
…
#if必须跟随#endif收尾
②.判断是否宏定义:
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
8.头文件的包含
我们在使用函数时,都会引用头文件,引用的格式:#include <xxxx.h> or #include”xxxx.h”
也许大家只知道,使用库函数里的函数时采用前者的格式,使用自己编写的函数时,使用后者的格式,那么大家有没有想过为什么呢?
原因很简单,前者是去标准位置进行查找,后者是先在源文件目录下查找,若找不到,再去标准位置查找。也许有的小伙伴会说,那我使用库函数里的函数,引用头文件时,也采用#include ”xxx.h”可以吗?当然可以,只不过我们明知道我们使用的是库函数里的函数,却还这么引用,虽然最后还是会在标准位置查找文件,但这会使运行速度降低,而且让我们不容易进行区分。
好了,这部分内容就结束了,这几天好累!