C/C++相关概念和易错知识点(8)(预处理、编译、链接)

Linux 下 C/C++ 程序编译的过程 本文将介绍如何将 C/C++ 语言编写的程序转换成为处理器能够执行的二进制代码的过程,包括四个步骤:预处理()编译()汇编()链接(在此之前,首先来看一下 GCC 工具链。 阅读详情

前言:这篇文章将从程序运行的顺序讲解预处理、编译、链接相关的知识点。重要分享预处理相关的知识和感想。后面可能会针对这三个知识点进一步分享

生成可执行程序全过程

.c文件在翻译环境中处理生成可执行程序。电脑是不能看懂我们写的代码的,所以需要翻译环境来将我们写的代码转换为可执行的程序,可以将翻译环境理解为我们日常生活中使用的翻译软件。在C语言相关概念和易错语法(1)中我就讲过我们写的代码转化为可执行程序之间需要经历的过程

简单从图中进行一些解释:

(1)在Linux环境下编译生成的是.o文件,在Windows环境下编译生成的是.obj文件。.o(.obj)文件已经是二进制文件了。同样,.exe文件也是二进制文件

(2)编译过程包括:预编译、编译、汇编,具体操作后面会着重讲解

(3)链接库里面存储的是与程序运行相关的函数,它保证我们可以正常生成.exe文件并能正常运行。

正文

编译过程:预编译、编译、汇编

一、预编译(预处理):

1.预处理进行的操作:预处理将所有(除#pragma)含#的指令、注释全部清除,包括#include,#define,#if等。具体处理方法是删去没必要的(如#if判断为假的代码块和注释),展开引用的头文件,生成.i文件,.i文件的内容还是C语言,我们依然能够看懂。

2.预处理相关的知识:

(1)展开头文件有两种表达方式:第一种是<xxx.h>直接到标准库的目录里去查找相关的文件,找到了就拷贝一份到该文件,找不到就报错;第二种是"xxx.h",先在我们所写的.c文件的同级目录下找是否有相应的头文件,如果没有,再到标准库里去找,再没有就报错。

我们由此可以知道为什么自己写的头文件要用"xxx.h"来引用,本质上是两种书写方式导致的查找方法不一样。但是,要注意尽量引用标准库的函数时包含的头文件用<>形式书写,这样可以有效提高代码运行速度,且易于区分。

(2)条件编译:条件编译和条件表达式相似,具体有如下几种

①分支表达:

②定义判断:

注意事项:注意使用以上编译都要以#endif结尾保证结构完整。

注意条件编译(所有预处理指令)的执行先于main内部程序,所以要避免以下的错误:

因为#if后面的常量表达式为0(假)的话,#if与#endif之间的代码会在预编译期间被直接删除掉,在这里我们可以看到,#if后面的常量表达式在预编译阶段执行时,a还没有被赋值为10,所以为假,表达式结果是0,和下面效果一样:

③相关应用

以offset为例子,offset用于计算结构体的偏移量,offset其实是个宏定义,里面就有用到条件编译:

通过这里我们也可知道,在C和C++里面的offset并不完全相同。

(3)#pragma的两个常用之处

①  #pragma pack( num )

用于设置默认对齐数,#pragma pack( 1 )相当于没有对齐数这一概念了。

在一个程序里,经过实验,如果两个pragma pack间没有多余的代码(不含预处理指令),以后一个pragma pack为准;如果有多余代码,以第一个pragma pack为准。

针对上面代码举例子:


#include <stddef.h>

#pragma pack()

#include <stdio.h>

#pragma pack(1)


struct Stu
{

	char name[15];

	short age;

	float score;

}s1;


int main()
{
    printf("name偏移量是%d\nage偏移量是%d\nscore偏移量是%d\n", (int)offsetof(struct Stu, name), (int)offsetof(struct Stu, age), (int)offsetof(struct Stu, score));

	return 0;
}

结果是:

②  #pragma once

在打开一个.h文件时,开头的第一句话就是这个,意思是防止头文件重复引用,这样在预处理结束后的代码过于冗杂,甚至出现错误

它相当于以下的代码:本质上都是防止重定义或重引用

顺便一提#undef是删除宏替换的意思而不是“不定义”的意思,如果要用到“不定义”的功能,需要在#if用否定方式描述,再用#define来操作

试着解释一下以下代码:

注意事项:#pragma有的指令是针对编译器的,比如使用#pragma pack(1)来控制默认对齐数,使得结构体,共用体实质上取消对齐,这影响到分配内存的规则,所以这种含#的指令在预编译阶段是不会删除的。

(4)宏替换和宏定义

宏替换:

①需要注意的一点就是宏替换会直接把你设置的内容置换,而中间不会做任何处理,因此当看到#define MAX 1+1时,不能把它想象成#define MAX 2。如果你想要达到这个效果,要勤加括号,最好在替换的内容的最外层都加一个括号,#define MAX (1+1),这样能最大程度避免出现BUG。

②由于它的直接替换的特征,有下面一个非常易错的代码,同时也涉及指针的特点:

先看一下下面的代码:



#define INT int*

typedef int* INT_PTR;

int main()
{

	int* a, b;

	INT c, d;

	INT_PTR e, f;

	return 0;
}

其中很多人会误以为6个变量都是int*,其实不然。

只有a,c,e,f是int*,这个细节在于对指针表达的理解。在指针的章节我就分享过:*在不同的地方应该有不同的理解,有的时候int*要合在一起理解,有的时候要拆开随着变量作为一个整体去理解。这里拆开理解更好:

int    *a    ,    b

相信这样划分就能很好的理解为什么b是int而不是int*,因为只有一个*,被a占用了,所以b只是个int。宏替换的情况也是如此。那么如果我想要让b也是int*,则怎么处理呢?

这就要利用上面的e和f的处理方式了。INT_PTR被用于整个替换int*,相当于创建了两个INT_PTR的变量e和f,因此两个都是int*。

在这里,我们应该进一步了解到*(指针的符号)在C语言中的特殊性了,很多地方比较反直觉,需要在不同的地方对它进行不同的解读。

另外,不要将*作为宏替换末尾的符号,因为这往往达不到你想要的效果

*会被自动拆开作为INT替换的那部分

这里可以看到它被替换为* int*而不是int*

宏定义:

①两种书写符号#和##

#写在前面,相当于我们传什么,就把什么原样转换成字符串


##写在后面,举个例子:在我们传了type后,当type和其它字母(符号)相连时,type一般不会被替换,当在type后加上##时,它会和后面的字符连在一起并且会被我们传的type替换:

反例:

(5)C语言中还有一些预定义符号,如__DATE__,__TIME__,特征是中间的符号都是大写,左右分别由__划分,这类符号很多,要学会识别

二、编译,汇编,链接

1.编译

编译生成的是.s文件,它主要是将我们写的代码进行进一步检查(词法,语法,语义等分析),词法语法问题会在这个阶段被发现。同时,编译会将我们写的代码转化成汇编代码文件,这种文件的内容我们已经看不懂了。

2.汇编

生成.o(.obj)文件。这个阶段将汇编代码文件转换成了二进制文件,每一条汇编语句对应一条机器指令,不过这个时候生成的文件不止一个,需要经过链接才能最终生成可执行程序。

3.链接

它将汇编生成的文件结合链接库生成.exe文件,期间我们所写的函数会在这一步被定位。因此,如果没有定义函数,在这里会被找到。

预处理,编译,汇编,链接 目录 前言 1.预处理: 宏替换: 头文件展开: 条件编译: 2.编译: 3.汇编: 4.链接: 前言 一个c/c++文件想要变成可执行文件要经过预处理生成预处理文件(.i文件),编译阶段对预处理文件进行编译生成汇编文件(.s文件),汇编阶段对汇编文件进行处理生成目标文件(.o文件),最后链接目标文件生成可执行程序。下面我将介绍在各个阶段对程序进行了哪些处理。 1.预处理: c/c++源文件中,以#开头的命令被称为预处理命令,如#include、#define、条件编译命令#if 阅读详情

相关推荐

如何得到编译器预处理搜索到的头文件位置?如何得到编译器预处理头文件搜索路径?/usr/bin/cpp是C++相关的程序吗?预处理?gcc 1.c和g++ 1.c预处理有什么区别?

gcc可以通过-MD命令获取编译的头文件依赖关系。例如gcc -MD demo.c可以得到类似如下:......

编程语言开发、框架原理、编程思想、最佳实践技巧经验分享。致力于各种编程技术的技巧经验干货分享,讲解开发避坑技巧,追踪开发前沿技术,实战展开解决难题。 程序员小迷助您在编程路上越走越好! 970

【保姆级讲解】C语言---预编译处理精华

预编译处理 1.1预编译处理概述 预编译处理也称为编译预处理,是在程序正式编译之前需要进行的前期编译处理阶段。主要作用是向编译器传递信息。以井号(“#”)开头的命令都是编译预处理命令,并且编译预处理命令后面没有分号(“;”)。 预处理:头文件 #include <stdio.h> 预处理阶段进行的(完成替换) 后面没有; 预处理阶段相关命令:GNU套件中的gcc编译器 参数-E 生成文件后缀为xxx.i 未包含头文件的预处理与源文件的比较: 编译预处理命令格式:gcc...

weixin_51177279的博客 1837

C/C++的编译和链接过程

cl.exe和link.exe分别是visual studio 中的编译器和链接器 配置属性中的【c/c++】(设置编译的一些选项) 和 【链接器】选项页中,最后的那个命令行汇总了所有生效的设置,就是最终执行的命令行 配置属性中的VC++ 目录用于设置各个路径,相当于设置环境变量PATH(搜可执行文件的路径): INCLUDE(搜include中文件的路径) LIBPATH(搜使用usin...

u012138730的专栏 8883

浅谈Visual C++中的预编译

所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。一、预编译头文件说明所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工

serg_的专栏 1104

【C/C++面试必备 | 基础概念】GCC 和 GDB调试 总结

一、GCC : gcc和g++是c/c++的编译器。 格式: gcc [options] file…… 主要options: 选项 含义 -v 查看gcc编译器的版本,显示gcc执行时的详细过程; -o 指定输出文件名为file,这个名称不能跟源文件名同名; ...

Muti-Agent 2万+

超详细YOLOv8目标检测全程概述:环境、训练、验证与预测详解

这篇博文提供了一个关于使用YOLOv8进行目标检测的全面指南,包括环境搭建、模型训练、验证和预测的详细步骤,以及如何解释训练过程中生成的各种图表和数据。整篇文章为读者提供了一个关于如何利用YOLOv8进行目标检测的实践指南,从环境搭建到模型部署,再到结果分析,每一部分都给出了详细的步骤和解释。此外,还有对于模型输出的解释,帮助读者更好地理解和使用YOLOv8模型。

CDBmax的博客 10万+

第七章、编译预处理

C语言编译预处理也是一个重点,要理解牢记

一眼万年 896

RTDETR最强结构图 | 包含模型整体结构图 | 全模块展开图 | AiFi模块展开图

RT-DETR 最全结构图

Coding... 9597

预处理/预编译详解(C/C++)

在上一篇的bolg中的编译与链接中提到过预处理,但只是较为简单的讲解,本篇将会对预处理进行详细的讲解。 其中在预处理中很重要的一个一个知识点是#define定义常量与宏,还区分了宏与函数的区别,以及#和##符号,还涉及条件编译头文件的包含等等。

m0_74830524的博客 2213

FreeRTOS全攻略:从入门到精通

本文聚焦 FreeRTOS,从基础概念出发,阐述其开源、轻量级等优势。着重介绍 FreeRTOS 入门知识,涵盖内存管理、任务操作、同步互斥机制等核心内容。其中,空闲任务与钩子函数保障系统基础运行与功能扩展;队列、信号量、互斥锁和事件组等机制,分别从数据传递、资源访问控制、优先级问题解决及多任务同步等方面,为开发者提供高效的任务间协作与资源管理方案,助力开发者掌握 FreeRTOS 核心技术,为嵌入式项目开发筑牢基础。

邓邓子的博客 6357

一文讲透 SAP SD:把报价、订单、交付、开票串成一条可落地的业务闭环

摘要:SAP SD(销售与分销)模块是企业O2C(订单到收款)流程的核心,管理从客户意向到回款的完整链路。该模块围绕销售订单、交付、开票等关键环节,与财务、物料管理等模块深度集成。SD的核心在于主数据管理、单据驱动流程、定价规则引擎和信用风控,通过条件技术实现复杂定价策略,并支持多维度分析。典型应用场景包括多渠道销售企业的价格体系管理、信用控制和物流协同,有效解决销售与交付脱节、财务对账困难等业务痛点。(149字)

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 512

预编译和预处理以及编译选项的控制

总是对这三个不是很清晰,今天回来后认真的总结了下,记在博客上。一 预编译:为了增加编译速度往往要提前对一些头文件及代码进行编译,然后给后面正式编译时使用,以节省开销。这些文件代码基本上不会更改,比如MFC的一些头文件以及一些必要的API使用代码,当然,你也可以把你自己的一部分代码封装起来到一个C或C++文件中,(比如在其中包含一些头文件或必要的代码什么的,然后在VC-C/C++--PreC

1080

【Dify系列教程重置精品版】第二章:在Windows上部署Dify

让我们的电脑允许使用Linux系统。有点兴奋是怎么回事?简单来说就是让你的电脑拥有另一个叫做Linux的系统。3.选择“适用于Linux的Windows子系统”与 “虚拟机平台”与“Hyper-V"第1章:安装 wsl (Windows Subsystem for Linux)上一章我们聊了什么是Dify,这一章我们讲一下怎么才能用Dify。好了,我们的环境部署好了,下一章我们正式进入Dify。下载完成后,我们直接一直无脑下一步,安装即可。完成上述步骤,我们就可以开始安装wsl了。我们的虚拟化也是开启的。

xiezhipu的博客 866

C/C++语言预处理,编译,汇编,链接的详细过程讲解

一、问题背景 最近博主在复习各类编程语言知识时,在类比Java解释型高级语言的运行机制时,第一时间想到了C/C++的运行机制,于是博主我查阅相关资料后对C/C++的运行机制做一个详细但易懂的总结归纳。 二、C/C++的运行机制 程序要运行起来,必须要经过四个步骤:预处理、编译、汇编和链接。 预处理编译汇编链接hello.c, hello.hhello.ihello.shello.o或hello.o...

快乐李同学的博客 6215

AI编程工具到底是初学者的绊脚石还是垫脚石?

现在是AI时代,我们要做的,是学会拥抱AI、使用AI,成为AI的驾驭者,如果你因为害怕使用AI学不到东西,那么大可不必,因为AI必然是今后的趋势,相信会用AI在今后的求职面试中也会有很大的占比。另外我之前写了多篇有关AI编程开发工具Trae的使用技巧和教程,需要的话你可以关注公众号后通过菜单栏获取。另外我也把所有Trae相关的文章整合到了腾讯ima知识库,感兴趣的可以关注公众号以后回复。

cpp加油站的专栏 184

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

C/C++相关概念和易错知识点(58)(static修饰变量和函数)

在.h声明Fun()函数的地方使用static,如果有n个.c都包含了这个.h,那就告诉我们有n个完全独立的Fun()存在。在类中定义的static函数和static变量类似,它相当于类的公共区域,这个函数的特殊之处在于它的第一个参数没有this指针,这也就意味着。注意区分“包含它的文件”和“它包含的文件”,上面我举例的.h中无法调用static函数就是“它包含的文件”这种情况。我们可以在一个.c定义函数,然后在.h中声明这个函数,如果有很多.c都包含了这个.h,那就相当于告诉它们在。

SGlow0708的博客 2376

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

C/C++ 编译、链接和运行

文件中的符号和调试信息可以用于调试和分析程序。如果需要生成发布版本的程序,可以使用strip命令删除这些信息,以减小程序的大小。

m0_51073072的博客 2130

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

ubuntu下C/C++编译

在ubuntu环境下对C/C++进行编译首先保证ubuntu系统中包含编译工具(g++/gcc)和编辑工具(vim) 如果环境中不存在可以在终端中输入以下命令进行安装: sudo apt-get install build-essential sudo apt-get install gcc sudo apt-get install g++ 初次之外了解gcc和g++之间的区别见如下链接 http...

赵凯月的博客 1万+

Linux中C/C++程序编译过程与动静态链接库概述

在云服务器上,如果默认没有安装 C/C++ 的静态库和编译工具,您可以通过相应的包管理器手动安装所需的工具和库。安装完成后,您就可以开始编写和编译 C/C++ 程序了。

Srlua的博客 1914

C/C++:编译全过程——预处理、编译、汇编、链接

C/C++的编译过程包含了四个步骤:1.预处理2. 编译(Compilation)3. 汇编(Assemble)4..链接(Linking)阶段源文件预处理文件汇编文件目标文件可执行文件预处理编译汇编链接executable预处理:源文件变为去除了注释、包含了头文件的文件。编译:源代码被转换为汇编语言。汇编:汇编语言代码被转换为机器代码(目标文件)。链接:目标文件被链接成最终的可执行文件。

weixin_45481496的博客 1189

VS中C/C++的编译流程(非常详细)

文章目录前言一、.CPP文件和.OBJ文件1. .CPP文件2. .obj文件二、编译过程三、链接过程四、C/C++中提供的一些特性简要说明编译流程 前言 编译过程就是将源文件中的源代码翻译成机器语言,保存到目标文件中。如果编译通过,就会把CPP文件转换成OBJ文件 一、.CPP文件和.OBJ文件 1. .CPP文件 每个cpp就是一个编译单元,每个编译单元相互之间是独立且相互不知的。一个编译单元是指一个.CPP文件及这include的所有.h文件。.h文件里面的代码将会扩展到包含它的.cpp文件,然

qq_52269550的博客 1万+

预处理、编译、汇编和链接

由此看出头文件中仅只包含函数或变量的声明, 如函数声明、类/结构体定义、宏定义、类型别名等,但不包含函数实现(内联函数和模板例外)。当使用了强制静态链接后, 我们发现文件的大小一下子变大了不少, 由此可见静态链接虽然导致速度变快了, 但是也引发了一个问题, 可执行程序的体积体积会变得很大。strace -e openat ./program 2>&1 | grep '\.so' //查看程序尝试打开哪些库文件。所以在这里test就是可执行程序,但是如果是Windows的话它就是test.exe格式。

m0_68381723的博客 2167

【C/C++】编译与链接过程详解

如果程序中引用了一个未定义的符号(如函数或变量),链接器会报出“未定义符号”的错误。例如,调用了某个函数,但该函数的实现没有包含在目标文件或库文件中。检查是否忘记包含某个源文件或库文件。确保函数或变量已经正确定义。

卜及中的博客 1269

C/C++程序编译与链接(一) 编译与链接的概念

编译器本质上是将源代码中的高级语言翻译成低级语言,比如翻译成intel x86平台的汇编代码。上面简单的描述编译器的主要工作后,可以体会到编译器的复杂性,通常一个大型的C/C++项目有成千上万上个源文件,源文件间的调用关系错中复杂,编译器生成正确的汇编代码,要理解这些调用关系产生正确符号地址等,再生成正确的执行文件,还要讲究速度。这样简单的想想都很复杂。平常我们认为C/C++复杂,认为它们是比较低级的语言,但是在它们背后,编译器还是做了大量的工作。那么对python,java这样简洁,高效的语言。

mo4776的博客 2836
上一篇: C/C++相关概念和易错知识点(7)(memcpy和memmove、文件函数)
下一篇: C/C++相关概念和易错知识点(14)(贪吃蛇初步实现、思路)
-SGlow-
博客等级 码龄3年 1513粉丝 118原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值