文档章节

预编译指令详解

pp__qq
 pp__qq
发布于 2013/12/26 00:46
字数 1550
阅读 178
收藏 12
点赞 0
评论 0

三字符序列

主要是因为以前键盘上没有某些字符,所以为了使用这些字符,必须使用多个可以打印字符来标识这个不能打印的字符,三字符的替换发生在所有操作之前;

??=
#
??(
[
??<
{
??/
\
??)
]
??>
}
??'
^
??!
|
??-
~

不过 现在编译器默认是关掉替换的,替换会造成很多有趣的事情,如:

int ci=1;
//接下来的??/
++ci;
printf("%d\n",ci);
/* ??/会被替换为'\';而'\'作为一个行连接符会让"++ci"被注释掉
 * 不过现在编译器默认是关掉替换的 */


宏展开的流程

  1. 首先根据','将实际参数分割成一个个记号,此时不会被实际参数进行扩展

#define cat(x,y)	x;y;
cat(int a;,int b;);/* 宏展开: int a;;int b;; */
/* 根据','分割记号 */
  1. 用实参产生的记号替换宏中未用引号引起来的相应形式参数的标识符

    • 如果标识符前面没有'#',或者前者或后面都没有'##'符号;则检查实际参数产生的记号,并在必要的时刻进行宏扩展

  2. 根据','将实际参数分成记号,并匹配宏定义中的形参,此时位于引号或者嵌套括号的逗号不会用于分割,如

/**
 *  根据 func 的返回值测试 func 是否成功执行,若出错,则抛出 std::runtime_error() 类型的异常,异常字符串为:文件名;行号;func;format..
 *  @param func 若 func 返回 int 类型,则当返回值不为0时,认为 func 出错;若 func 返回指针类型,则当返回值为 0 时,认为出错.
 */
#define TF(func,format,...)  testSystemCall(func,__FILE__,LINE_TO_STR(__LINE__),#func,format,##__VA_ARGS__)

TF(socket(AF_INET,SOCK_STREAM,0),"HELLOWORLD");
// 此时根据','进行分割,匹配情况为:
// func = socket(AF_INET,SOCK_STREAM,0)
// format = "HELLOWORLD"
// 所以此时替换后内容为:testSystemCall(socket(2,1,0),"main.cc","13","socket(AF_INET,SOCK_STREAM,0)","HELLOWORLD");
// 即 socket() 内的","不会用于分割.

#

