纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

C语言预处理预编译命令及宏 C语言预处理预编译命令及宏定义详解

Bitdancing   2021-10-13 我要评论
想了解C语言预处理预编译命令及宏定义详解的相关内容吗Bitdancing在本文为您仔细讲解C语言预处理预编译命令及宏的相关知识和一些Code实例欢迎阅读和指正我们先划重点:C语言预处理命令,C语言预编译命令,C语言宏定义下面大家一起来学习吧。

.c 源程序 ----- 编译 ----- 链接 ---- exe ----运行 -------->

程序翻译环境和执行环境

翻译环境:源代码被转换为可执行机器指令(二进制代码)。

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

翻译环境:详解编译+链接

在这里插入图片描述

1.组成程序的每个源文件通过编译过程分别转换成目标代码。

2.每个目标文件由链接器捆绑在一起形成一个单一而完整的可执行程序。

3.链接器同时也会引入标准C函数库中任何被该程序所用到的函数而且他可以搜索程序员个人的程序库将其需要的函数也链接到程序。

extern声明外部文件中的函数

在这里插入图片描述

1. 编译 — 预处理/预编译 test.c ---- test.i

文本操作

#include 头文件的包含

注释删除:使用空格替换注释

#define 替换所以宏无法进行调试。

……

2. 编译 — 编译 test.i ---- test.s

把c语言代码翻译成汇编代码

语法分析

词法分析

语义分析

符号汇总

3. 编译 — 汇编 test.s ---- test.obj

把汇编代码转换成二进制代码(指令)。

形成符号表。(符号+地址)

4. 链接 test.obj ---- test.exe

合并段表

符号表的合并和重定位

在这里插入图片描述

运行环境

1.程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中程序的载入必须由手工安排也可能是通过可执行代码置入只读内存来完成。

2.程序的执行便开始。接着便调用main函数。

3.开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack)存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

4.终止程序。正常终止main函数;也有可能是意外终止

预处理/预编译详解

预定义符号

本来就有的符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C其值为1否则未定义

应用

printf("data: %s\n time: %s" ,__DATE__,__TIME__);

输出

data: Jul 13 2021
time: 15:13:54

#define 定义标识符

宏和define区别宏是有参数的。

下面是宏的声明方式:

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表它们可能出现在stuff中

参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在参数列表就会被解释为stuf的一部分。

例如

#define SQUARE(X) (X)*(X)
int main()
{
    int ret = SQUARE(5);
	return 0;
}

宏的参数是替换的不是传参的。

在定义宏的时候不要吝啬括号。

#和##

#的作用

使用#把一个宏参数变成对应的字符串。

把参数插入到字符串中

#define PRINT(X) printf("the value of "#X" is %d\n", X)
int main()
{
	int a = 10;
	int b = 20;
	PRINT(a);
	PRINT(b);
	return 0;
}

输出

the value of a is 10
the value of b is 20

##的作用

## 可以把位于他两边的符号合成一个符号允许宏定义从分离的文本片段创建创建标识符。

#define CAT(X,Y) X##Y
int main()
{
	int class84 = 2021;
	printf("%d\n", CAT(class, 84));
}

输出

2021

带副作用的宏参数

#define MAX(a, b)  ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);
//输出的结果是什么?

这里我们得知道预处理器处理之后的结果是什么:

z = ( (x++) > (y++) ? (x++) : (y++));

输出结果

x=6 y=10 z=9

宏和函数的对比

对于上述的宏也可以用函数实现其功能。

使用宏的优点:

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作需要的时间更多所以宏比函数在程序的规模和速度方面更胜一筹。

2.函数的参数必须声明为特定的类型所以函数只能在类型合适的表达式上使用。反之这个宏可以用于整型、长整型、浮点数等等宏是类型无关的。

使用宏的缺点:

1.每次调用宏一份宏定义的代码插入程序中除非宏比较短否则可能会大幅度增加代码的长度。

2.宏无法调试。在预编译(预处理)阶段已经把 # define 给替换了已经不再是宏了。

3.宏由于类型无关也就不够严谨。

3.宏可能会带来运算符优先级的问题更容易导致程序出错。

inline 内联函数

命名约定

函数和宏语法相似语言本身没法帮我们区分二者。把宏名全部大写函数名不要全部大写。

#undef 移除宏定义

这条指令用于移除宏定义。

如果现存的一个名字需要被重新定义那么他的旧名字首先要被移除。

#undef NAME

命令行定义

许多C的编译器提供了一种能力允许在命令行中定义符号用于在启动编译过程。例如:当我们根据一个源文件要编译出不同的一个程序的不同版本的时候这个特性有点用处。假设某个程序中声明了一个某个长度的数组如果机器内存有限我们需要一个很小的数组但是另外一个机器内存大写我们需要一个数组能够大写。

条件编译

在这里插入图片描述

#define DEBUG
#ifdef DEBUG
#endif

常见的条件编译指令

#if 常量表达式
//...
#endif

举例子:为真参与编译为假 (0)不参与编译。

#if 1
	printf("balabala....");
#endif

二、多个分支的条件编译

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//....
#endif

举例子

#if 1==1
#elif 2==1
#else
#endif

三、判断是否被定义

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

四、嵌套指令

#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

文件包含

我们已经知道#include指令可以使另外一个文件被编译。就像它实际出现于#include指令的地方一样。这种替换的方式很简单:预处理器先删除这条指令并用包含文件的内容替换。这样一个源文件被包含10次那就实际被编译10次。

头文件包含的方式:

1.本地文件包含:#include "Filename"

查找策略:先在源文件所在目录下查找如果该头文件未找到编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。

2.库文件包含:#include <Filename.h>

查找策略:查找头文件直接去标准路径下去查找如果找不到就返回错误信息。

这样是不是可以说对于库文件也可以使用“”的形式包含?

答案是肯定的可以。但是这样做查找的效率就低些当然这样也不容易区分是库文件还是本地文件了。

wwww想到自己经常重复包含留下了悔恨的泪水~~

出现嵌套文件包含解决方法 :条件编译

每个头文件开头这样写:

#ifndef __TEST__H__
#define __TEST__H__
 //头文件的内容
#endif         //__TEST__H__

或者

#pragma once

就可以避免头文件的重复引入。

总结一下:预处理阶段的预处理指令:条件编译指令 / #include / #define / #error /#pragma / ……

offsetof(宏类型成员名字)偏移量模拟实现

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
struct S
{
	char c1;
	int a;
	char c2;
};
#define OFFSETOF(struct_name, member_name) (int)&(((struct_name*)0)->member_name)
int main()
{
	printf("%d\n", OFFSETOF(struct S, c1));
	printf("%d\n", OFFSETOF(struct S, a));
	printf("%d\n", OFFSETOF(struct S, c2));
	return 0;
}

相关文章

猜您喜欢

  • AJAX省市县三级联动 AJAX实现省市县三级联动效果

    想了解AJAX实现省市县三级联动效果的相关内容吗不争亦不屑在本文为您仔细讲解AJAX省市县三级联动的相关知识和一些Code实例欢迎阅读和指正我们先划重点:AJAX省市县三级联动,AJAX省市县联动,AJAX三级联动下面大家一起来学习吧。..
  • python requests接口测试 关于python实现requests接口测试的问题

    想了解关于python实现requests接口测试的问题的相关内容吗人生是一场彩排在本文为您仔细讲解python requests接口测试的相关知识和一些Code实例欢迎阅读和指正我们先划重点:python,requests接口测试,python,requests接口下面大家一起来学习吧。..

网友评论

Copyright 2020 www.Shellfishsoft.com 【贝软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式