专栏首页CSDN搜“看,未来”讲通C/C++预编译/条件编译指令 #ifdef,#ifndef,#endif,#define,…

讲通C/C++预编译/条件编译指令 #ifdef,#ifndef,#endif,#define,…

文章目录
  • 开门见山
  • 预处理指令
    • #include
    • #define
      • 示例一
      • 示例二
      • 示例三
    • #undef
    • #program once
  • program
  • 条件编译指令
    • #ifdef、#ifndef、#endif
    • #if、#elif、#else、#endif
  • error
  • 自我测评

开门见山

本文主要介绍c语言中条件编译相关的预编译指令,常见的预处理指令如下:

    #include包含一个源代码文件
    #define定义宏
    #undef取消已定义的宏
    #if如果给定条件为真,则编译下面代码
    #ifdef如果宏已经定义,则编译下面代码
    #ifndef如果宏没有定义,则编译下面代码
    #elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
    #endif结束一个#if……#else条件编译块
    #error停止编译并显示错误信息

预处理指令

预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。

预处理指令是在编译器进行编译之前进行的操作.预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。

这些话在《编译原理》里面都能找到,我就不多哔哔了。

#include

这个大家都不陌生吧。

简单说一下,第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。

记得还有个外部库,本人比较喜欢玩动态库,所以这个稍微熟一点。

#define

这个也不陌生吧,定义宏用的。 有关#define这个宏定义,在C语言中使用的很多,因为#define存在一些不足,C++强调使用const来定义常量。宏定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。

示例一

#include <stdio.h>
#define MAX(x,y) (((x)>(y))?(x):(y))
#define MIN(x,y) (((x)<(y))?(x):(y))
int main(void)
{
#ifdef MAX    //判断这个宏是否被定义
    printf("3 and 5 the max is:%d\n",MAX(3,5));
#endif
#ifdef MIN
    printf("3 and 5 the min is:%d\n",MIN(3,5));
#endif
    return 0;
}

/*
 * (1)三元运算符要比if,else效率高
 * (2)宏的使用一定要细心,需要把参数小心的用括号括起来,
 * 因为宏只是简单的文本替换,不注意,容易引起歧义错误。
*/

示例二

#include <stdio.h>
#define SQR(x) (x*x)
int main(void)
{
    int b=3;
#ifdef SQR//只需要宏名就可以了,不需要参数,有参数的话会警告
    printf("a = %d\n",SQR(b+2));
#endif
    return 0;
}

/*
 *首先说明,这个宏的定义是错误的。并没有实现程序中的B+2的平方
 * 预处理的时候,替换成如下的结果:b+2*b+2
 * 正确的宏定义应该是:#define SQR(x) ((x)*(x))
 * 所以,尽量使用小括号,将参数括起来。
*/

示例三

#include <stdio.h>
#define STR(s) #s
#define CONS(a,b) (int)(a##e##b)
int main(void)
{
#ifdef STR
    printf(STR(VCK));
#endif
#ifdef CONS
    printf("\n%d\n",CONS(2,3));
#endif
    return 0;
}

/* 第一个宏,用#把参数转化为一个字符串
 * 第二个宏,用##把2个宏参数粘合在一起,及aeb,2e3也就是2000
*/

关于#define宏的使用,应该特别小心,尤其是含有参数计算的时候,最保险的做法将参数用括号括起来。


#undef

取消宏的定义,这个倒是没见过,是我孤陋寡闻了。

#program once

为了避免同一个文件被include多次,C/C++中有两种方式,一种是#ifndef方式,一种是#pragma once方式。在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别。

#ifndef的方式受C/C++语言标准支持。它不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。 当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,编译器却硬说找不到声明的状况——这种情况有时非常让人抓狂。 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。

#pragma once一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。 其好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。大型项目的编译速度也因此提高了一些。 对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。

program

#program是一个预处理指令,会把后面的值传给编译器。这个预处理指令是用于向编译器提供窗外信息的标准方法。 我记得之前用VS操作sqlite3的时候就用这个把sqlite3的头传进项目中。


条件编译指令

条件编译指令,顾名思义和预编译指令不同,它们决定了那些代码被编译,哪些代码不被编译。 不过我个人不认为这是动态的。

#ifdef、#ifndef、#endif

#ifdef用于判断某个宏是否定义,和#ifndef功能正好相反,二者仅支持判断单个宏是否已经定义 #endif用于终止#if预处理指令。

