目录
#define
翻译环境
是将源代码转换成可执行的机器指令
1每个.c文件都会单独在翻译环境经过编译器处理,每个对应一个目标文件.obj
2然后链接器会将每个目标文件和所需的库函数(而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中)链接在一起生成一个可执行文件
编译
分成了3个步骤,预编译,编译,汇编
--------------------------------------------------------------------------------------------------------------------------
1预编译
生成.i文件
1完成了头文件的包含 比如#include<> 把头文件的内容拷贝到我们的程序中
2#difine的符号和宏的替换
3注释的删除
上面这些操作都是文本操作
--------------------------------------------------------------------------------------------------------------------------
2编译
生成.s文件
将c语言的代码转换位汇编代码(语法分析,词法分析,语义分析,符号汇总)
--------------------------------------------------------------------------------------------------------------------------
3汇编
生成.obj文件
把汇编代码转换为二进制或者机器指令,会生成符号表
--------------------------------------------------------------------------------------------------------------------------
1. 预处理 选项 gcc - E test.c - o test.i预处理完成之后就停下来,预处理之后产生的结果都放在 test.i 文件中。2. 编译 选项 gcc - S test.c编译完成之后就停下来,结果保存在 test.s 中。3. 汇编 gcc - c test.c汇编完成之后就停下来,结果保存在 test.o 中。--------------------------------------------------------------------------------------------------------------------------
链接
把多个目标文件和链接库进行链接
1合并段表(test.o和add.o这种文件合并在一起)
2符号表的合并和重定位
执行环境
用于实际执行代码
1程序必须载入内存,在有操作系统的环境下,由操作系统自动完成,在独立的环境下,程序的载入必须必须由手工安排,也可能是通过可执行代码置入只读内存来完成
2程序的执行便开始,接着调用main函数
3开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack)存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
预处理详解
预定义符号
#include<stdio.h> int main() { printf("%s\n", __FILE__);//E:\c代码\Project29\源.cpp // 文件名(地址+标识名+文件类型) printf("%d\n", __LINE__);//6 这行文件所在的行数 printf("%s\n", __DATE__);//Mar 14 2022打印现在的日期 printf("%s\n", __TIME__);//21:30:21打印现在的时间 printf("%s\n", __FUNCTION__);//main 所在的函数 return 0; }
#define
#define定义标识符
#include<stdio.h> #define M 100 #define reg register #define CASE break;case//在写case语句的时候自动把 break写上 #define DEBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n" ,\ __FILE__,__LINE__ , \ __DATE__,__TIME__ ) #define MAX 1000; int main() { reg int num = 0; int m = M; printf("%d\n", m); //int M=MAX; 等价替换成int M=1000;; return 0; }
define后面是不建议加;的,因为等价替换,你加分号,替换时还是会把分号代替进去
#include<stdio.h> #define MAX 1000; int main() { int M=MAX;// 等价替换成int M=1000;;会出现语法错误 return 0; }
#define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。#include<stdio.h> #define SQUARE(X) X*X #define SQUARE1(X) (X)*(X) #define DOUBLE(X) (X)+(X) #define DOUBLE1(X) ((X)+(X)) int main() { printf("%d\n", SQUARE(3));//9 printf("%d\n", 3*3);//等价替换成这样 printf("%d\n", SQUARE(3+1));//完全替换 //替换成3+1*3+1 printf("%d\n", SQUARE1(3 + 1));//16 //替换成(3+1)*(3*1) printf("%d\n",10* DOUBLE((4)));//44 //替换为10*(4)+(4) printf("%d\n", 10*DOUBLE1((4)));//80 //替换为10*((4)+(4)) return 0; }
define 的替换规则
1在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果是,它们首先被替换
2替换文本随后被插入程序中原来的文本的位置,对于宏,参数名被他们的值替换
3最后再次对结果文件进行扫描,看看它是否包含任何被#define定义的符号,如果是,就重复上面的过程
1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。#和##这种用法只能在宏定义里使用,因为#没有将变量当成变量,而是当成字符串替换
#include<stdio.h> #define PRINT(X) printf("the value of "#X" is %d\n",X) //只能实现一种数据类型 #define PRINT1(X,FORMAT) printf("the value of " #X " is " FORMAT "\n",X) //可以实现多种数据类型 int main() { printf("hello world\n"); printf("hello" " world\n");//输出hello world int a = 10; PRINT(a); //printf("the value of ""a"" is %d\n", a); int b = 20; PRINT(b); //printf("the value of ""b"" is %d\n", b); int c = 30; //printf("the value of ""c"" is %d\n", c); PRINT(c); //想实现输出 the value of a is 10 //想实现输出 the value of b is 20 //想实现输出 the value of c is 30 //实现这样的功能用函数是行不通的 用宏实现 float f = 5.5f; PRINT1(f,"%f"); //printf("the value of " "f" " is " "%f" "\n",X); return 0; }
也是只能在宏定义中用
#include<stdio.h> #define CAT(X,Y) X##Y int main() { int class101 = 100; printf("%d\n", CAT(class, 101));//100 printf("%d\n", class101); return 0; }
带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。#include<stdio.h> #define MAX(X,Y) ((X)>(Y)?(X):(Y)) int main() { int a = 5; int b = 8; int m = MAX(a++, b++); //等价代换为int m = ((a++) > (b++) ? (a++) : (b++)); printf("%d\n", m);//9 printf("%d\n", a);//6 printf("%d\n", b);//10 return 0; }
宏和函数比较
#define MAX(X,Y) ((X)>(Y)?(X):(Y)) int Max(int x, int y) { return x > y ? x : y; } int main() { int a = 5; int b = 8; int m = MAX(a, b); //在预编译阶段被换成((a)>(b)?(a):(b)) m = Max(a,b); //调用函数需要做大量调用函数操作,然后还需要做返回操作 return 0; }
宏的优点
1用于调用函数和从函数返回的代码可能比实际执行这个小型计算机工作所需要的时间更多,所以宏比函数在程序规模和速度方面更胜一筹
2 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于> 来比较的类型。 宏是类型无关的宏的缺点1每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。2. 宏是没法调试的。(调试在运行的时候调试)3. 宏由于类型无关,也就不够严谨。4. 宏可能会带来运算符优先级的问题,导致程容易出现错。宏有时候能做到函数做不到的事情,比如#可以使之变成字符串,##可以合成两边的符号#include<stdio.h> #include<stdlib.h> #define MALLOC(num,type) (type*)malloc(num*sizeof(type)) int main() { int* p = MALLOC(10, int);//这个是函数做不到的 return 0; }
总结 简单宏来做,函数做不到的也用宏
命名约定一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是:把宏名全部大写函数名不要全部大写#undef
#include<stdio.h> #define M 100 int main() { int a = M; #undef M //取消M的宏定义 printf("%d\n", a); //printf("%d\n", M); M无效了 return 0; }
命令行定义
条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。(满足条件就编译,不满足就不编译)#include<stdio.h> int main() { #ifdef PRINT printf("hehe\n"); #endif //不会打印hehe return 0; }
#include<stdio.h> #define PRINT int main() { #ifdef PRINT printf("hehe\n"); #endif //会打印hehe return 0; }
常见的条件编译指令
#include<stdio.h> #define PRINT #define TEST 0 int main() { #/*ifdef PRINT printf("hehe\n"); #endif*/ #if -1 printf("hehe\n");//如果是0就不会打印,其他常量都可以打印 #endif //多分支的条件编译 #if 1==1 printf("hehe\n");//满足这个表达式就会打印hehe,而且不会继续下面的判断 #elif 1==2 printf("haha\n"); #else printf("heihei\n");//只有上面的判断都不成立,才打印heihei #endif //打印hehe //判断是否被定义 这里指宏定义 #ifdef TEST printf("test\n"); #endif // TEST //打印test //判断是否被定义 指宏定义 #if defined(TEST) printf("test\n"); #endif //打印test //如果没定义就打印HEHE #ifndef HEHE printf("HEHE\n"); #endif //打印HEHE //判断是否被定义 指宏定义 #if !defined(HEHE) printf("HEHE\n"); #endif //打印HEHE //嵌套判断 #if defined(OS_UNIX)//1 #ifdef OPTION1//2 unix_version_option1(); #endif//2 #ifdef OPTION2//3 unix_version_option2(); #endif//3 #elif defined(OS_MSDOS)//1 #ifdef OPTION2//4 msdos_version_option2(); #endif//4 #endif//1 return 0; }
文件包含
//库函数包含,c语言库提供的函数头文件使用<> //查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。 #include<stdio.h> //本地文件包含,自定义的函数的头文件用"" //查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 //准位置查找头文件。如果找不到就提示编译错误。 #include"add.h" int mian() { return 0; }
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?答案是肯定的, 可以 。但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。嵌套文件包含
comm.h是公共头模块,test1使用了comm.h,test2使用了comm.h,所以在test.c中会重复去替换comm.h的内容,这样就造成了文件内容的重复
如何解决这个问题
条件编译
其他预处理指令
#include #define #if #ifdef #endif #ifndef #else #elif #undef #line //改变当前行数和文件名称 #error //编译程序时,只要遇到#error就会生成一个编译错误的提示消息,并停止编译 #pragma //为实现定义的命令,它允许想编译程序传送各种指令 #pragma once//只允许头文件在一个文件里编译一次 #pragma pack(4) //设置对齐数为4
参考《 C 语言深度解剖》学习