0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C/C++预处理命令的工作原理及分类

CHANBAEK 来源:明解嵌入式 作者:Sharemaker001 2023-04-15 11:32 次阅读

摘要:在C/C++语言编程过程中,经常会用到如#include、#define等指令,同时也会涉及到大量的预处理与条件编译,这样做的好处可以使代码更利于移植移植性,也让代码易于修改。 因此引入了预处理与条件编译的概念。

预处理的行为是由指令控制的。 所有的预处理器命令都是以#开头,它必须是第一个非空字符。 预处理指令由预处理程序(预处理器)操作。

预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。 因此, 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。 通俗来讲预处理命令的作用就是在编译和链接之前,对源文件进行一些文本方面的操作,比如文本替换、文件包含、删除部分代码等,这个过程叫做预处理(在编译之前对源文件进行简单加工)

相比其他编程语言,C/C++语言更依赖预处理器,故在阅读或开发C/C++程序过程中,可能会接触大量的预处理指令。 预处理指令不属于C/C++语言的语法,但在一定意义上可以说预处理扩展了C/C++。 预处理命令的分类主要划分为以下几种类型:

1、宏定义

#define命令并不是真正的定义符号常量,而是定义一个可以替换的宏。 被定义为宏的标示符称为“宏名”。 在编译预处理过程时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。 宏替换在编译前进行,不分配内存; 宏展开不占运行时间,只占编译时间; 宏替换只作替换,不做计算。

#define NEMBER 9 //#define 宏名 文本
#define M(a, b) a*b //#define 宏名(参数表) 文本
#define SWITCHON  //#define 宏名  //(定义一个条件编译的开关字段)
#define NAME(n) num ## n   //宏定义,使用 ## 运算符,粘合的作用
int num0 = 10;
printf("num0 = %d\\n", NAME(0));//宏调用NAME(0)被替换为 num ## 0,被粘合为:num0
//可变宏:… 和 __VA_ARGS__
#define PR(...) printf(__VA_ARGS__)     //宏定义
PR("hello\\n");                          //宏调用
//输出结果:hello
//在宏定义中,形参列表的最后一个参数为省略号“…”,而“__VA_ARGS__”就可以被用在替换文本中,来表示省略号“…”代表了什么。
//而上面例子宏代换之后为:printf(“hello\\n”);

#undef指令删除前面定义的宏名字(也就是#define的标识符)。 也就是说,它“不定义”宏。 (注意:如果标识符当前没有被定义成一个宏名称,那么就会忽略该指令),一般形式为:

#undef  NEMBER      //取消之前已定义的NEMBER
#define NEMBER 100  //重新定义NUMBE为100

2、系统预定义的宏

LINE : 当前源文件的行号,整数

FILE : 当前源文件名,char 字符串,文件的完整路径和文件名**

DATE : 当前编译日期,char 字符串,格式:月 日 年

TIME : 当前编译时间,char 字符串,格式:时 分 秒

STDC : 整数 1,表示兼容 ANSI/ISO C 标准,配合 #if 使用

**TIMESTAMP ** : 最后一次修改当前文件的时间戳,char 字符串,格式:年 月份 日期 时 分 秒

3、文件包含

当一个C语言程序由多个文件模块组成时,主模块中一般包含main函数和一些当前程序专用的函数。 程序从main函数开始执行,在执行过程中,可调用当前文件中的函数,也可调用其他文件模块中的函数。

如果在模块中要调用其他文件模块中的函数,首先必须在主模块中声明该函数原型。 一般都是采用文件包含的方法,包含其他文件模块的头文件。

包含文件的格式有#include后面跟尖括号<>和双引号“”之分。 两者的主要差别是搜索路径的不同。 C的标准库加.h,C++标准库可以不加.h。

尖括号形式:如#include

双引号形式:如#include“para.h”,首先到当前工作目录下查找该文件,如果未发现,再按尖括号包含时的办法到系统目录下查找。 包含自定义的头文件,一般采用该方式。 虽然系统标准库头文件采用此方式也正确,但浪费了不必要的搜索时间,故系统标准库头文件不建议采用该包含方式。

4、条件编译

条件编译允许程序员有选择按照不同的条件去编译程序的不同部分,从而得到不同的目标代码。 使用条件编译,可方便地处理程序的调试版本和正式版本,也可使用条件编译使程序的移植更方便。

常见的条件编译指令有 #if、#elif、#else、#endif、#ifdef、#ifndef。

#if、#elif、#else、#endif的使用和if、elseif 、else的使用非常相似,一般使用格式如下:

#if 整型常量表达式1
    程序段1
#elif 整型常量表达式2
    程序段2
#else
    程序段3
#endif

执行起来就是,如果整形常量表达式为真,则执行程序段1,否则继续往后判断依次类推(注意是整形常量表达式),最后#endif是#if的结束标志。

#ifdef的作用是判断某个宏是否定义,如果该宏已经定义则执行后面的代码,#ifndef恰好和#ifdef相反,一般使用格式如下:

//ifdef
#ifdef  宏名
    程序段1
#else
    程序段2