#ifdef ABC
// ... codes while definded ABC
#elif (CODE_VERSION > 2)
// ... codes while CODE_VERSION > 2
#else
// ... remained cases
#endif // #ifdef ABC 

如果不需要多条件预编译的话,上面例子中的#elif和#else均可以不写。

#if、#elif、#else、#endif

#if可支持同时判断多个宏的存在,与常量表达式配合使用。

#if 常量表达式1
// ... some codes
#elif 常量表达式2
// ... other codes
#elif 常量表达式3
// ...
...
#else
// ... statement
#endif

常量表达式可以是包含宏、算术运算、逻辑运算等等的合法C常量表达式,如果常量表达式为一个未定义的宏, 那么它的值被视为0。

在判断某个宏是否被定义时,应当避免使用#if,因为该宏的值可能就是被定义为0。而应当使用#ifdef或#ifndef。 注意: #if、#elif之后的宏只能是对象宏。如果宏未定义,或者该宏是函数宏,则编译器可能会有对应宏未定义的警告。

error

#error 用于抛出某个异常信息,并结束程序运行。


自我测评

这是redis的空间配置器中的一段代码,大家自己看一下能否心领神会:

#ifndef __ZMALLOC_H
#define __ZMALLOC_H

/* Double expansion needed for stringification of macro values. */
#define __xstr(s) __str(s)
#define __str(s) #s

#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif

#elif defined(USE_JEMALLOC)
#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
#include <jemalloc/jemalloc.h>
#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) je_malloc_usable_size(p)
#else
#error "Newer version of jemalloc required"
#endif

#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_size(p)
#endif

#ifndef ZMALLOC_LIB
#define ZMALLOC_LIB "libc"
#ifdef __GLIBC__
#include <malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_usable_size(p)
#endif
#endif

最后,本文为我“redis6.0.6源码学习”的开路先锋,大家如有兴趣,可以点个关注一起学习呀

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 我能看懂的MakeFile(自命名,多文件,多目标)

    好,我解释一下。 我们的目标文件(就是拿来运行的那个)其实就是通过文件之间的依赖关系,对源文件进行编译而得到的,但是这个依赖关系系统不知道,所以需要我们告诉系...

    看、未来
  • exec族

    这个一直没搞太明白,相识度高是一回事,不会用又是另一回事儿。 所以今天又温故了一遍:

    看、未来
  • 133: error: in C++98 XXX must be initialized by constructor, not by '{...}'

    C11相对与C98加了不少东西,比如容器的初始化等,如果用C98标准初始化容器,那真的是很爽的。

    看、未来
  • Java ClassLoader 透析

    ClassLoader 是 Java 届最为神秘的技术之一,无数人被它伤透了脑筋,摸不清门道究竟在哪里。网上的文章也是一篇又一篇,经过本人的亲自鉴定,绝大部分内...

    码脑
  • 命令模式

    首先来看命令模式(Command Pattern)的定义: 《Head First Design Pattern》中的定义是:The Command Pat...

    卡尔曼和玻尔兹曼谁曼
  • spring定时器注解的用法详解

    爱明依
  • To B大战,腾讯产业加速器持续扩容

    ? 来源 :腾讯产业加速器x36氪 ---- 2020年3月18日,腾讯交出了2019年的最后一份答卷——2019Q4财报与全年业绩报告。其中,To B业务...

    腾讯SaaS加速器
  • 老大难的 Java ClassLoader 再不理解就老了

    ClassLoader 是 Java 届最为神秘的技术之一,无数人被它伤透了脑筋,摸不清门道究竟在哪里。网上的文章也是一篇又一篇,经过本人的亲自鉴定,绝大部分内...

    芋道源码
  • 老大难的 Java ClassLoader,到了该彻底理解它的时候了

    ClassLoader 是 Java 届最为神秘的技术之一,无数人被它伤透了脑筋,摸不清门道究竟在哪里。网上的文章也是一篇又一篇,经过本人的亲自鉴定,绝大部分内...

    老钱
  • 音乐网站之技术总结

               以迅雷不及掩耳响叮当之势 ,biu~biu~! 搞完了一个老师的布置的结业课设!  下面总结一下遇到的阻力以及解决的策略:   No.1 ...

    Gxjun

扫码关注云+社区

领取腾讯云代金券