百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

预处理的底层原理和预处理编译运行异常的解决方案

liuian 2025-10-02 03:20 7 浏览

若文章对您有帮助,欢迎关注 程序员小迷 。助您在编程路上越走越好!




[Mac-10.7.1 Lion Intel-based]


Q: 预处理到底干了什么事情?


A: 预处理,顾名思义,预先做的处理。源代码中的头文件包含,宏以及条件编译的东西都会在预处理的时候搞定。换句话说,以#开头的语句即为预处理。但是,如果#被包含在引号里面,那就只是单纯的字符或者字符串了。


Q: 怎么证明预处理的存在?


A: 如下代码,保存为macro.c:

#include <stdio.h>


#define NUM 100


int main()
{
    printf("%d\n", NUM);
    return 0;
}

使用gcc -E macro.c -o macro.i得到预处理的结果(因为篇幅问题,只截取最后数行代码):


# 500 "/usr/include/stdio.h" 2 3 4
# 2 "macro.c" 2






int main()
{
 printf("%d\n", 100);
 return 0;
}


可以看到,源代码中的NUM已经被替换成了宏定义的100.

事实上,预处理中的宏同样可以在命令行中指定,如下代码,保存为macro.c:

#include <stdio.h>


int main()
{
    printf("%d\n", NUM);
    return 0;
}


可以看到代码中的NUM没有被定义,然后使用编译命令加上对NUM的宏定义:


gcc -DNUM=100 macro.c -o macro


编译结束,没有问题,运行亦ok.


同理,对于头文件包含以及条件编译都可以通过预处理命令得到处理之后的代码形式,这样会更好地理解预处理的含义。调试宏也不是一个简单的事情,如果很难确定某个宏到底有没有作用或者宏对应的字符串到底是什么的时候,使用预处理命令得到结果是很好的方式。


Q: 有时看到一个字符串前面带有一个#符号,它表示什么含义?


形如:

#define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));


A:它表示将对应字符组合转换成相应的字符串格式。

使用如下代码,保存为macro.c:

#include <stdio.h>


#define PRINT_CH(ch)    printf(#ch" is %c\n", (ch));


int main()
{
    PRINT_CH('a')
    return 0;
}


使用gcc -E -o macro.i macro.c编译命令得到预处理后的文件macro.i.


使用如下命令得到预处理文件的最后10行:



可以看到PRINT_CH('a')宏被处理成: printf("'a'"" is %c\n", ('a'));

#ch的作用就是将它变成"ch"的模样。两个字符串字面量放一起相当于字符串拼接的作用。编译和运行结果:


Q: 除了上面的#符号,还会看到##符号,它又是什么含义?

A: 它是代表参数连接,连接过程很单纯,让你看不到一点改变。举个例子:

#include <stdio.h>
#define CATENATE(a, b)  a##b


int main()
{
    int ab = 1;
    printf("%d\n", CATENATE(a, b));
    return 0;
}


保存为preprocess.c,预处理后的结果是(仅截取最后数行代码):

# 2 "preprocess.c" 2




int main()
{
 int ab = 1;
 printf("%d\n", ab);
 return 0;
}


编译运行:


可以发现, a##b得到的结果就是ab这个符号。


Q: 宏定义也是可以续行的,为什么有的时候用的反斜杠来续行,最后编译依然有错?


A: 这有可能续行符被用在了字符串字面量里面,这个不允许的;或者续行符后面跟着非换行字符导致的。续行的含义是将随后紧随的换行字符干掉,当成没发生,如果不是换行字符就可能导致错误。不过gcc 4.2对这个要求也不是很严格了,如下代码,保存为preprocess.c:

#include <stdio.h>
#define ADD(a, b)   \ 
    ((a) + (b))


int main()
{
    int ret = ADD(1, 2);
    printf("%d\n", ret);


    return 0;
}


其中ADD宏行末的续行符后面有一个空格,在有的编译器下会编译错误。


为了确认续行符后面是否有空格,使用cat -e preprocess.c命令得到行末信息:


可以看到第二行的续行符后面有个空格。编译它,没有什么问题。





Q: 看到printf函数是可变参数的,如果用宏可以定义吗?

A: 是的。__VA_ARGS__便是可变参数的代表。如下代码:

#include <stdio.h>


#define printf_ex(...)  printf(__VA_ARGS__)


int main (int argc, const char * argv[])
{
    printf_ex("hello%d\n", 1);
    return 0;
}


上面的代码宏定义了printf_ex函数,参数为可变参数,它的实现即为printf函数,__VA_ARGS__即为printf_ex的可变参数。


编译运行:


Q: 既然可以宏定义,那么重复的宏定义的结果是什么呢?

A: 这样的话,一般预处理器会按照后者定义的为准,不过一般的预处理器也会发出警告。

如下代码,保存为redefinition.c:

#include <stdio.h>


#define A   10
#define A   11


#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));


int main (int argc, const char * argv[])
{
    PRINT_D(A)
    return 0;
}


编译:


