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

目录
  • 程序翻译环境和执行环境
  • 翻译环境:详解编译+链接
    • 1. 编译 — 预处理/预编译 test.c ---- test.i
    • 2. 编译 — 编译 test.i ---- test.s
    • 3. 编译 — 汇编 test.s ---- test.obj
    • 4. 链接 test.obj ---- test.exe
  • 运行环境
  • 预处理/预编译详解
    • #define 定义标识符
  • #和##
    • #的作用
    • ##的作用
    • 命名约定
    • 命令行定义
    • 条件编译
      • 常见的条件编译指令
    • 文件包含
    • offsetof(宏类型,成员名字)偏移量模拟实现

.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;
}

以上就是C语言预处理预编译命令及宏定义详解的详细内容,更多关于C语言预处理预编译命令及宏的资料请关注我们其它相关文章!

(0)

相关推荐

  • C语言编程之预处理过程与define及条件编译

    目录 名示常量#define 重定义常量 在#define中使用参数 预处理器粘合剂:##运算符 变参宏:- 和_ _ VAG_ARGS_ _ 宏与函数 预处理指令 #undef指令 从C预处理器的角度看已定义 条件编译 offsetof函数 这张图描述了从源文件到可执行文件的整体步骤 这张图展示了大体上步骤. 从代码到运行环境,编译器提供了翻译环境.在一个程序中,会存在多个文件 ,而每个源文件都会单独经过编译器处理. 预编译: 1,会将#include等头文件所包含的内容,库函数全部拷贝过来

  • C语言之预处理命令的深入讲解

    c提供的预处理功能有: 宏定义 文件包含 条件编译 为了与其她c语句区分,命令经常以符号"#"开头. 宏定义 #define 标识符 字符串 可以避免反复输入字符串,后面不加:宏定义在默认时的有效范围是全部.也可以用#undef终止宏定义区域. 不含参数 宏展开带入程序 含参数 #include<stdio.h> #define PI 3.1415 #define S(r) PI*r*r int main() { int a; float area; scanf("

  • 详解C语言#define预处理宏定义

    目录 #define介绍: #define宏定义无参的一般形式为:#define  标识符 常量 #define宏定义有参的一般形式为:#define  标识符(参数表) 表达式 #运算符: ##运算符: 可变宏...和__VA_ARGS__: 开发项目中常用的宏定义: #define介绍: C语言里可以用#define定义一个标识符来表示一个常量.特点是:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了,也不做类型定义.预编译又叫预处理.预编译就是编译前的处理.这个操作是在

  • C语言从编译到运行过程详解

    目录 C语言从编译到运行 一.前言 二.C程序编译过程 三.阶段过程 1.预处理阶段 2.编译阶段 3.汇编阶段 4.链接阶段 C语言从编译到运行 一.前言 最近在看CSAPP(深入理解计算机系统)然后以前也学过C语言,但是从来没有深究写好的C代码是怎么编译再到执行的. 所以现在自己学习,然后记录下来. 以最常用的hello world!程序为例 程序名: main.c #include <stdio.h> int main() { printf("Hello world!\n&qu

  • 常用C/C++预处理指令详解

    预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查.预处理命令以符号"#"开头. 常用的预处理指令包括: 宏定义:#define 文件包含:#include 条件编译:#if.#elif.#ifndef.#ifdef.#endif.#undef 错误信息指令:#error #line指令 布局控制:#pragma 宏定义 宏定义又称为宏代换.宏替换,简称"宏".宏替换只作替换,不做计算,不做表达式求解.宏定义分带参数的宏定义和不带参数的宏

  • C语言的基本语法详解

    目录 1.标识符与关键字 2.常量和符号常量 (1)常量和常量符号 (2)变量 3.C语言数据类型 (1)整型常量 整型变量 原码.反码和补码 (2)实型数据 实型常量 实型变量 实型变量的定义以及初始化 (3)字符型数据 ASCII码 字符型变量 转义字符字符 字符串常量 字符串变量 总结 1.标识符与关键字 给变量所取的名字叫变量名,定义变量的名字需要遵循标识符的命名规则. 标识符是用来标识变量.符号常量.数组.函数.文件等名字的有效字符序列. 标识符的命名规则: 1.只能由字母.数字和下划

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

    目录 程序翻译环境和执行环境 翻译环境:详解编译+链接 1. 编译 - 预处理/预编译 test.c ---- test.i 2. 编译 - 编译 test.i ---- test.s 3. 编译 - 汇编 test.s ---- test.obj 4. 链接 test.obj ---- test.exe 运行环境 预处理/预编译详解 #define 定义标识符 #和## #的作用 ##的作用 命名约定 命令行定义 条件编译 常见的条件编译指令 文件包含 offsetof(宏类型,成员名字)偏移

  • Go语言基础go build命令用法及示例详解

    目录 go build 一个Go项目在GOPATH下,会有如下三个目录 使用: 注意: go build 1. 用于测试编译多个包或一个main包 2. build命令编译包丢弃非main包编译结果,只是检查是否能够被编译 3. 保留main包编译结果 一个Go项目在GOPATH下,会有如下三个目录 bin存放编译后的可执行文件 pkg存放编译后的包文件 src存放项目源文件 一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可. 使用:

  • Go语言基础go doc命令用法及示例详解

    目录 go doc 一.使用go doc命令在终端查看 go doc package go doc package/subpackage go doc package function 二.是使用浏览器查看的方式 go doc 为我们提供了快速生成文档以及查看文档的工具,让我们可以很容易的编写查看文档 一.使用go doc命令在终端查看 go doc package 获取包的文档注释 例如: go doc fmt 会显示使用 godoc 生成的 fmt 包的文档注释. go doc packag

  • 基于C中含有if的宏定义详解

    含有if的宏定义当宏定义中含有 if 时1) 定义如下宏#define DC(p) if( foo(p) )fun(p)用在下面的环境中if(k>n)DC(k);elseDC(n);宏替换后,如下if(k>n)if( foo(k) )fun(k);elseif( foo(n) )fun( n );可见, 原来的 if 和 else 不再配对.2) 为了避免这类问题, 我们可以将包含if语句的宏定义为一个整体.#define DC(p) {if( foo(p) ) fun(p);}但是, 替换后

  • .net预编译命令详解(图)

    Net预编译命令 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -? 显示说明 我们需要选择的命令为 aspnet_compiler.exe -m IIS元数据库 (可是怎么获得IIS元数据库喃) 使用IIS资源管理工具 下载地址:http://download.csdn.net/detail/cake_green/6756847 这个要分的哈 大家也可以百度 打开 Metabase 根据路径就可以找到你的网站

  • C语言中带返回值的宏定义方式

    目录 C语言中带返回值的宏定义 宏定义编写 宏定义分析 宏定义验证 经验总结 C语言中一些宏定义和常用的函数 typeof 关键字 snprintf()函数的作用 __builtin_expect的作用 C语言中常用的预定义 反斜杠的作用 总结 C语言中带返回值的宏定义 相信大家在实际工作中,一定有遇到需要编写一个宏定义,且希望它能带返回值的场景吧? 比如我之前就遇到一个场景,早期的代码是使用函数实现的功能,现在想换成宏定义,但是又要保留之前调用函数的代码不动,这样我就只能想办法写一个带返回值的

  • 基于make命令与makefile文件详解

    一.多个源文件带来的问题 在编写c/c++测试程序时,我们习惯每次修改一处代码,然后就马上编译运行来查看运行的结果.这种编译方式对于小程序来说是没有多大问题的,可对于大型程序来说,由于包含了大量的源文件,如果每次改动一个地方都需要编译所有的源文件,这个简单的直接编译所有源文件方式对程序员来说简直是噩耗. 我们看一个例子: // main.c #include "a.h" // 2.c #include "a.h" #include "b.h" /

  • Linux下tcpdump命令解析及使用详解

    简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具.tcpdump可以将网络中传送的数据包的"头"完全截获下来提供分析.它支持针对网络层.协议.主机.网络或端口的过滤,并提供and.or.not等逻辑语句来帮助你去掉无用的信息. 实用命令实例 默认启动 tcpdump 普通情况下,直接启动tcpdump将监视第一个网络接口上所有流过的数据包. 监视指定网络接口的数据包 tcpdum

  • go语言入门环境搭建及GoLand安装教程详解

    Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型.编译型语言.Go 语言语法与 C 相近,但功能上有:内存安全,GC(垃圾回收),结构形态及 CSP-style 并发计算. Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.

  • 语言编程花絮内建构建顺序示例详解

    目录 1 构建 顺序 1.1 交叉编译 1.2 设置 2 构建测试支持 1 构建 顺序 依据词法名顺序 当导入一个包,且这个包 定义了 init(), 那么导入时init()将被执行. 具体执行顺序: 全局变量定义时的函数 import 执行导入 -> cont 执行常量 --> var 执行变量 --> 执行初始化 init() --> 执行 main() ----> main import pk1 ---> pk1 const ... import pk2 ---&

随机推荐