当前位置 : 主页 > 编程语言 > c语言 >

C语言预处理

来源:互联网 收集:自由互联 发布时间:2023-09-03
程序的翻译环境 test.c ------编译--链接------test.exe----------运行 |------翻译环境--------| |-----运行环境-----| 翻译环境:指的是源代码被转换为可执行的机器指令 运行环境:用于实际执行代码
  • 程序的翻译环境

test.c ------编译--链接------>test.exe---------->运行

         |------翻译环境--------|          |-----运行环境-----|

翻译环境:指的是源代码被转换为可执行的机器指令

运行环境:用于实际执行代码

test.exe:放的是二进制信息(二进制文件)

test.c:文本文件

  • 程序的执行环境

每一个源文件都会经过编译器生成目标文件,然后所有的目标文件和链接库会经过链接器生成可执行文件

  • C语言成语的编辑+链接

C语言预处理_预处理

预编译----(gcc -E test.c产生test.i)(这个阶段做的是文本操作)

  1. #inclde 头文件包含
  2. 删除注释---使用空格替换注释
  3. 对#define进行替换

编译---(gcc -S test.i产生test.s)

  1. 把C语言代码翻译成汇编代码(语法分析,词法分析,语义分析,符号汇总)
  2. 符号汇总是把文件里的符号进行保存,比如main ADD等符号

汇编--- (gcc -c test.s生成test.o)

  1. 把汇编代码转化为二进制代码
  2. 形成符号表(在符号汇总的基础上对每个符号增加他的地址,如果是没有实际意义的符号,则增加一个无意义的地址)

链接--- ()

  1. 合并段表--- 把所有的.o文件合并到一起 (段表是每个文件的文件格式,他们的(elf格式文件)格式相同,不同的是每个文件的内容)
  2. 符号表的合并和重定位 (如果碰到两个相同符号,那么一定选取有效的文件地址)

运行环境

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统来完成,在独立的环境中,程序的载入必须有手工安排,也可能是通过可执行代码置入只读内存来完成
  2. 程序的执行便开始,接着便调用main函数
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时可以使用静态(stactic)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止;也可能是意外终止
  • 预处理符号介绍
//文件名的路径
	printf("%s\n", __FILE__);
    //行号
	printf("%d\n", __LINE__);
	//日期
	printf("%d\n", __DATE__);
	//时间
	printf("%d\n", __TIME__);
	//函数名
	printf("%d\n", __FUNCTION__);
	//如果编译器遵循ANSI C ,其值为1,否则未定义----=linux下的gcc遵循,而vs不遵循
	//printf("%d\n", __STDC__);
  • 预处理指令#define

#define   #include    #pragma pack(4)   #pragme     #if     #endif     #ifdef     #line

  • #define:可以定义标识符,也可以定义宏。不限制整形,字符型等等 也可以是关键字
  • #define:不推荐加分号
  • 宏和函数的对比

宏的优点

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所以须的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹
  2. 更为重要的是函数的参数必须声明为特定类型,所以函数只能在类型合适的表达式上使用,反之这个宏怎可以使用整形,长整型,浮点型等可以用来比较的类型,宏的类型是无关

宏的缺点

  1. 每当使用宏的时候,一份宏的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度
  2. 宏是没法调试的
  3. 宏由于类型无关,也不够严谨
  4. 宏可能带来运算符优先级的问题,导致程序容易出错

宏有时候可以做到函数做不到的事情,宏的参数可以出现类型(可以传类型),但是函数在调用的时候会有函数调用和返回的开销,而宏在预处理阶段就完成了,不会有调用和开销

#define定义宏

  1. #define允许把参数定义到宏内
  1. #define替换规则
  2. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果是,他们首先被替换
  3. 替换文本首先插入到程序中原来文本的位置,对于宏,参数名被他们的值替换
  4. 最后,再次对结果文件进行扫描,看看他是否包含任何由#define定义的符号,如果是,就重复上述处理过程

注意事项

  • 宏参数和#define定义中可以出现其他#define定义的变量,但是对于宏,不能递归
  • 当预处理搜索#define定义的符号,字符串常量的值不被搜索
  • 预处理操作符和#和##的介绍

#的作用--在#define在宏里面把参数给所对应的字符串

#define PRINTF(x) printf("the number of " #x " is %d\n",x)

int main()
{
	int a = 10;
	int b = 20;
	printf("the number of ""a"" is %d\n",a);
	printf("the number of ""b"" is %d\n",b);
	PRINTF(a);
	PRINTF(b);
	return 0;
}

