C语言 深入讲解条件编译的用处

目录
  • 一、基本概念
  • 二、条件编译的本质
  • 三、#include 的本质
  • 四、条件编译的意义
  • 五、小结

一、基本概念

  • 条件编译的行为类似于 C 语言中的 if...else...
  • 编译是预编译指示命令,用于控制是否编译某段代码

下面看一段简单的条件编译的代码:

#include <stdio.h>

#define C 1

int main()
{
    const char* s;

    #if( C == 1 )
        s = "This is first printf...\n";
    #else
        s = "This is second printf...\n";
    #endif

    printf("%s", s);

    return 0;
}

下面为输出结果:

可以输入gcc -E Test.c -o file.i 命令,看看预编译阶段发生了什么,下面是部分输出结果:

# 2 "Test.c" 2

int main()
{
    const char* s;

        s = "This is first printf...\n";

    printf("%s", s);

    return 0;
}

可以看到宏定义和条件编译都没有了,由相应内容取而代之。

二、条件编译的本质

预编译器根据条件编译指令有选择的删除代码

编译器不知道代码分支的存在

if...else... 语句在运行期进行分支判断

条件编译指令在预编译期进行分支判断

可以通过命令行定义宏

  • gcc -Dmacro=value file.c
  • gcc -Dmacro file.c

下面看一个通过命令行定义宏的代码:

#include <stdio.h>
int main()
{
    const char* s;

    #ifdef C
        s = "This is first printf...\n";
    #else
        s = "This is second printf...\n";
    #endif

    printf("%s", s);

    return 0;
}

终端输入gcc -DC Test.c,输出结果如下:

三、#include 的本质

  • #include 的本质是将已经存在的文件内容嵌入到当前文件中
  • #include 的间接包含同样会产生嵌入文件内容的操作

这就出现一个问题,间接包含同一个头文件是否会产生编译错误?

下面就来通过一段代码深入探究:

global.h:

// global.h
int global = 10;

test.h:

// test.h

#include "global.h"

const char* NAME = "test.h";

char* hello_world()
{
    return "hello world!\n";
}

test.c:

#include <stdio.h>
#include "test.h"
#include "global.h"
int main()
{
    const char* s = hello_world();
    int g = global;

    printf("%s\n", NAME);
    printf("%d\n", g);

    return 0;
}

编译后编译器报错,global 重定义:

为什么 global 会重定义呢?下面开始单步编译,输入gcc -E test.c -o test.i,输出部分结果如下:

# 2 "test.c" 2
# 1 "test.h" 1

# 1 "global.h" 1

int global = 10;
# 4 "test.h" 2

const char* NAME = "test.h";

char* hello_world()
{
    return "hello world!\n";
}
# 3 "test.c" 2
# 1 "global.h" 1

int global = 10;
# 4 "test.c" 2

int main()
{
    const char* s = hello_world();
    int g = global;

    printf("%s\n", NAME);
    printf("%d\n", g);

    return 0;
}

这样就很明显了,程序先将 test.h 里面的东西复制进 test.c,由于 test.h 里面有一个 include "global.h",就把int global = 10; 复制过来,然后复制

const char* NAME = "test.h";

char* hello_world()

{undefined

return "hello world!\n";

}

在然后由于test.c 里面又定义一个#include "global.h",又把int global = 10; 复制过来,造成了重复定义。

条件编译可以解决头文件重复包含的编译错误

#ifndef _HEADER_FILE_H_
#define _HEADER_FILE_H_
//source code
#endif

如果没有定义 header_file.h,则定义,且执行里面的代码;否则,如果定义了,里面的代码就不会执行。

所以上述代码中可以这么改:

global.h:

// global.h
#ifndef _GLOBAL_H_
#define _GLOBAL_H_
int global = 10;

#endif

test.h:

// test.h
#ifndef _TEST_H_
#define _TEST_H_
#include "global.h"
const char* NAME = "test.h";
char* hello_world()
{
    return "hello world!\n";
}
#endif

这样编译就能通过了

四、条件编译的意义

条件编译使得我们可以按不同的条件编译不同的代码段,因而可以产生不同的目标代码

#if...#else...#endif 被预编译器处理,而 if...else... 语句被编译器处理,必然被编译进目标代码

实际工程中条件编译主要用于以下两种情况:

  • 不同的产品线共用一份代码
  • 区分编译产品的调试版和发布版

下面看一段产品线区分及调试代码:

product.h:

#define DEBUG 1
#define HIGH  1

test.c:

#include <stdio.h>
#include "product.h"
#if DEBUG
    #define LOG(s) printf("[%s:%d] %s\n", __FILE__, __LINE__, s)
#else
    #define LOG(s) NULL
#endif
#if HIGH
void f()
{
    printf("This is the high level product!\n");
}
#else
void f()

{

}
#endif
int main()

