河南省建设注册中心网站,网站服务器空间选择,做淘宝客网站详细步骤,wordpress搜索页自定义提示#xff1a;文章写完后#xff0c;目录可以自动生成#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、c语言的编译与链接1、编译与链接概述2、编译与链接详解 二、c语言预处理1.c语言中内置的预定义符号2、#define定义标识符3、#define定义宏4、#define 替换规… 提示文章写完后目录可以自动生成如何生成可参考右边的帮助文档 文章目录 前言一、c语言的编译与链接1、编译与链接概述2、编译与链接详解 二、c语言预处理1.c语言中内置的预定义符号2、#define定义标识符3、#define定义宏4、#define 替换规则5、在使用#define定义宏时#和##的作用6、带副作用的宏参数7、宏和函数对比8、#undef 三、条件编译1、常见的条件编译指令 四、文件包含1、头文件被包含的方式 前言
一、c语言的编译与链接
1、编译与链接概述
在c语言的一个项目中会有很多.c文件这些.c文件就是源文件组成一个程序的每个源文件通过编译过程分别转换成目标代码object code即每个源文件都转换为对应的目标文件即.obj文件。 然后这些目标文件由链接器linker捆绑在一起形成一个单一而完整的可执行程序。 链接器同时也会引入标准C函数库中任何被该程序所用到的函数而且它可以搜索程序员个人的程序库将其需要的函数也链接到程序中。 编译器将这些存储代码的源文件编译成存储机器可以识别的二进制指令的目标文件然后链接器将这些目标文件链接起来因为test.c中使用了add.c和sub.c中定义的函数所以链接这一步就是将test.c中使用的这些函数链接起来。然后才会形成一个可执行的程序。
2、编译与链接详解
源文件test.c和add.c在编译过程中先进行预编译在预编译阶段会将代码中的注释都去掉然后像#define定义的标识符和宏等都会被替代掉#include定义的头文件包含也会被引入其中即test.i和add.i中有的是预处理过后的代码。可以看到test.c中的一些代码和注释在test.i中已经没有了。 在经过预处理后下一步就是将生成的test.i文件进行编译了该处理会将c语言代码翻译成汇编代码。并且会进行语法分析、词法分析、语义分析和符号汇总等。 最后编译器会将编译形成的test.cod汇编文件经过汇编处理变为机器可以识别的二进制指令。然后还会形成符号表。 然后就是链接器将生成的目标文件test.obj和add.obj进行符号表的合并与重定位还有段表的合并。 test.cod在汇编后会将全局的一些函数等形成符号表add.cod在汇编后也会将全局的一些函数等形成符号表当在链接时因为test.obj和add.obj的符号表中都给Add符号申请了地址但是经过检查后发现test.obj里面Add的地址无用所以会合并符号表即当程序要调用Add函数时就去0x 500地址去找该函数。 然后链接器将这些目标文件和该程序引用的库里面的一些文件都链接到一起形成可执行程序test.exe。
二、c语言预处理
1.c语言中内置的预定义符号
这些预定义符号都是c语言内置的可以直接使用这些符号来打印我们想要的信息。 使用这些预定义符号就可以得到当前语句执行的相关信息。
2、#define定义标识符
除了上面的预定义符号外c语言也支持使用#define来定义标识符。 例如定义MAX 替代 100则在下面的程序中如果使用了MAX将都会在预处理阶段被替换为100.并且#define可以除了可以定义数字外还可以定义字符、类型、代码等。
#define MAX 100
#define STR abcdef
#define FAC fac() //调用函数
//如果定义的语句过长可以分成几行写除了最后一行外每行的后面都加一个反斜杠(续行符)
#define DEBUG_PRINT printf(file:%s line:%d \date:%s time:%s\n,\__FILE__, __LINE__,\__DATE__,__TIME__)void fac()
{printf(hh\n);
}
int main()
{int a MAX;printf(%d\n, a);printf(%d\n, MAX);FAC;DEBUG_PRINT;return 0;
}可以看到我们在test.c中的注释和#define定义的标识符都在预处理阶段被去掉了而且#define定义的标识符就是将这些标识符直接替换成了后面的语句。所以在使用#define定义标识符后语句后边不用加上 ; 符因为替换时会将 ; 符也加上这样语句就会有两个 ; ; 语句结束符。
3、#define定义宏
#define 机制包括了一个规定允许把参数替换到文本中这种实现通常称为宏macro或定义宏definemacro。 #define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表它们可能出现在stuff中。 注意 参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在参数列表就会被解释为stuff的一部分。 在定义宏时应该尽可能多的使用括号以避免出现下面的情况。 所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
#define SQUARE(x) ((x)*(x))
#define DOUBLE(x) ((x)(x))int main()
{int a 9;int r SQUARE(a); //81//经过预处理后等价于 int r ((a) * (a));printf(%d\n, r);int z SQUARE(a 1); //100//经过预处理后等价与 int z ((a 1) * (a 1));printf(%d\n, z);int ret 3 * DOUBLE(100); //600//经过预处理后等价于 int ret 3 * ((100) (100))printf(%d\n, ret);return 0;
}4、#define 替换规则
在程序中扩展#define定义符号和宏时需要涉及几个步骤。
在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值替换。最后再次对结果文件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。 注意宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏不能出现递归。当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。
#define ADD(x,y) ((x)(y))
#define MAX 100int main()
{//会先将MAX替换为100然后再将ADD(2,100)替换为 ((2)(100))int ret ADD(2, MAX);printf(%d\n, ret);//字符串里面的MAX不会被替换为100printf(MAX %d\n, MAX);return 0;
}5、在使用#define定义宏时#和##的作用
使用 # 把一个宏参数变成对应的字符串。
//宏可以做到这样的操作
//参数前面加上#就表示直接替换为N这个字符而不是N的值
#define PRINT(N,format) printf(#N format\n,N)//函数不能将n改变为其他字符
void print(int n)
{printf(n \n, n);
}
int main()
{int a 10;double b 3.14;//当我们想要输出这样的语句时函数做不到但是宏可以做到/*printf(a %d\n, a);printf(b %lf\n, b);*/PRINT(a,%d);PRINT(b,%lf);return 0;
}##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
#define CAT(name,num) name##numint main()
{int class101 101;printf(%d\n, CAT(class, 101));//在预处理时会变为 printf(%d\n, class101)//##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。return 0;
}6、带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候如果参数带有副作用那么你在使用这个宏的时候就可能出现危险导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。 例如
x1;//不带副作用
x;//带有副作用即在宏执行的过程中参数的值会发生变化。
#define MAX(x,y) ((x)(y)?(x):(y))int main()
{int a 5;int b 8;int c MAX(a, b);//在预处理后变为//int c ((a) (b) ? (a) : (b));printf(%d\n, a);printf(%d\n, b);printf(%d\n, c);return 0;
}7、宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。 #define MAX(a, b) ((a)(b)?(a):(b)) 那为什么不用函数来完成这个任务 原因有二 1、用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。 2、更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。 当然和宏相比函数也有劣势的地方 1、 每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。 2、 宏是没法调试的。 3、宏由于类型无关也就不够严谨。 4、宏可能会带来运算符优先级的问题导致程容易出现错。
宏有时候可以做函数做不到的事情。比如宏的参数可以出现类型但是函数做不到。
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))int main()
{int* p (int*)malloc(10 * sizeof(int));int* p2 MALLOC(10, int);//int* p2 (int*)malloc(10 * sizeof(int));return 0;
}命名约定: 把宏名全部大写 函数名不要全部大写。
8、#undef
这条指令用于移除一个宏定义。
#define MAX 100
int main()
{int a MAX;printf(%d\n, a);printf(%d\n, MAX);
#undef MAX //此时MAX已经不再为定义的标识符printf(%d\n, MAX);return 0;}三、条件编译
1、常见的条件编译指令
在编译一个程序的时候我们如果要将一条语句一组语句编译或者放弃是很方便的。因为我们有条件编译指令。 1、#if
#if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如
#define __ DEBUG __ 1
#if __ DEBUG __ //..
#endif int main()
{int arr[10] { 0 };int i 0;for (i 0; i 10; i){arr[i] i 1;
//如果#if 后面的常量表达式为真则里面的语句会参与编译
#if 1printf(%d , arr[i]);
#endif}return 0;
}2.多个分支的条件编译 //...
#elif 常量表达式//...
#else //...
#endif #define NUM 1
int main()
{//只有满足条件的语句才会编译而不满足条件的语句在预处理阶段就被去除了
#if NUM1printf(hehe\n);
#elif NUM2printf(haha\n);
#elseprintf(heihei\n);
#endifreturn 0;
}3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol #define MAX 0
int main()
{//如果使用#define定义了MAX标识符就为真
#if defined(MAX)printf(defined\n);
#endif#if !defined(MAX)printf(!defined\n);
#endif//下面两组和上面两组等价
#ifdef MAXprintf(ifdef\n);
#endif#ifndef MAXprintf(ifndef\n);
#endifreturn 0;}4.嵌套指令
#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif
#elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif
#endif #define OPTION1 1#define FAC1 1
#define FAC2 2
#define FAC3 3int main()
{
#if defined(OPTION1)#ifdef FAC1printf(FAC1);#endif
#else #ifdef FAC2printf(FAC2);#endif
#endifreturn 0;
}四、文件包含
我们已经知道 #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。这种替换的方式很简单 预处理器先删除这条指令并用包含文件的内容替换。 这样一个源文件被包含10次那就实际被编译10次。
1、头文件被包含的方式
本地文件包含
#include filename 查找策略先在源文件所在目录下查找如果该头文件未找到编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。 库文件包含
#include filename.h查找头文件直接去标准路径下去查找如果找不到就提示编译错误。 由上可知对于库文件也可以用 包含但是这样做查找的效率就低些当然这样也不容易区分是库文件还是本地文件了。 嵌套文件包含 comm.h和comm.c是公共模块。 test1.h和test1.c使用了公共模块。 test2.h和test2.c使用了公共模块。 test.h和test.c使用了test1模块和test2模块。 这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。此时我们使用条件编译就可以解决这个问题 每个头文件的开头写
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__ 或者
#pragma once