可以发现出现了A重复宏定义的警告。

运行:




Q: 有的时候发现需要打印当前运行的位置,比如文件名,行数以及运行所在的函数,这该怎么办?

A: 可以使用__FILE__, __LINE__和__func__这些预定义的符号来处理。这里以__func__的使用为例子:

#include <stdio.h>


int add(int a, int b)
{
    printf("func %s execute begin...", __func__);
    return a + b;
}


int main (int argc, const char * argv[])
{
    add(1, 2);
    return 0;
}


编译运行:


可以看到add函数里面的__func__被替换成了add.







微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。

我是 程序员小迷 (致力于C、C++、C#、Android、iOS、Java、Kotlin、Objective-C、Swift、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

若文章对您有帮助,欢迎关注 程序员小迷 。助您在编程路上越走越好!

相关推荐

教你把多个视频合并成一个视频的方法

一.情况介绍当你有一个m3u8文件和一个目录,目录中有连续的视频片段,这些片段可以连成一段完整的视频。m3u8文件打开后像这样:m3u8文件,可以理解为播放列表,里面是播放视频片段的顺序。视频片段像这...

零代码编程:用kimichat合并一个文件夹下的多个文件

一个文件夹里面有很多个srt字幕文件,如何借助kimichat来自动批量合并呢?在kimichat对话框中输入提示词:你是一个Python编程专家,完成如下的编程任务:这个文件夹:D:\downloa...

Java APT_java APT 生成代码

JavaAPT(AnnotationProcessingTool)是一种在Java编译阶段处理注解的工具。APT会在编译阶段扫描源代码中的注解,并根据这些注解生成代码、资源文件或其他输出,...

Unit Runtime:一键运行 AI 生成的代码,或许将成为你的复制 + 粘贴神器

在我们构建了UnitMesh架构之后,以及对应的demo之后,便着手于实现UnitMesh架构。于是,我们就继续开始UnitRuntime,以用于直接运行AI生成的代码。PS:...

挣脱臃肿的枷锁:为什么说Vert.x是Java开发者手中的一柄利剑?

如果你是一名Java开发者,那么你的职业生涯几乎无法避开Spring。它如同一位德高望重的老国王,统治着企业级应用开发的大片疆土。SpringBoot的约定大于配置、SpringCloud的微服务...

五年后,谷歌还在全力以赴发展 Kotlin

作者|FredericLardinois译者|Sambodhi策划|Tina自2017年谷歌I/O全球开发者大会上,谷歌首次宣布将Kotlin(JetBrains开发的Ja...

kotlin和java开发哪个好,优缺点对比

Kotlin和Java都是常见的编程语言,它们有各自的优缺点。Kotlin的优点:简洁:Kotlin程序相对于Java程序更简洁,可以减少代码量。安全:Kotlin在类型系统和空值安全...

移动端架构模式全景解析:从MVC到MVVM,如何选择最佳设计方案?

掌握不同架构模式的精髓,是构建可维护、可测试且高效移动应用的关键。在移动应用开发中,选择合适的软件架构模式对项目的可维护性、可测试性和团队协作效率至关重要。随着应用复杂度的增加,一个良好的架构能够帮助...

颜值非常高的XShell替代工具Termora,不一样的使用体验!

Termora是一款面向开发者和运维人员的跨平台SSH终端与文件管理工具,支持Windows、macOS及Linux系统,通过一体化界面简化远程服务器管理流程。其核心定位是解决多平台环境下远程连接、文...

预处理的底层原理和预处理编译运行异常的解决方案

若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好![Mac-10.7.1LionIntel-based]Q:预处理到底干了什么事情?A:预处理,顾名思义,预先做的处理。源代码中...

为“架构”再建个模:如何用代码描述软件架构?

在架构治理平台ArchGuard中,为了实现对架构的治理,我们需要代码+模型描述所要处理的内容和数据。所以,在ArchGuard中,我们有了代码的模型、依赖的模型、变更的模型等,剩下的两个...

深度解析:Google Gemma 3n —— 移动优先的轻量多模态大模型

2025年6月,Google正式发布了Gemma3n,这是一款能够在2GB内存环境下运行的轻量级多模态大模型。它延续了Gemma家族的开源基因,同时在架构设计上大幅优化,目标是让...

比分网开发技术栈与功能详解_比分网有哪些

一、核心功能模块一个基本的比分网通常包含以下模块:首页/总览实时比分看板:滚动展示所有正在进行的比赛,包含比分、比赛时间、红黄牌等关键信息。热门赛事/焦点战:突出显示重要的、关注度高的比赛。赛事导航...

设计模式之-生成器_一键生成设计

一、【概念定义】——“分步构建复杂对象,隐藏创建细节”生成器模式(BuilderPattern):一种“分步构建型”创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建...

构建第一个 Kotlin Android 应用_kotlin简介

第一步:安装AndroidStudio(推荐IDE)AndroidStudio是官方推荐的Android开发集成开发环境(IDE),内置对Kotlin的完整支持。1.下载And...