如果在上述替换时发现形式参数标识符之前存在'#'符号,则

  • 首先在实际参数产生的记号两端添加"符号,使得记号变成一个字符串字面值

  • 然后再用该记号替换 '#'与其后的标志符

  • 实际参数中的字符串字面值,字符常量两边或内部的每个双引号( " )或反斜杠( \ )前面都会由预编译器自动插入一个反斜杠( \ )。


#define mStr(x)	 #x
printf("%s\n",mStr("3\3"));
/* 此时宏展开 "\"3\\3\"" */


##

在形式参数都被替换后都要把##及其前后的空白符都删除掉,以便将相邻记号连接起来形成一个新记号。

#define cat(x,y) x ## y
printf("%d\n",cat(1,2));


多次扫描

重复扫描替换记号序列以查找更多的己定义标识符。但是, 当某个标识符在某个扩展中被替换后,再次扫描并再次遇到此标识符时不再对其执行替换,而是保持不变 。

printf("%d\n",mtest(m,test)(3,3));
/* 第一次扫描:mtest(3,3);此时因为mtest()
 * 在上一层扫描中已经被展开了,所以此时不会再展开 */
printf("%d\n",mtest(m,test1)(3,3));
/* 第一次扫描:mtest1(3,3);然后
 * 下一次扫描再展开mtest1 */


include

#include 把该行(直接在当前行)替换为文件名指定的文件的内容 ,三种形式:

  • #include <文件>

  • #include "文件"

  • #include 宏调用

#define incFile(file) #file
#include incFile(___Dlove_HTTPSend.h);
/* 就是直接 #inlcude "___Dlove_HTTPSend.h" */

if elif else endif

#if 整数表达式
   文本 
#else
   文本
#endif
  • "文本" 是指任何不属于条件编译指令结构的程序代码,它可以包含预处理指令,也可以为空

  • 因为预编译器不进行计算,所以#if后只能用整数表达式(必须是整型,并且其中不包含sizeof,强制类型转换运算符或枚举常量);

defined ifdef 

defined 标志符 或者 defined(标志符);
    如果该标识符在预处理器中已经定义,则用 1 替换它,否则,用 0 替换。预处理器进行宏扩展之后仍然存在的任何标识符都将用0来替换
        加粗字体。。我不甚了解,大概就是因为在宏的多次扫描中已经扩展过的标志符不会在被扩展,差不多这个意思;

error

#error 描述
    将终止编译并打印指定的诊断信息。

可变参数宏

语法

通过在宏定义中使用"...",在宏拓展中使用"__VA_ARGS__"来实现具有可变参数的宏,如

#define errRep(func,...)   \
   fprintf(stderr,func,__VA_ARGS__);

当只需要一个参数的时候

#define err(format,...) fprintf(stderr,format,__VA_ARGS__)

int main(int argc,char *argv[]){
    err("HelloWord");
    err("Hello;%d;%f",33,3.3);
    return 0;
}
$ g++ -E main.cc
# 1 "main.cc"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "main.cc"
int main(int argc,char *argv[]){
    fprintf(stderr,"HelloWord",); # 当没有参数时,__VA_ARGS__ 相当于空,所以此时会多了一个逗号.
    fprintf(stderr,"Hello;%d",360);
    return 0;
}
  • 使用 ##__VA_ARGS__ 来消除,

#define err(format,...) fprintf(stderr,format,##__VA_ARGS__)
$ g++ -E main.cc 
# 1 "main.cc"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "main.cc"
int main(int argc,char *argv[]){
    fprintf(stderr,"HelloWord"); # 多余的逗号不见了
    fprintf(stderr,"Hello;%d",360);
    return 0;
}

标准宏

__LINE__ 当前行号
__FILE__ 当前源文件名
__DATE__ 当前编译日期
__TIME__ 当前编译时间
__STDC__ 当程序严格遵循STDC时为1
__cplusplus 当程序使用C++语言编写为1
#include<iostream>
using namespace std;

int main(){
	cout<<"源文件为: "<<__FILE__<<endl<<"编译于:"__DATE__<<" "<<__TIME__<<endl;
	cout<<"使用";
#ifdef  __cplusplus
	cout<<"C++";
#else
	cout<<"C"
#endif
	cout<<"编写 "<<endl;
#ifndef __STDC__
	cout<<"不";
#endif
	cout<<"遵循ANSC标准"<<endl;
	return 0;
}

do{}while(0)

1.一般用在宏定义中,如:

/*
 * 功能:往stderr流中写入出错信息,以退出程序
 * 参数:与 fprintf() 参数一致
 */
#define mErrRep(errfuc,...)        \
    do{    \
        fprintf(stderr,errfuc,##__VA_ARGS__);    \
        exit(1);        \
    }while(0);

此时 mErrRep() 宏有两条语句,并且经常出现在 if() 后面;此时使用 do{}while(0) 可以很安全的这样:

if((dirs=opendir("/root")) == NULL)
   mErrRep("main:opendir: %s\n",strerror(errno));

/* 会被替换为: */ 
 if(dirs=opendir("/root") ==NULL )
     do{
          fprintf(stderr,"main:opendir: %s\n",strerror(errno));
          exit(1);
      }while(0)

如果不使用 do{}while(0) 的话在每一个if()后面都应该加上一个{}以括起宏定义中可能含有的多条语句

© 著作权归作者所有

共有 人打赏支持
pp__qq
粉丝 17
博文 51
码字总数 97223
作品 0
合肥
程序员
C语言的编译链接过程详解

学过C语言的人都应该知道,我们所编辑的C语言程序是不能直接放到机器上运行的,它只不过是一个带".c"后缀的文件(也称为源代码)而已,需要经过一定的处理才能转换成机器上可运行的可执行文件...

LIU-X1001 ⋅ 2016/06/30 ⋅ 0

pragma 预处理指令详解

pragma指令简介 在编写程序的时候,我们经常要用到#pragma指令来设定编译器的状态或者是指示编译器完成一些特定的动作. 下面介绍了一下该指令的一些常用参数,希望对大家有所帮助! 一. message...

flyhighly ⋅ 2010/12/23 ⋅ 0

深入理解C语言的预编译指令之 include

写过C语言的朋友都熟悉#include,在打印“hello world”这样一条语句也用上这条指令。但是,说熟悉它,只是表面熟悉,更多感觉是既熟悉又抽象陌生,结果也就只是不知道为什么的背诵了。抽象、...

ningcaichen66 ⋅ 2017/09/22 ⋅ 0

预处理命令详解

预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。 在C语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其...

AlphaJay ⋅ 2011/07/27 ⋅ 0

C语言编译过程总结简版

C语言编译过程总结详解 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形...

AlphaJay ⋅ 2010/05/21 ⋅ 1

C/C++ 编译连接过程

所谓编译,就是把文本形式的源代码(.c和.cpp)翻译为机器语言形式的目标文件(一般为obj文件)。 所谓链接,就是把目标文件和用到的库文件组织成计算机可执行代码的过程。 从上面的叙述我们...

_编程菜鸟_ ⋅ 2014/08/25 ⋅ 0

fatal error C1010: unexpected end of file while...

在编译VC++6.0是,出现fatal error C1010: unexpected end of file while looking for precompiled header directive 的错误. 解决方法: 1、如果发生错误的文件是由其他的C代码文件添加进入当...

Mr&Cheng ⋅ 2013/01/20 ⋅ 0

关于预编译和宏定义部分说明

From: http://www.neu.edu.cn/cxsj/pointchart/c11/index.html #pragma指令 TAG:预编译和宏定义,ANSI C,# pragma TEXT: 其语法格式如下: # pragma token-sequence 此指令的作用是触发所定义的......

AlphaJay ⋅ 2010/04/19 ⋅ 0

C++中头文件、源文件之间的区别与联系

.h头文件和.cpp文件的区别 疑惑1:.h文件能够编写main函数吗? 实验: 编写test.h文件,里面包含main函数 若直接编译g++ test.h -o test,通过file命令 file test,得到如下结果test: GCC p...

风筝Fergus ⋅ 2013/04/17 ⋅ 2

iOS代码运行的磨刀石-预编译指令

iOS中代码运行的磨刀石--预编译指令 所谓预编译,就是程序代码在编译之前,开发工具为我们预先做的一些工作。不要小瞧这些指令,没有它们,我们的代码可能寸步难行。 一、文件包含相关预处理...

珲少 ⋅ 2015/04/28 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring Bean基础

1、Bean之间引用 <!--如果Bean配置在同一个XML文件中,使用local引用--><ref bean="someBean"/><!--如果Bean配置在不同的XML文件中,使用ref引用--><ref local="someBean"/> 其实两种......

霍淇滨 ⋅ 20分钟前 ⋅ 0

05、基于Consul+Upsync+Nginx实现动态负载均衡

1、Consul环境搭建 下载consul_0.7.5_linux_amd64.zip到/usr/local/src目录 cd /usr/local/srcwget https://releases.hashicorp.com/consul/0.7.5/consul_0.7.5_linux_amd64.zip 解压consu......

北岩 ⋅ 23分钟前 ⋅ 0

Webpack 4 api 了解与使用

webpack 最近升级到了 v4.5+版 01 官方不再支持 node4 以下版本 官方不再支持 node4 以下版本官方不再支持 node4 以下的版本,所以如果你的node版本太低,先开始升级node吧!话说node10 ...

NDweb ⋅ 32分钟前 ⋅ 0

使用nodeJs安装Vue-cli

Vue脚手架就是一个Vue框架开发环境 脚手架的意思是帮你快速开始一个vue的项目,也就是给你一套vue的结构,包含基础的依赖库,只需要 npm install就可以安装,让我们不需要为了编辑或者一些其...

木筏笔歆 ⋅ 今天 ⋅ 0

【微信小程序开发实战】0x00.开发前准备工作

写在开始 本人资深后端码农一枚,近期项目需求,接触到了微信小程序,将学习过程整理成文分享给小伙伴们,由于是边学边整理难免有表述不对的地方,望大家及时指正,感谢。 本人微信号: dream...

dreamans ⋅ 今天 ⋅ 0

linux redis的安装和php7下安装redis扩展

安装redis服务器 (1)下载安装包: $ wget http://download.redis.io/releases/redis-2.8.17.tar.gz (2)编译程序: $ tar xzf redis-2.8.17.tar.gz $ cd redis-2.8.17 $ make $ cd src &&......

concat ⋅ 今天 ⋅ 0

Guava EventBus源码解析

一、EventBus使用场景示例 Guava EventBus是事件发布/订阅框架,采用观察者模式,通过解耦发布者和订阅者简化事件(消息)的传递。这有点像简化版的MQ,除去了Broker,由EventBus托管了订阅&...

SaintTinyBoy ⋅ 今天 ⋅ 0

http怎么做自动跳转https

Apache 版本 如果需要整站跳转,则在网站的配置文件的<Directory>标签内,键入以下内容: RewriteEngine on RewriteCond %{SERVER_PORT} !^443$ RewriteRule ^(.*)?$ https://%{SERVER_NAME......

Helios51 ⋅ 今天 ⋅ 0

Python爬虫,抓取淘宝商品评论内容

作为一个资深吃货,网购各种零食是很频繁的,但是能否在浩瀚的商品库中找到合适的东西,就只能参考评论了!今天给大家分享用python做个抓取淘宝商品评论的小爬虫! 思路 我们就拿“德州扒鸡”...

python玩家 ⋅ 今天 ⋅ 0

MySQL 内核深度优化

MYSQL数据库适用场景广泛,相较于Oracle、DB2性价比更高,Web网站、日志系统、数据仓库等场景都有MYSQL用武之地,但是也存在对于事务性支持不太好(MySQL 5.5版本开始默认引擎才是InnoDB事务...

java高级架构牛人 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部