{
    LOG("Enter main() ...");
    f();
    printf("1. Query Information.\n");
    printf("2. Record Information.\n");
    printf("3. Delete Information.\n");
    #if HIGH
    printf("4. High Level Query.\n");
    printf("5. Mannul Service.\n");
    printf("6. Exit.\n");
    #else
    printf("4. Exit.\n");
    #endif
    LOG("Exit main() ...");
    return 0;
}

宏 DEBUG 是指产品是调试版还是发布版,调试版为 1,发布版为 0, 宏 HIGH指的是产品是高端产品还是低端产品,高端产品为 1,低端产品为 0

如果我们想测试调试版的高端产品,令 DEBUG 为 1,HIGH为 0 即可:

同理,我们想测试发布版的低端产品,令 DEBUG 为 0,HIGH为 0 即可:

五、小结

  • 通过编译器命令行能够定义预处理器使用的宏
  • 条件编译可以避免重复包含头同一个头文件
  • 条件编译是在I程开发中可以区别不同产品线的代码
  • 条件编译可以定义产品的发布版和调试版

到此这篇关于C语言 深入讲解条件编译的用处的文章就介绍到这了,更多相关C语言 条件编译内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言 图文并茂详解程序编译过程

    目录 一.初识编译器 二.程序被编译的过程 三.小结 一.初识编译器 编译器是一个广义的概念,真正的编译器由下面几个模块组成,真正的编译器是进行语法分析和语义分析的. 二.程序被编译的过程 如下,file.i 是中间代码,file.s 是一个汇编文件,file.o 是二进制文件. 预编译 处理所有的注释,以空格代替 将所有的 #define 删除,并且展开所有的宏定义 处理条件编译指令 #if, #ifdef, #elif,#else,#endif 处理 #include,展开被包含的文件 保留

  • C语言 程序的编译系统解析

    目录 程序的翻译环境和执行环境 编译和链接 翻译环境 编译的几个阶段 预处理 编译 汇编 链接 运行环境 今天我来补一下C语言篇的程序的编译的一篇文章,也算是有一个结尾了. 程序的翻译环境和执行环境 在ANSI C的任何一种实现中,存在两个不同的环境 : 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令. 第2种是执行环境 ,它用于实际执行代码. 一个.c的文件事如何变成.exe的可执行文件的呢?下面这张图片是一个大概的过程: 编译和链接 翻译环境 组成一个程序的每个源文件通过编译过

  • C语言中条件编译详解

    通常情况,我们想让程序选择性地执行,多会使用分支语句,比如if-else 或者switch-case 等.但有些时候,可能在程序的运行过程中,某个分支根本不会执行. 比如我们要写一个跨平台项目,要求项目既能在Windows下运行,也能在Linux下运行.这个时候,如果我们使用if-else,如下: Windows 有专有的宏_WIN32,Linux 有专有的宏__linux__ if(_WIN32) printf("Windows下执行的代码\n"); else if(__linux_

  • 解析C语言与C++的编译模型

    首先简要介绍一下C的编译模型: 限于当时的硬件条件,C编译器不能够在内存里一次性地装载所有程序代码,而需要将代码分为多个源文件,并且分别编译.并且由于内存限制,编译器本身也不能太大,因此需要分为多个可执行文件,进行分阶段的编译.在早期一共包括7个可执行文件:cc(调用其它可执行文件),cpp(预处理器),c0(生成中间文件),c1(生成汇编文件),c2(优化,可选),as(汇编器,生成目标文件),ld(链接器). 1. 隐式函数声明 为了在减少内存使用的情况下实现分离编译,C语言还支持"隐式函数

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

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

  • C语言程序的编译与预处理详解

    目录 一.程序的编译 1. 编译阶段 2.链接 二.预处理详解 1.预定义符号 2.#define定义的标识符 3.#define定义的宏 4.#unef 总结 一.程序的编译 我们写的源文件(*.c)是经过怎样的处理生产可执行文件(*.exe)的呢?这种处理有两个步骤-编译和链接.源文件在编译阶段通过编译器将每个源文件转换为目标文件(这些文件是可执行的机器指令),再通过链接器将其捆绑到一起,生成一个完整的可执行程序. 1. 编译阶段 编译阶段可细分为3个阶段:预处理(即预编译).编译.汇编 预

  • 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语言 深入讲解条件编译的用处

    目录 一.基本概念 二.条件编译的本质 三.#include 的本质 四.条件编译的意义 五.小结 一.基本概念 条件编译的行为类似于 C 语言中的 if...else... 编译是预编译指示命令,用于控制是否编译某段代码 下面看一段简单的条件编译的代码: #include <stdio.h> #define C 1 int main() { const char* s; #if( C == 1 ) s = "This is first printf...\n"; #els

  • C语言 详细讲解#pragma的使用方法

    目录 一.#pragma 简介 二.#pragma message 三.#pragma once 四.#pragma pack 五.小结 一.#pragma 简介 #pragma 用于指示编译器完成一些特定的动作 #pragma 所定义的很多指示字是编译器特有的 #pragma 在不同的编译器间是不可移植的 预处理器将忽略它不认识的 #pragma 指令 不同的编译器可能以不同的方式解释同一条 #pragma 指令 一般用法: #pragma parameter 注:不同的 parameter

  • C语言详细讲解#error与#line如何使用

    目录 一.#error 的用法 二.#line 的用法 三.小结 一.#error 的用法 #error 用于生成一个编译错误消息 用法 #error message,message不需要用双引号包围 #error 编译指示字用于自定义程序员特有的编译错误消息,类似的,#warning 用于生成编译警告. #error 是一种预编译器指示字 #error 可用于提示编译条件是否满足 用法示例如下: 编译过程中的任意错误信息意味着无法生成最终的可执行程序. 下面初探一下 #error #inclu

  • C语言简明讲解预编译的使用

    目录 小复习 1.内置符号 2.自定义符号 3.自定义宏 4.条件编译 小复习 预处理,预编译是编译的第一步. 会有三件基本的事情发生: 引入#include 去除注释 修改#define 1.内置符号 这些符号都可以直接使用: __FILE__            点c文件全名__LINE__            当前行号__DATE__            编译日期__TIME__            编译时间 举例: #include<stdio.h> int main() {

  • Python调用R语言实例讲解

    网络上经常看到有人问数据分析是学习Python好还是R语言好,还有一些争论Python好还是R好的文章.每次看到这样的文章我都会想到李舰和肖凯的<数据科学中的R语言>,书中一直强调,工具不分好坏,重要的是解决问题的思路,就算是简单的excel,也能应付数据分析中的大部分问题.再者Python和R本来就没有什么好对比的,一门是计算机工程语言,一门是统计语言,只有将两者结合起来,才能发挥更大的威力,不是吗,对于数据分析的人来说,难道不是两样都要掌握的吗? rpy2是Python调用R程序的模块,旨

  • Java调用R语言实例讲解

    R是统计计算的强大工具,JAVA是做应用系统的主流语言.JAVA负责系统的构建,R用来做运算引擎,从而实现应用型和分析性相结合的系统. 一.Rserve(远程通信模式) Rserve是一个基于TCP/IP的服务器,通过二进制协议传输数据,可以提供远程连接,使得客户端语言能够调用R. Rserve作为一个package发布在CRAN上,可以直接使用install.packages("Rserve")进行安装.需要使用时在R控制台下加载该包,然后输入命令Rserve(),开启服务器就可以供

  • C语言 深入浅出讲解指针的使用

    目录 一.利用指针倒序字符串 二.题目实例 三.总结 一.利用指针倒序字符串 void _reversal(char* left, char* right) { while (left < right) { char tmp = *left; *left = *right; *right = tmp; left++; right--; } } 通过上述代码不难看出,left与right分别代表一个字符数组的首端和尾端,通过中间变量 tmp进行首尾交换,left++中的left是char*类型,同

  • C语言由浅入深讲解文件的操作上篇

    目录 为什么使用文件 什么是文件 文件名 关于文件的一些概念 文件函数 fopen fclose 实例代码 绝对路径 文件的打开方式 文件操作流程 为什么使用文件 前面写的通讯录,增加人数退出程序后,数据就会消失.此时数据是存放在内存中,下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受. 所以文件操作就应运而生.数据持久化的方法有两种:1.把数据存放在磁盘文件2.存放到数据库使用文件我们们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化. 什么是文件 但是在程序设计中

  • C语言由浅入深讲解文件的操作下篇

    目录 文件的顺序读写 字符输入输出fgetc和fputc 文本行输入输出函数fgets和fputs 格式化输入输出函数fscanf和fprintf 二进制输入输出函数fread和fwrite 文件的随机读写 fseek ftell fwind 文本文件和二进制文件 文件结束的判定 feof 文件缓冲区 第一篇讲了文件的基本概念,和文件如何打开和关闭.第二篇主要介绍文件的顺序读写和随机读写.外加文件缓冲区的知识点. 文件的顺序读写 字符输入输出fgetc和fputc fgetc:字符输入函数,也就

  • 适合初学者的C语言字符串讲解

    这一篇博客我们来了解一下字符串,看下面这个我们熟知的也是最先学习的代码 "Hello world!" 这一堆的字母就是字符串字面值,简称字符串,每一个字母都是一个字符,字符串需要用" "双引号来引起,字符需要用’ '单引号来引起,就像下面 "Hello world!"  //字符串'a'  //字符'!'  //字符 字符串也算常量,上面三条都算字面常量,之前的讲常量的时候就举过这种例子 那么字符串有什么用呢? 假设我们要把下面的字符串存起来,那

随机推荐