##的作用:把两个分离的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符

#define CAT(x,y)  x##y

int main()
{
	int CLASS2020 = 2024;
	
	printf("%d\n",CAT(CLASS,2020));//2024
	return 0;
}

带有副作用的宏参数

#define MAX(x,y) (x)>(y)?(x):(y);

int main()
{
	//int a = 10;
	//int b = a + 1;//1
	//int b = a++;//2--带有副作用,a的值改变了


	int a = 10;
	int b = 11;

	int max = MAX(a++, b++);
	printf("%d\n", max);//12
	printf("%d\n", a);  //11
	printf("%d\n", b);  //13
	return 0;
}

属性

#define定义宏

函数

代码长度

每次使用时,宏代码都会被插入到程序中,除了非常小的宏之外,程序的长度会大幅度增长

函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码

执行速度

更快

存在函数的调用和返回的额外开销,所以相对慢一些

操作符优先级

宏参数的求职是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多一些括号

函数只在函数调用的时候求职一次,他的结果值传递给函数,表达式的求值结果更容易预测

带有副作用的参数

参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预测的结果

函数参数,只在传参的时候求值一次,结果更容易控制

参数类型

宏的参数与类型无关,只要对参数的操作是合法的,他就可以使用于任何参数类型

函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的

调试

宏是不便调试的

函数是可以逐语句调试的

递归

宏是不能递归的

函数是可以递归的


命名约定:把宏名全部大写,函数名不要大写

  • 命令行定义

允许在命令行中定义符号,用于启动编译过程

  • 预处理指令#include

包含本地文本文件,用 “” -------查找策略:先在源文件所在目录下查找,如果该文件未找到,编译器就像查找库函数头文件一样,在标准位置查找头文件,如果找不到就提示编译错误

linux环境的标准头文件路径:/user/include

vs标准头文件的路径:

包含库函数文件  用 <> ------  直接在标准位置查找头文件,如果找不到就提示编译错误


使头文件不被重复包含

#ifndef  __TEST__H__

#define  __TEST__H__

#endif

第二种写法

#pragma once


  • 预处理指令#undef

用于移除一个宏定义

  • 条件编译

在编译一个程序的时候,我们如果将一条语句编译或者是放弃编译是很方便的,因为有条件编译指令

//#define DEBUG 1
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int i = 0;
	for (i=0;i<10;i++)
	{
		arr[i] = 0;
#ifdef DEBUG
		printf("%d\n", arr[i]);
#endif // DEBUG

		
	}
	return 0;

}

也是在预处理阶段进行处理,如果DEBUG没定义,则ifdef的内容不会生效

  1. #ifdef----#endif
  2. #if 常量表达式 ------ #endif
  3. 多分支条件指令
  4. 嵌套指令



其他预处理指令

#error

#pragma

#line 





inline - 内敛函数




宏参数展开

在对宏进行展开的时候,如果宏的参数也是可以展开的宏,会先把参数完全展开,再展开宏,例如

ADD_COMMA(ADD_COMMA(1, 2), ADD_COMMA(3, 4))     // -> 1, 2, 3, 4

一般情况下的宏展开,都可以认为是先对参数求值,再对宏求值,除非遇到了 # 和 ## 操作符。

# 操作符

# 操作符后面跟的宏参数,不会进行展开,会直接字符串化,例如:

#define STRINGIZE(arg0) # arg0

STRINGIZE(a)                // -> "a"
STRINGIZE(STRINGIZE(a))     // -> "STRINGIZE(a)"

根据这条规则 STRINGIZE(STRINGIZE(a)) 只能展开为 "STRINGIZE(a)"

## 操作符

## 操作符前后的宏参数,都不会进行展开,会先直接拼接起来,例如:

#define CONCAT(arg0, arg1) arg0 ## arg1

CONCAT(Hello, World)                        // -> HelloWorld
CONCAT(Hello, CONCAT(World, !))             // -> HelloCONCAT(World, !)
CONCAT(CONCAT(Hello, World) C, ONCAT(!))    // -> CONCAT(Hello, World) CONCAT(!)

CONCAT(CONCAT(Hello, World) C, ONCAT(!)) 只能是先拼接在一起,得到 CONCAT(Hello, World) CONCAT(!)





【文章原创作者:韩国机房 http://www.558idc.com/kt.html欢迎留下您的宝贵建议】
上一篇:C语言函数大全-- w 开头的函数(1)
下一篇:没有了
网友评论