C语言高级应用 -------- (6)C语言的预处理(编译链接)、函数和函数库

本文详细介绍了C语言预处理过程,包括预处理的作用、预处理命令的使用方法、宏定义的注意事项及应用示例等内容。此外,还讲解了gcc如何仅进行预处理而不编译,以及如何利用这些技巧辅助调试。

一、C语言预处理理论

1、由源码到可执行程序的过程

由粗略到详细

源码.c——>(编译)elf可执行程序
源码.c——>(编译)目标文件.o——>(链接)elf可执行程序
源码.c——>(编译)汇编文件.S——>(汇编)目标文件.o——>(链接)elf可执行程序
源码.c——>(预处理)预处理过的.i源文件——>(编译)汇编文件.S——>(汇编)目标文件.o——>(链接)elf可执行程序

预处理用预处理器,编译用编译器,汇编用汇编器,链接用链接器,这几个工具再加上其他一些额外的会用到的可用工具,合起来叫编译工具链gcc就是一个编译工具链

2、预处理的意义

为了帮助 编译器 做一些杂事, 让我们的编译器专心工作(只专心于将 .c 文件转化为 .S 文件)

所以就是为了将分工明确。让编译器的功能更加聚焦

3、预处理器处理的命令(后面有详解)

  • #include(#include <>和#include ""的区别)
  • 注释
  • #if #elif #endif #ifdef (条件编译)
  • 宏定义

4、gcc当中如何只预处理不编译

gcc -E xx.c -o xx.i
-E 可以设定我们只预处理不编译
可以实现只预处理不编译。一般情况下没必要只预处理不编译,但有时候这种技巧可以用来帮助我们研究预处理过程,帮助debug程序。

补充 gcc 的一些其他用法:

gcc xx.c -o xx可以指定可执行程序的名称
gcc hello.c -o hello.out :将我们可执行程序指定名称为 hello.out,不指定的话默认是 a.out

gcc xx.c -c -o xx.o可以指定只编译不连接,也可以生成.o的目标文件
-c 规定我们只编译不进行链接。

举例分析:

  • gcc -E a.c -o a.i
    在这里插入图片描述

总结:
第一,宏定义语句本身不见了(可见编译器根本就不认识#define,编译器根本不知道还有个宏定义);

第二,typedef重命名语言还在,说明它和宏定义是有本质区别的(说明typedef是由编译器来处理而不是预处理器处理的);

二、预处理器工作内容详解

1、头文件包含

#include < > 和 #include" "的区别:

  • < >的特点:
    专门用来包含系统提供的头文件(就是系统自带的,不是程序员自己写的)

规则:

C语言编译器只会到系统指定目录(编译器中配置的或者操作系统配置的寻找目录,譬如在ubuntu中是/usr/include目录,

编译器还允许用-I来附加指定其他的包含路径)去寻找这个头文件(隐含意思就是不会找当前目录下),如果找不到就会提示这个头文件不存在。

举例:
在这里插入图片描述

  • " "的特点:
    专门用来存放程序员在本文件夹下面,自己写的头文件。

举例:
在这里插入图片描述

2、注释(预处理器将其去掉)

3、条件编译

(1)有时候我们希望程序有多种配置,我们在源代码编写时写好了各种配置的代码,然后给个配置开关,在源代码级别去修改配置开关来让程序编译出不同的效果。

(2)条件编译中用的两种条件判定方法分别是 #ifdef 和 #if
区别:

#ifdef XXX判定条件成立与否时主要是看XXX这个符号在本语句之前有没有被定义,
只要定义了(我们可以直接#define XXX或者#define XXX 12或者#define XXX YYY)这个符号就是成立的。

#if (条件表达式),它的判定标准是()中的表达式是否为true还是flase,跟C中的if语句有点像。

三、宏定义

1、括号问题

宏定义主要的问题:
1、宏定义是在预编译阶段,直接替换。 所以我们要注意符号的优先级。
例如:

#define X(a,b) a+b
c = 3*X(a,b)  
c = 3* a+b;     这并不是我们想要的结果

常规做法:用 “()” 将我们的每个部分都括起来。
注:我们每个小部分都需要括号。 最后 整体 再加上括号

#define x(a,b)    ( (a) + (b) )

2、常用的示例

  • 求最大值 (三目运算符, ? 和 :)
    注意逻辑: 先从最里面的开始写, 写一个逻辑括一个括号
#define MAX(a,b) ( ( (a) > (b) ) ? (a) : (b) )
  • SEC_PER_YEAR ------- 一年有多少秒
#define SEC_PER_YEAR (365* 24* 60*60)

注:直接这样写会溢出
1、一个数字,直接使用,在c编译器当中,默认是 int 类型,所以我们这个会超过 int 的范围
2、有符号数 和 无符号数。 ----- 无符号数用 %u 来打印
3、int :4个字节, long : 8个字节。
将这个整数,转化为 无符号的long型

4、 这个 UL 必须放在括号里面。

#define SEC_PER_YEAR (365* 24* 60*60UL)
unsigned int l = SEC_PER_YEAR;
printf("l = %u",l)

3、带参数的宏, 带参数的函数 的区别

(1)调用开销:

  • 带参数的宏是直接展开,在预处理阶段就完成, 所以没有任何调用开销。
  • 函数会跳转到我们的函数体当中去执行程序,然后再跳转回来。 ---- 效率比较低

(2)参数检查

  • 宏定义没有参数检查。 ----- 程序员编写过程中要小心。
  • 函数在编译阶段, 编译器会给我们进行参数类型核对,如果我们发生错误,编译器会提醒报数。

4、内联函数 inline 关键字的使用

作用:

  • 本质是一个函数, 有函数的优点, 编译器帮助我们进行 静态类型检查。 (编译器处理,和预处理器没有关系

  • 也有宏定义的优点,就是在代码当中,会原地展开,减小调用开销。

总结:内联函数就相当于 带着静态类型检查 的参数宏。

使用情况:
函数很短,我们既希望减小调用开销,又希望可以进行参数检测。

5、条件编译

1、#ifdef
分析:

  • 如果前面有定义 DEBUG,将 debug(x) 替换成 printf(x)
  • 否则 debug(x) 就会定义成 空(也就是什么也没有)

在大型程序当中, 我们会有很多调试信息, 所以就会有 debug 版本,和 release 版本。
debug:加一个宏定义。 release :去掉这个宏定义。

#define DEBUG

#ifdef DEBUG
#define debug(x)  printf(x)
#else
#define debug(x)
#endif

void main()
{
debug("This is a info");
}

2、undef:注销宏
意思:就是注销掉 DEBUG 这个宏。

#define DEBUG
#undef DEBUG

四、函数

1、函数的本质

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想文艺一点的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值