#endif
//ifndef
#ifndef 宏名
    程序段1 
#else 
    程序段2 
#endif

#ifdef表示如果该宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译(这个和上面的#if一样最后都需要#endif),上述格式也可以不用#else,这一点上和if else相同。

#ifndef表示如果该宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译。

5、特殊命令

#line 可以改变 LINE 和 _FILE_两个宏的内容,即为其指定新的值。 其本质是重定义 LINEFILE ,主要有以下两种形式:

#line linenum

#line linenum 文件名

int main()
{
   printf( "code is on line %d, in file %s\\n", __LINE__, __FILE__ );
#line 10
   printf( "code is on line %d, in file %s\\n", __LINE__, __FILE__ );
#line 20 "hello.cpp"
   printf( "code is on line %d, in file %s\\n", __LINE__, __FILE__ );
   printf( "code is on line %d, in file %s\\n", __LINE__, __FILE__ );
}

输出为:

code is on line 7, in file line_directive.cpp
code is on line 10, in file line_directive.cpp
code is on line 20, in file hello.cpp
code is on line 21, in file hello.cpp

#error :当预处理器预处理到#error命令时将停止编译并输出用户自定义的错误消息,一般用于调试程序。

#error [用户自定义的错误消息]
//注:上述语法成份中的方括号"[]"代表用户自定义的错误消息可以省略不写。
//举例1:
#error Sorry,an error has occurred!
//举例2: 
#error


#ifndef A
    #define A 5
#endif

#if A < 5
    #error Sorry,an error has occurred!
#endif

#warning :****类似于#error 指令,但不会导致取消预处理,程序继续编译,不会影响程序的正常运行。 #warning 指令之后的信息在预处理继续之前作为消息输出,产生警告。

#warning [用户自定义的警告信息]
#warning Sorry,an warning has occurred!

#pragma:是功能比较丰富且灵活的指令,可以有不同的参数选择,从而完成相应的特 定功能操作。 #pragma指令是计算机或操作系统特定的,并且通常对于每个编译器而言都有所不同。 #pragma指令可用于条件语句以提供新的预处理器功能,或为编译器提供实现所定义的信息

其格式一般为: #pragma Para。 其中Para 为参数,参数可以有 message 类型、code_seg、once、warning、pack 等,具体可以在网上详细查看。 举两个常用的例子:

#pragma一次

只要在头文件的最开始加入这条指令就能够保证指定该文件在编译源代码文件时仅由编译器包含(打开)一次,使用 #pragma once 可减少生成次数,和使用预处理宏定义来避免多次包含文件的内容的效果是一样的,但是需要键入的代码少,可减少错误率,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。

//使用#progma once
#pragma once  
// Code placed here is included only once per translation unit 


//使用宏定义方式
#ifndef HEADER_H_ 
#define HEADER_H_  
// Code placed here is included only once per translation unit  
#endif // HEADER_H_

#pragma once是编译相关,就是说这个编译系统上能用,但在其他编译系统不一定可以,也就是说移植性差,不过基本上已经是每个编译器都有这个定义了。

#pragma包 (n)

指定结构、联合和类成员的封装对齐。 其实就是改变编译器的内存对齐方式。 这个功能对于集合数据体使用,默认的数据的对齐方式占用内存比较大,可进行修改。 在没有参数的情况下调用pack会将n设置为编译器选项中设置的值。 如果未设置编译器选项,windows默认为8,linux默认为4。 具体的使用方法为,其中n称为对齐系数,取值必须是2的幂次方,即1、2、4、8、16等。

1. #pragma pack(show)     以警告信息的形式显示当前字节对齐的值.
2. #pragma pack(n)        将当前字节对齐值设为 n .
3. #pragma pack()         将当前字节对齐值设为默认值(通常是8) .
4. #pragma pack(push)     将当前字节对齐值压入编译栈栈顶.
5. #pragma pack(pop)      将编译栈栈顶的字节对齐值弹出并设为当前值.
6. #pragma pack(push, n)  先将当前字节对齐值压入编译栈栈顶, 然后再将 n 设为当前值.
7. #pragma pack(pop, n)   将编译栈栈顶的字节对齐值弹出, 然后丢弃, 再将 n 设为当前值.
8. #pragma pack(push, identifier)        将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier .
10. #pragma pack(pop, identifier)        将编译栈栈中标识为 identifier 位置的值弹出, 并将其设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃.
11. #pragma pack(push, identifier, n)    将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier, 再将 n 设为当前值.
12. #pragma pack(pop, identifier, n)     将编译栈栈中标识为 identifier 位置的值弹出, 然后丢弃, 再将 n 设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃.
//注意: 如果在栈中没有找到 pop 中的标识符, 则编译器忽略该指令, 而且不会弹出任何值.

通常成对使用:

#pragma pack (n)          //作用:编译器将按照n个字节对齐。
#pragma pack ()           //作用:取消自定义字节对齐方式。


