C 预处理指令

0. Overview

C的预处理指令格式为#name,均以#开头,#和指令名之间不可有空白字符,#前可以有空字符,但为增强可读性,一般应从第一列开始

#name不能由宏展开得来,name也不能由宏展开得来,如

// Wrong 1
#define INC #include
INC <stdio.h>
// Wrong 2
#define INC include
#INC <stdio.h>

预处理指令只能占一行,但是在写代码时可以用'\'分隔多行,但处理时仍会将这多行合为一行。有些指令带参数,参数需与指令由空白字符分隔

预处理指令主要提供下列功能:

  • 引入头文件
  • 宏展开
  • 条件编译
  • line control:#line(感觉一般人用不着)
  • 诊断(diagnostics):可在编译器检查程序,发出errors或warnings

1. 头文件

#include来包含头文件,该指令的参数形式有两种:

  1. #include <file>
    用于系统头文件。Preprocessor将在a standard list of system directories下搜寻文件file,可以用编译器的-I选项来将目录添加到这个list
  2. #include "file"
    用于程序自身的头文件。Preprocessor的搜寻顺序如下: a. 先在包含该文件的当前目录搜寻文件file, b. 然后在quote directories中搜寻,可以用编译器的-iquote选项来将目录添加到quote directories中 c. 最后再在用于搜寻<file>的目录下搜寻(即 1. 中的顺序)(所以用#include "stdio.h"只要你不覆盖这个头文件的话也不会出现问题,总能找着)

#include的参数无论是用""还是<>括起来,都如同一个字符串,里面的注释不会被识别,宏也不会展开。但是不同于字符串的是,backslash不再有转义作用,而是一个单纯的字符'\'

在这一行,文件名参数后面除了注释外不能有任何其他内容

只包含一次 Once-Only Headers

如果一个头文件被include两次,编译器就会处理两次,因此可能会出错,如重定义等等,标准做法是用所谓的wrapper #ifndef将头文件的内容包起来,如:

/* File foo.  */
#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEENthe entire file#endif /* !FILE_FOO_SEEN */

代码片段中的宏FILE_FOO_SEEN叫做controlling macro或者guard macro,在用户程序头文件中,该宏的名字不能_开头,在系统头文件中,该宏的名字需要__(双下划线)开头以免与用户程序头文件冲突。在任意类型的头文件中,该宏的名字应该包含头文件文件名再加上额外的文字以避免与其他头文件冲突

2. 宏 Macros

宏是赋予名字的一段代码,每次使用时都将名字替换成宏内容。宏分为两种,它们在使用时有很大的不同:

  1. Object-like macros:使用时像用data objects一样
  2. Function-like macros:使用时像函数调用一样

2.1 对象形式的宏 Object-like macros

Object-like macro就是一个简单的标志符,表示一个代码片段,在使用时由这个代码片段来替换,用法:

#define NAME macro_body

宏body又叫expansion或replacement list,是一个token序列

按照惯例,宏的名字一般用大写字母

#define macro_body也只占一行,并且macro_body后面不能有其他内容(除空白字符或注释外),在写代码时也可以用'\'分隔多行,但预处理时仍会将它们合为一行

C preprocessor顺序地扫描源程序,因此宏定义只从定义处开始生效

宏展开是递归进行的,preprocessor将一个宏展开后会接着处理展开后的结果,如果这里面有其他的宏,会继续展开下去。但是如果结果里面再次出现刚刚展开的这个宏的话将不会展开第二次,以免出现无限递归的情况

#define TABLESIZE BUFSIZE
#define BUFSIZE 1024
TABLESIZE
// -> 先展开为 BUFSIZE
// -> 再展开为 1024

注意虽然宏会展开多次,但是每次的展开过程只是单纯地用body替换name,如上面的例子中在展开TABLESIZE时只是单纯地用BUFSIZE来替换它,接下来preprocessor才检查替换结果是不是另一个宏

2.2 函数形式的宏 Function-like macros

如其名,这种宏使用起来像函数调用一样。用法:

#define name() body

注意,小括号()必须和宏的名字连在一起,否则会被当成object-like宏来展开,同时,在使用时也必须用name()的形式(此时name()间可以有空格,2.3中同),只用name的话不会被展开

2.3 宏参数

Function-like宏像函数一样可以接受参数,用法:

#define name(params_list)

其中params_list是参数列表,参数必须是有效的C标志符,由,分隔(参数列表中可以出现空格,但是空格没有实际作用)

在“调用”函数形式的宏时,将实参列表写在宏name后面的小括号里,由,分隔,函数形式宏的“调用”不限制在一行内,可以写成多行,但是参数数量必须和定义时的数量相匹配。可以实参可以是空,但是数量也必须匹配(直白讲即逗号数量必须一致),如:

min(, b)        → ((   ) < (b) ? (   ) : (b))
min(a, )        → ((a  ) < ( ) ? (a  ) : ( ))
min(,)          → ((   ) < ( ) ? (   ) : ( ))
min((,),)       → (((,)) < ( ) ? ((,)) : ( ))min()      error→ macro "min" requires 2 arguments, but only 1 given
min(,,)    error→ macro "min" passed 3 arguments, but takes just 2

#define min(X, Y)  ((X) < (Y) ? (X) : (Y))x = min(a, b);          →  x = ((a) < (b) ? (a) : (b));y = min(1, 2);          →  y = ((1) < (2) ? (1) : (2));z = min(a + 28, *p);    →  z = ((a + 28) < (*p) ? (a + 28) : (*p));

在展开时会去除各个实参的leading、trailing whitespace,实参的token序列中的whitespace会减成一个空格。在每个实参中,小括号必须平衡,小括号中的逗号不会结束这个参数(即小括号中的逗号不是实参分隔符),但中括号和大括号不要求平衡,而且它们中的逗号会作为实参分隔符截断这个参数

宏定义中若参数出现在字符串中,在展开时不会展开成相应实参,如:

#define foo(x) x, "x"
foo(bar)        → bar, "x"

2.4 字符串化 Stringizing

有时可能需要讲宏参数转换成字符串常量,但是在 2.3 的最后提到字符串中的参数不会被实参替换,为了解决这个问题,可以用预处理操作符#来进行转换。当参数有一个前导#时,preprocessor会将其替换为实参,再转换成字符串常量,但是这个过程发生后,被转换成的字符串中如果还有宏则不会继续展开,如果还想继续展开,则需要写成多级宏的形式,如:

#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)→ "foo"
xstr (foo)→ xstr (4)→ str (4)→ "4"

2.5 拼接

预处理操作符##用于在宏body中将两个tokens拼在一起,如A ## B将展开为AB,要求展开后必须是一个有效的C标志符,如一个标志符和数字拼接,两个数字间的拼接,一些复合操作符如+=的拼接等等,有些拼接是无效的,如x+

拼接常见的应用场景为宏参数间的拼接,如:

#define COMMAND(NAME)  { #NAME, NAME ## _command }struct command commands[] =
{COMMAND (quit),COMMAND (help),…
};

2.6 取消宏定义

#undef name用于取消宏定义,name可以是object-like宏的名字,或者是function-like宏的名字(不用加小括号以及参数列表)

3. 条件编译

3.1 条件编译常用场景

  • 根据机器架构或操作系统的不同使用不同的代码
  • 将原文件编译成两个不同的程序,其中一个版本可能会用于输出一些data进行debugging等等
  • 使用#if 0来将排除一段代码,但将其保留在源文件中用作注释

3.2 条件编译语法

ifdefifndef

#ifdef MACROcontrolled text#endif /* MACRO */

if

#if expressioncontrolled text#endif /* expression */

expression是一个integer类型的C表达式,可以包含

  • 整形常量
  • 字符常量
  • 数学运算表达式和逻辑运算表达式(遵循短路求值)
  • 宏,在计算宏所代表的表达式前将先展开所有的宏
  • defined预处理指令
  • 所有不是宏的标志符都视为数字0,函数形式的宏但没有调用实参列表也视为0

defined

用在#if#elif表达式中,用于测试一个名字是否被定义成了一个宏,defined namedefiend ( name )作用相同:如果name定义为了一个宏,则表达式值为1,否则为0,因此#if defined MACRO等价于#ifdef MACRO

在测试多个宏是否存在时defined比较有用,如:

#if defined (__vax__) || defined (__ns16000__)

else

可以用在#if#ifdef#ifndef

elif

elif不需要一个#endif和其匹配

4. 诊断信息

  • #error导致preprocessor产生一个fatal error,#error所在行的剩余tokens组成错误信息
  • #warning导致preprocessor产生一个warning并继续预处理,#warning所在行的剩余tokens组成错误信息

两者都不对其参数进行宏展开

参考

  • GNU - The C Preprocessor: Macros

转载于:https://www.cnblogs.com/jerrywossion/p/11071192.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386588.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows NAT端口映射

Windows本身命令行支持配置端口映射&#xff0c;条件是已经安装了IPV6&#xff0c;启不启用都无所谓&#xff0c;我在win7和server2008上是可以的。xp&#xff0c;2003装了ipv6协议也是可以的。 CMD下操作 增加端口映射&#xff0c;将10.10.10.10的8080映射到10.10.10.11的80…

阿里P8大牛亲自教你!史上最全的Android面试题集锦,这原因我服了

一、架构师专题 想要掌握复杂的技术&#xff0c;必须要理解其原理和架构。本模块结合实际一线互联网大型项目理解架构思维&#xff0c;抽丝剥茧&#xff0c;层层深入&#xff0c;帮助大家成为Android架构师&#xff0c;在思想上对架构认识有一次升华&#xff0c;并知其所以然&a…

面向对象程序设计——UML分析和本学期总结

​ 随着第四单元UML第二次作业的结束&#xff0c;本学期的OO学习也宣告结束了&#xff08;但还得写博客&#xff09;&#xff0c;下面就对本单元和本次作业做一个总结。 第四单元两次作业的架构设计 ​ 本单元是对UML的结构进行解析&#xff0c;第一次作业是对UML类图的解析&am…

docker linux k8s kubeadm

一. 安装docker 1.添加yum国内依赖 yum -y install yum-utils yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo2.安装docker yum -y install docker-ce docker-ce-cli containerd.io3.启动docker systemctl start docker4…

小程序FMP优化实录,大厂面试题汇总

前言 金九银十面试季&#xff0c;相信大家肯定急需一套Android面试宝典&#xff0c;今天小编就给大家准备了我珍藏已久的Android高阶面试宝典&#xff0c;一份超级详细的Android面试必备知识点&#xff0c;供大家学习 &#xff01; 想必每一个安卓程序员都有追求大厂的决心&a…

文件CRC和MD5校验

文件CRC和MD5校验 CRC和MD5用于文件和数据的传输校验&#xff0c;以确认是否接收成功。 unit CRCMD5;interface { 获取文件CRC校验码 } function GetFileCRC(const iFileName: string): String; { 获取字符串CRC校验码 } function GetStringCRC(const Str: string): Cardinal; …

Oracle字符分隔函数(split)

为了让 PL/SQL 函数返回数据的多个行&#xff0c;必须通过返回一个 REF CURSOR 或一个数据集合来完成。REF CURSOR 的这种情况局限于可以从查询中选择的数据&#xff0c;而整个集合在可以返回前&#xff0c;必须进行具体化。Oracle 9i 通过引入的管道化表函数纠正了后一种情况。…

已成功拿下字节、腾讯、脉脉offer,吐血整理

为什么想跳槽&#xff1f; 简单说一下当时的状况&#xff0c;我在这家公司做了两年多&#xff0c;这两年多完成了一个大项目&#xff0c;作为开发的核心主力&#xff0c;开发压力很大&#xff0c;特别是项目上线前的几个月是非常辛苦&#xff0c;几乎每晚都要加班到12点以后&a…

复杂HTML解析

#再端一碗BeautifulSoup #获取《战争与和平》中的人物名字from urllib.request import urlopen from bs4 import BeautifulSouphtml urlopen("http://www.pythonscraping.com/pages/warandpeace.html") bsObj BeautifulSoup(html,html.parser)#namelist bsObj.fin…

java main方法里调用mapper

在main方法中调用mybatis的mapper&#xff0c;一次性执行导入数据功能package com.runxsoft.test;import com.runxsoft.iutils.common.utils.UserUtils; import com.runxsoft.superwe.base.SqlVo; import com.runxsoft.superwe.base.mapper.ProtogenesisMapper; import com.run…

已成功拿下字节、腾讯、脉脉offer,满满干货指导

开头 笼统来说&#xff0c;中年程序员容易被淘汰的原因其实不外乎三点。 1、输出能力已到顶点。这个人奋斗十来年了&#xff0c;依旧碌碌无为&#xff0c;很明显这人的天花板就这样了&#xff0c;说白了&#xff0c;天赋就这样。 2、适应能力越来越差。年纪大&#xff0c;有家…

ServletRequest HttpServletRequest 请求方法 获取请求参数 请求转发 请求包含 请求转发与重定向区别 获取请求头字段...

原文地址:ServletRequest HttpServletRequest 请求方法 获取请求参数 请求转发 请求包含 请求转发与重定向区别 获取请求头字段ServletRequest 基本概念 JavaWeb中的 "Request"对象 实际为 HttpServletRequest 或者 ServletRequest, 两者都为接口服务器接收请求…

c#扫描图片去黑边(扫描仪去黑边)

/// <summary> /// 自动去除图像扫描黑边 /// </summary> /// <param name"fileName"></param> public static void AutoCutBlackEdge(string fileName) { //打开图像 Bit…

已成功拿下字节、腾讯、脉脉offer,算法太TM重要了

一、背景介绍 从实用角度梳理一篇能够帮大家快速扫盲的CMake基础教程&#xff0c;也是对我目前负责项目的一次学习总结。既然选择从项目实用性考虑&#xff0c;下面的讲解内容可能并不一定完整&#xff0c;更多的是符合项目目前使用到的一些特性。 接下来正面回答这个问题&am…

SpringBoot2.0 Actuator 监控参数说明

主要内容更 监控参数说明 Maven坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency><groupId>io.micrometer</groupId>&…

带你一步一步深入Handler源码,醍醐灌顶!

开头 最近有粉丝反应&#xff0c;不想做安卓了&#xff0c;有朋友转到前端了&#xff0c;安卓不行了&#xff0c;问我怎么办&#xff1f; 自从RN&#xff0c;Weex这种跨平台编程语言出来以后&#xff0c;安卓将死的言论总是不绝于耳。随着颇有摧枯拉朽之势Flutter的出现&…

Spring基于状态机squirrel-foundation简单使用

squirrel-foundation的一些使用方法在百度上资料还是比较少&#xff0c;我是根据以下三个大佬写的文章借鉴的&#xff0c;在这里记录一下。 1、squirrel-foundation-demo 2、Squirrel使用&#xff08;中文文档&#xff09; 3、squirrel-foundation状态机的使用细节 我在这里直接…

记得把每一次面试当做经验积累,深夜思考

开头 Android开发&#xff0c;假如开始没有任何的开发经验的话&#xff0c; 千万不要着急&#xff0c;不要想着在短时间内就把一个语言学习好&#xff0c; 因为你之前没有任何的学习经验&#xff0c; 在这个过程中需要有耐心地学习完JAVA的基础知识&#xff0c; 然后才开始踏上…

squirrel-foundation-demo

一个简单的squirrel-foundation-demo 利用状态机模拟一个订单的支付过程。 squirrel-foundation没有任何严重的依赖关系&#xff0c;因此基本上它应该是高度可嵌入的。squirrel-foundation没有整合spring框架&#xff0c;所以首先要用spring集成squirrel-foundation。spring集成…

MongoDB学习目录

MongoDB基础篇 MongoDB 之 $ 关键字 python操作MongoDB 转载于:https://www.cnblogs.com/yanzhi-1996/p/11095016.html