C语言预处理

预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。

当我们写了一个代码,从一个文本文件的代码到最后成为一个可执行程序的过程中,要经过

编译+链接->.exe文件(二进制文件)

一个工程中每个源文件都会经过编译器生成目标文件(.obj,linux下是.o文件),经过链接器(链接链接库和目标文件)输出.exe文件

其中编译分为三个部分:预编译 -> 编译 -> 汇编

预编译的作用

1)#include头文件的包含

2)注释删除,使用空格替换注释

3)#define进行替换

在linux下,采用gcc -E的指令将源文件进行预编译

 在目录下会生成test.i文件

可以看到预编译阶段完成了上述的描述的功能。

预编译完成之后,下一步是将test.i进行编译。

编译的作用是将C代码翻译成汇编代码,并对代码进行语法分析、词法分析、语义分析、符号汇总

在linux下采用gcc -S将test.i文件编译生成test.s文件

可以看到test.s里面写的都是汇编代码。

同样的编译的最后一步汇编是将test.s文件里面的汇编转换为二进制代码,并且形成符号表(将之前的符号汇总与相应的地址对应起来形成映射表)

linux下采用gcc -c的指令

 默认生成test.o文件

因为采用的是文本编辑器vim打开的,所以看到的二进制显示出来的都是乱码。

要生成可执行程序还差一步链接工作

链接器的两个主要任务是符号解析和重定位,符号解析将目标文件中的每个全局符号都绑定到一个唯一的定义,而重定位确定每个符号的最终内存地址,并修改对那些目标的引用。

链接可以在编译时由静态编译器来完成,也可以在加载时和运行时由动态链接器来完成。

在linux下采用gcc -o的命令

针对预处理(预编译),C语言提供多种预处理功能

1、预定义符号

__FILE__:表示代码所在的路径

__LINE__:表示代码所在行数

__DATE__:获取日期

__TIME__:获取时间

	printf("%s\n", __FILE__);//打印代码所在文件
	printf("%d\n", __LINE__);//打印该句代码所在的行数
	printf("%s\n", __DATE__);//打印当前日期
	printf("%s\n", __TIME__);//打印当前时间

2、预处理指令

包括:

#define,#include,#pragma,#if,#endif,#ifdef,#ifdef等等

#define可以声明常量、表达式,#define定义的宏是直接替换,而不是赋值

比如说下面这段代码

#define Add(a,b) a+b
printf("%d", 5 * Add(3, 4));

这题的结果是19而不是35。在预编译之后,printf语句会变成printf("%d", 5 * 3 + 4);

采用宏的方式,使用#也可以将一个宏参数变成字符串,例如

#define PRINT(X)  printf("I love "#X"\n")
PRINT(CSDN);

运行结果:I love CSDN

##在宏中的作用

把位于他两边的符号合成一个符号

#define sum##num等效于#define sumnum

宏的好处没有类型的概念,直接替换在某些场合使用宏可以避免类型不匹配的问题,且效率比函数高,没有函数调用和返回的开销。

宏的缺点就是无法调试,因为没有类型,所以不够严谨,无法做类型检查。

条件编译

1、#ifdef、#ifndef和#endif

#ifdef DEBUG//如果定义了DEBUG就执行下面的代码
printf("DEBUG\n");
#endif

#ifndef RELEASE//如果没有定义了RELEASE就执行下面的代码
printf("RELEASE\n");
#endif

#ifndef、#define、#endif或者#pragma once可以解决头文件重复多次包含

#ifndef _TEST_H_
define _TEST_H_

//…………

#endif

//或者采用#pragma once
#pragma once

2、#if、#elif和#else

#if 表达式
//……
#elif 表达式
//……
#else 表达式
//……
#endif 表达式
//如果表达式为真则参与编译

#include

#include<>与#include" "的区别

<>是直接去标准库函数路径下去找 ,如果找不到就报错。

" "是首先在源文件所在目录查找,如果找不到,就去标准库函数路径底下去找,如果再找不到,就会报错。""的效率要低一些。

offsetof

计算结构体成员偏移量的宏

如果不清楚结构体大小如何计算,可以参考这篇文章

https://blog.csdn.net/weixin_43164548/article/details/118405163?spm=1001.2014.3001.5501https://blog.csdn.net/weixin_43164548/article/details/118405163?spm=1001.2014.3001.5501https://blog.csdn.net/weixin_43164548/article/details/118405163?spm=1001.2014.3001.5501采用C语言官方的例子

struct foo {
  char a;
  char b[10];
  char c;
};

int main ()
{
  printf ("offsetof(struct foo,a) is %d\n",(int)offsetof(struct foo,a));
  printf ("offsetof(struct foo,b) is %d\n",(int)offsetof(struct foo,b));
  printf ("offsetof(struct foo,c) is %d\n",(int)offsetof(struct foo,c));
  
  return 0;
}

运行结果:

offsetof(struct foo, a) is 0
offsetof(struct foo, b) is 1
offsetof(struct foo, c) is 11

那么offsetof是如何实现的呢?

#define OFFECTOF(struct_name, member_name)   (int)&((struct_name* )0->member_name)
//直接假设从起始地址0开始,取出每个成员的地址就是偏移量

  • 17
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值