#pragma  pack (push,1)    //作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为一个字节对齐
#pragma pack(pop)         //作用:恢复对齐状态
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    19275

    浏览量

    229737
  • 函数
    +关注

    关注

    3

    文章

    4329

    浏览量

    62586
  • 命令
    +关注

    关注

    5

    文章

    684

    浏览量

    22019
  • C++
    C++
    +关注

    关注

    22

    文章

    2108

    浏览量

    73634
  • 编译器
    +关注

    关注

    1

    文章

    1634

    浏览量

    49116
收藏 人收藏

    评论

    相关推荐

    C语言常用的预处理命令

    1.基础知识(1)C语言常用的预处理命令——typedef具体可见C语言基础语法知识。(2)延时函数(知识粗略估计延时时间,若要精确延时,需要用到后续的定时器)void delay(u
    发表于 07-14 07:10

    c语言预处理命令以什么开头

    c语言预处理命令以什么开头,目前我并没有windows软件编写经验,对C语言的应用也仅限于各种单片机的编程,所以对预处理的理解也只限于单片机
    发表于 07-20 07:00

    C语言预处理命令有哪些?

    不止。先看几个个常识性问题: A) 预处理C 语言的一部分吗? B) 包含“#”号的都是预处理吗? C) 预处理指令后面都不需要加“;”号
    发表于 06-25 06:15

    Tcl/Tk命令C/C++的集成研究

    针对 Tcl/Tk 脚本中需要调用C/C++函数的问题,简要说明了Tcl/Tk 命令的运行机理,给出了一个使用Tcl/Tk 命令来调用C/
    发表于 08-26 09:47 36次下载

    预处理器的工作原理作用

    预处理器的工作原理作用,希望对学者们有帮助。
    发表于 10-29 11:40 0次下载

    基于51单片机--C语言之预处理总结

    编译预处理器是C语言编译器的一个重要组成部分。很好的利用C语言的预处理命令可以增强代码的可读性,灵活性,和易于修改等特点,便于程序的结构化。
    发表于 08-17 10:48 1080次阅读

    C语言常用的预处理命令和循环左移右移函数的详细资料概述

    本文档的主要内容详细介绍的是C语言常用的预处理命令和循环左移右移函数的详细资料概述。
    发表于 10-22 08:00 71次下载

    C语言程序设计教程之预处理命令的详细资料说明

    预处理命令的主要内容:三种预处理命令,宏定义,文件包含,条件编译
    发表于 02-26 14:43 13次下载
    <b class='flag-5'>C</b>语言程序设计教程之<b class='flag-5'>预处理</b><b class='flag-5'>命令</b>的详细资料说明

    C++的const多文件编译预处理的资料说明

    本文档的主要内容详细介绍的是C++的const多文件编译预处理的资料说明包括了:1、const型常量,2、常对象,3、常成员函数,4、常数据成员,5、常引用,6、多文件,7、编译预处,8、多文件结构中使用编译预处理的问题
    发表于 04-03 08:00 0次下载
    <b class='flag-5'>C++</b>的const多文件编译<b class='flag-5'>预处理</b>的资料说明

    8051单片机的预处理命令的详细资料说明

    在编写程序时,经常会使用以“#”开头的预处理命令。在对程序进行编译时,会有专门的预处理程序来对这些命令进行处理
    发表于 06-11 17:47 0次下载
    8051单片机的<b class='flag-5'>预处理</b><b class='flag-5'>命令</b>的详细资料说明

    C51的预处理命令和用户配置文件详细资料说明

    用户编写的C51程序代码只能控制程序的执行流程,若要对编译程序进行操作,就要用到预处理命令。在编译环境对源程序进行编译前,先对程序中的预处理命令
    发表于 03-19 14:52 6次下载
    <b class='flag-5'>C</b>51的<b class='flag-5'>预处理</b><b class='flag-5'>命令</b>和用户配置文件详细资料说明

    C语言预处理命令分类工作原理详细说明

    器,故在阅读或开发 C/C++ 程序过程中,可能会接触大量的预处理指令。 1、预处理指令及分类 C
    发表于 11-25 10:34 18次下载
    <b class='flag-5'>C</b>语言<b class='flag-5'>预处理</b><b class='flag-5'>命令</b>的<b class='flag-5'>分类</b>和<b class='flag-5'>工作原理</b>详细说明

    C语言预处理指令及分类

    C/C++ 程序中的源代码中包含以 # 开头的各种编译指令,这些指令称为预处理指令。预处理指令不属于 C/
    的头像 发表于 11-29 10:14 2263次阅读

    C语言预处理命令是什么

    我们在写C语言程序时经常使用库函数之前,应该用`#include`引入对应的头文件。这种以`#`号开头的命令称为预处理命令
    的头像 发表于 02-17 13:59 2674次阅读
    <b class='flag-5'>C</b>语言<b class='flag-5'>预处理</b><b class='flag-5'>命令</b>是什么

    C预处理器及其工作原理

    C预处理器(C Pre-Processor)也常简写为 CPP,是一个与 C 编译器独立的小程序,预编译器并不理解 C 语言语法,它仅是在程
    的头像 发表于 03-12 14:14 629次阅读
    <b class='flag-5'>C</b><b class='flag-5'>预处理</b>器及其<b class='flag-5'>工作原理</b>