C++基础Ⅰ编译、链接

news2024/9/25 11:17:02

目录儿

  • 1 C++是如何工作的
    • 1.1 预处理语句
    • 1.2 include
    • 1.3 main()
    • 1.4 编译
      • 单独编译
      • 项目编译
    • 1.5 链接
  • 2 定义和调用函数
  • 3 编译器如何工作
    • 3.1 编译
      • 3.1.1 引入头文件
        • 系统头文件
        • 自定义头文件
      • 3.1.2 自定义类型
      • 3.1.3 条件判断
      • 拓展: 汇编
    • 3.2 链接
      • 3.2.1 起始函数
      • 3.2.2 被调用的函数
    • 3.3 总结,编译和链接的区别

1 C++是如何工作的

工具:Visual Studio

1.1 预处理语句

.cpp源文件中,所有#字符开头的语句为预处理语句

例如在下面的 Hello World 程序中

#include<iostream>

int main() {
	std::cout <"Hello World!"<std::endl;
	std::cin.get();
}

#include<iostream>就是一个预处理语句(pre-process statement),编译器在加载源文件的时候,识别到#开头的语句,会优先处理这个语句,所以称为预处理语句。

注意:预处理语句是在 编译器加载源文件的时候处理的,那个时候还没有发生编译动作

1.2 include

include关键字的含义就是找到<xxx>里面指定名称的文件,然后把文件里面的内容拷贝到当前文件,以供调用;

这个被导入的文件称为头文件;

1.3 main()

main()函数是程序的入口,计算机从main()函数开始运行程序,每个程序都要有一个main()函数;

main()函数的返回值是int类型,但是在 Hello World 程序中我们没有返回任何值,这是因为main()函数比较特殊,如果没有显式返回一个int值,他会默认返回0

1.4 编译

单独编译

当写好了一个源文件,就可以对其进行编译操作,在Visual Studio上直接按快捷键ctrl + F7,或者点击编译按钮执行编译
在这里插入图片描述
编译结果在输出窗口就能看到:
在这里插入图片描述
注意,此时我们是针对一个源文件进行单独编译,而不是编译整个项目。

每次编译需要指定规则和目标平台
在这里插入图片描述

  • 规则:
    默认分为DebugRelease,代表着编译代码时按照设置选择的的规则设置进行编译,这些规则是可以自行设置的,但一般都用默认设置。比如在 Debug 规则的默认设置中,不会对程序进行优化
    在这里插入图片描述
    Release 规则的默认设置中,则会对程序进行链接优化
    在这里插入图片描述
  • 目标平台:
    意思就是你这个代码编译后是用在哪个平台的,比如x86/windows64位x64/windows32位或者是Android等移动平台(因为C++是不能跨平台运行的,所以不同的目标平台编译出来的二进制码不一样,不像Java

打开项目目录可以看到,在不同的规则下编译生成的文件分别放在不同的目录下面:
在这里插入图片描述
在目录里面可以看到.cpp文件编译生成的.obj文件
在这里插入图片描述
打开看, 里面的内容都是二进制机器码
在这里插入图片描述

项目编译

在资源文件窗口中,项目名称→右键→生成,这也是一个编译操作,但此时是编译整个项目;
它会把项目中的每个.cpp文件编译成.obj文件,然后再把这些.obj文件链接成一个程序比如.exe程序;
在这里插入图片描述
在输出窗口可以看到生成了一个.exe文件
在这里插入图片描述
打开对应的目录就能看到
在这里插入图片描述

1.5 链接

链接比较复杂,大概就是一个C++项目通常都包含这很多个源文件,而编译后每一个源文件对应地都会生成一个.obj二进制码文件,然后链接的作用就是把这些二进制码文件链接起来构成一个完整的项目。

2 定义和调用函数

写在同一个文件中:

#include<iostream>

void Log(const char* message) {
	std::cout << message << std::endl;
}

int main() {
	Log("Hello World!");
	std::cin.get();
}

写在不同的文件中:
在这里插入图片描述
Log.cpp

#include<iostream>

void Log(const char* message) {
	std::cout << message << std::endl;
}

Main.cpp

#include<iostream>

void Log(const char* message);
//void Log(const char*); // 声明函数时可以忽略参数名

int main() {
	Log("Hello World!");
	std::cin.get();
}

想要在Main.cpp文件中调用Log函数,必须先声明,声明函数和定义函数的区别就是一个有方法体,一个没有方法体;

这里注意的点是,编译器在编译单个Main.cpp这个源代码文件的时候,并不会去检查这个声明的函数是否真实存在,而且编译单个文件的时候不会对编译文件进行链接;
但是当运行或者编译整个项目的时候,也就是进行文件链接的时候,如果声明的函数不存在,就会报错:
在这里插入图片描述

3 编译器如何工作

首先需要知道,编译分为两个阶段: 编译 + 链接

3.1 编译

不经过设置时, 执行编译默认会直接编译成.obj文件, 直接就是二进制码了
为了能够搞清楚从 源代码 → 二进制码 的过程中发生了什么, 我们接下来就先不直接编译成.obj文件
而是先把编译过程中预处理后产生的内容输出成文件, 看看预处理都处理了啥.

3.1.1 引入头文件

前面说过,编译器处理#include<xxx>这个语句就是把对应的xxx头文件里面的内容copy到当前文件中#include语句所在的地方

下面来证实一下
首先打开项目属性:
在这里插入图片描述
这一步是为了让编译器在预编译后把内容输出成一个文件, 这样我们就可以看到预编译的内容了, 注意这个设置也是精确到配合和平台的, 选择所有配置和自己的操作系统平台就行;

注意: 修改了输出预编译文件后, 编译器就不会输出.obj文件, 所以做完实验就要把它改回去!!

系统头文件

Main.cpp中, 我们引入了iostream头文件

#include<iostream>

int main() {
	std::cout << "Hello World!" << std::endl;
	std::cin.get();
}

编译一下, 生成的.i文件就是预编译文件
在这里插入图片描述
可以发现这个文件有1.6M大, 我只写了几行代码
直接打开看
在这里插入图片描述
会发现这个文件有6万多行, 这些内容就是从iostream头文件中copy过来的, 所以整个文件很大
这就印证了#inclued引入语句的作用.

自定义头文件

下面我们要编译这个Mutiply.cpp

int Mutiply(int a, int b) {
	int result = a * b;
	return result;

可以看到这个函数是缺少了一个}的, 故意的
接下来创建一个头文件EndBrace.h
在这里插入图片描述
头文件里面的内容就是一个}

}

接下来在Mutiply.cpp中引入这个EndBrace.h头文件
在这里插入图片描述
引入后Mutiply.cpp内容如下:

int Mutiply(int a,int b) {
	int result = a * b;
	return result;
#include"EndBrace.h"

好,接下来编译一下这个Mutiply.cpp源文件
在项目对应目录中可以看到生成了一个Mutiply.i文件, 这个就是预编译生成的文件
在这里插入图片描述
直接打开看内容

#line 1 "D:\\workspace\\CPP\\cherno\\cherno\\Mutiply.cpp"
	int Mutiply(int a,int b) {
		int result = a * b;
		return result;
	#line 1 "D:\\workspace\\CPP\\cherno\\cherno\\EndBrace.h"
}
#line 5 "D:\\workspace\\CPP\\cherno\\cherno\\Mutiply.cpp"

忽略那些#line语句, 可以看到EndBrace.h头文件中的}被复制到了Mutiply.cpp

注意,在c++中,引入头文件有两种方式:

  • #include<>这个语法用于引入系统头文件, 这种引入方式下预处理器会在标准系统目录中搜索这些文件。例如引入iostream头文件,可以使用#include <iostream>
  • #include ""语法用于引入用户定义的头文件, 这种引入方式下预处理器会首先在当前目录中搜索这些文件,如果没有找到,则在标准系统目录中搜索。例如,如果当前目录中有一个名为myheader.h的头文件,则可以使用#include "myheader.h"将其包含到程序中。

3.1.2 自定义类型

还是在Mutiply.cpp中做修改,

#define ECHOO int

ECHOO Mutiply(int a, int b) {
	ECHOO result = a * b;
	return result;
}

这里我自定义了一个ECHOO类型, 实际上是一个int类型
然后我在Mutiply定义中用了这个ECHOO类型代替原来的int

接下来编译看一下预编译生成的文件内容:

#line 1 "D:\\workspace\\CPP\\cherno\\cherno\\Mutiply.cpp"
int Mutiply(int a, int b) {
	int result = a * b;
	return result;
}

编译器自动把ECHOO替换成了它的实际类型int,
所以说#define实际上是自定义别名, 用一个别名代替实际的类型或者字符,符号
这也是C++灵活的地方,可以给各种类型, 符号自定义别名

再改一下, 用ECHOO代替Hello

#define ECHOO Hello

ECHOO Mutiply(int a, int b) {
	ECHOO result = a * b;
	return result;
}

预编译:

#line 1 "D:\\workspace\\CPP\\cherno\\cherno\\Mutiply.cpp"
Hello Mutiply(int a, int b) {
	Hello result = a * b;
	return result;
}

有点意思

3.1.3 条件判断

改一下Mutiply.cpp, 用#if语句来做条件判断

#if 0
int MutiplyOne(int a, int b) {
	int result = a * b;
	return result;
	//#include "EndBrace.h"
}
#endif // 0

#if 1
int MutiplyTwo(int a, int b) {
	int result = a * b;
	return result;
	//#include "EndBrace.h"
}
#endif // 1

函数MutiplyOne#if 0#endif包起来了
函数MutiplyTwo#if 1#endif包起来了

预编译:

#line 1 "D:\\workspace\\CPP\\cherno\\cherno\\Mutiply.cpp"
#line 8 "D:\\workspace\\CPP\\cherno\\cherno\\Mutiply.cpp"
int MutiplyTwo(int a, int b) {
	int result = a * b;
	return result;
}
#line 17 "D:\\workspace\\CPP\\cherno\\cherno\\Mutiply.cpp"

函数MutiplyOne不能被预编译
函数MutiplyTwo能正常被预编译
非常明显, 是因为判断条件的原因
我们可以通过#if condition这个语句来动态地禁用 / 启用某一段代码, 非常灵活
有点儿意思

拓展: 汇编

通过Visual Studio可以输出汇编文件, 设置一下汇编程序输出
,
接下来编译Mutiply.cpp:

int MutiplyOne(int a, int b) {
	int result = a * b;
	return result;
	//#include "EndBrace.h"
}

在项目目录中生成的.asm文件就是生成的汇编程序文件
在这里插入图片描述
打开可以看到一条一条的汇编指令,

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.36.32537.0 

include listing.inc

INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES

msvcjmc	SEGMENT
__B1702CDC_Mutiply@cpp DB 01H
msvcjmc	ENDS
PUBLIC	?MutiplyOne@@YAHHH@Z				; MutiplyOne
PUBLIC	__JustMyCode_Default
EXTRN	_RTC_InitBase:PROC
EXTRN	_RTC_Shutdown:PROC
EXTRN	__CheckForDebuggerJustMyCode:PROC
;	COMDAT pdata
pdata	SEGMENT
$pdata$?MutiplyOne@@YAHHH@Z DD imagerel $LN3
	DD	imagerel $LN3+63
	DD	imagerel $unwind$?MutiplyOne@@YAHHH@Z
pdata	ENDS
;	COMDAT rtc$TMZ
rtc$TMZ	SEGMENT
_RTC_Shutdown.rtc$TMZ DQ FLAT:_RTC_Shutdown
rtc$TMZ	ENDS
;	COMDAT rtc$IMZ
rtc$IMZ	SEGMENT
_RTC_InitBase.rtc$IMZ DQ FLAT:_RTC_InitBase
rtc$IMZ	ENDS
;	COMDAT xdata
xdata	SEGMENT
$unwind$?MutiplyOne@@YAHHH@Z DD 025051601H
	DD	01112316H
	DD	0700a0021H
	DD	05009H
xdata	ENDS
; Function compile flags: /Odt
;	COMDAT __JustMyCode_Default
_TEXT	SEGMENT
__JustMyCode_Default PROC				; COMDAT
	ret	0
__JustMyCode_Default ENDP
_TEXT	ENDS
; Function compile flags: /Odtp /RTCsu /ZI
;	COMDAT ?MutiplyOne@@YAHHH@Z
_TEXT	SEGMENT
result$ = 4
a$ = 256
b$ = 264
?MutiplyOne@@YAHHH@Z PROC				; MutiplyOne, COMDAT
; File D:\workspace\CPP\cherno\cherno\Mutiply.cpp
; Line 2
$LN3:
	mov	DWORD PTR [rsp+16], edx
	mov	DWORD PTR [rsp+8], ecx
	push	rbp
	push	rdi
	sub	rsp, 264				; 00000108H
	lea	rbp, QWORD PTR [rsp+32]
	lea	rcx, OFFSET FLAT:__B1702CDC_Mutiply@cpp
	call	__CheckForDebuggerJustMyCode
; Line 3
	mov	eax, DWORD PTR a$[rbp]
	imul	eax, DWORD PTR b$[rbp]
	mov	DWORD PTR result$[rbp], eax
; Line 4
	mov	eax, DWORD PTR result$[rbp]
; Line 6
	lea	rsp, QWORD PTR [rbp+232]
	pop	rdi
	pop	rbp
	ret	0
?MutiplyOne@@YAHHH@Z ENDP				; MutiplyOne
_TEXT	ENDS
END

看汇编指令在某些需要极致性能优化的时候会很有用, 但一般很少没多少人会这样做.

3.2 链接

当源文件被编译成一个个.obj文件的时候, 它们实际上还是一个一个独立的文件, 彼此直接没有关系
链接就是把所有.obj文件链接在一起,形成一个完整的程序
而这个程序必须有一个起始函数,如果没有特殊指定,这个起始函数默认是main函数!

3.2.1 起始函数

所以当一个项目是没有main函数的时候,单独编译文件不会报错,但是build或者运行项目的时候会报错

例:
当前项目中只有两个源文件,都不包含main函数
在这里插入图片描述
单独编译这两个源文件都没问题
但是当我生成整个项目时,发生了报错LNK1120
在这里插入图片描述
LNK 开头的错误是链接阶段发生的错误;
C 开头的错误是编译阶段发生的错误,比如语法错误;

现在我加一个源文件,里面声明了MutiplyOneLog函数,定义了main函数
并且在main函数中调用MutiplyOneLog函数

#include<iostream>

int MutiplyOne(int a, int b);
void Log(const char* message);

int main() {
	Log("Hello World!");
	std::cout << MutiplyOne(5, 18) << std::endl;
	std::cin.get();
}

在这里插入图片描述
直接运行项目,成功

Hello World!
90

这就代表着程序编译成功,链接器也成功找到了程序的起始函数,并成功把相关的函数找到、链接起来了。

3.2.2 被调用的函数

如果被调用的函数没有在当前源文件中声明

#include<iostream>

int MutiplyOne(int a, int b);
//void Log(const char* message);

int main() {
	Log("Hello World!");
	std::cout << MutiplyOne(5, 18) << std::endl;
	std::cin.get();
}

会报编译错误,因为这个属于语法错误:
在这里插入图片描述
如果被调用的函数不存在
会报链接错误:
在这里插入图片描述
所以很明显,链接的作用是把以起始函数为根节点,所有被调用到的函数链接起来,形成一整条调用链(或者说一整棵调用树)
在这里插入图片描述

如果想要指定其他函数作为程序的起始函数,可以通过链接器的高级设置指定:
在这里插入图片描述

3.3 总结,编译和链接的区别

编译:是先对源文件进行预处理,引入头文件,把头文件内容copy到源文件中,然后再把这些源文件编译成.obj或其他格式的二进制文件

链接:是把编译好的.obj文件里面相互调用的函数链接起来,形成一个完整的程序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/907757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++新经典09--函数新特性、inline内联函数与const详解

函数回顾与后置返回类型 函数定义中如果有形参则形参应该有名字&#xff0c;而不光是只有类型&#xff0c;但是如果并不想使用这个形参&#xff0c;换句话说这个形参并不在这个函数中使用&#xff0c;则不给形参名也可以&#xff0c;但在调用这个函数的时候&#xff0c;该位置…

什么情况下,亚马逊账户会被判滥用?

如果说有对亚马逊跨境电商有所了解的朋友就会知道&#xff0c;现在亚马逊跨境电商的规则是十分严格的&#xff0c;亚马逊开店变得越来越困难&#xff0c;尤其是要想成功的把一个亚马逊店铺给开好。 这几年不少有一些违规的亚马逊卖家都被系统检测到了&#xff0c;如果说被系统…

投资者的秘密武器,代理IP在金融决策中的驱动作用

在如今数据为王的时代&#xff0c;无论从事哪个行业&#xff0c;都需要使用数据分析来指导自己的决策&#xff0c;而这些数据又是从哪里来的呢&#xff1f;很多人都知道&#xff0c;数据采集可以帮助我们将分散在互联网各个网站上的大量数据集中起来。对于金融行业来说&#xf…

重磅丨无人机新规出台,这些红线不能踩!

近年来&#xff0c;随着无人机研发技术逐渐成熟&#xff0c;无人机在各个领域得到了广泛应用&#xff0c;包括VR全景航拍、乡村农业、城市管理、环境监测等领域&#xff0c;其应用场景及使用方式都还在迅速拓展&#xff0c;无人机行业受到社会广泛关注。 但在实践中&#xff0c…

SOLIDWORKS焊件是什么?

SOLIDWORKS是一款广泛应用于机械设计领域的三维计算机辅助设计软件。SOLIDWORKS提供了强大的焊件功能&#xff0c;可以帮助工程师们以更高的效率设计焊接件。本文将介绍SOLIDWORKS焊件的概念、特点以及使用方法&#xff0c;以期帮助读者更好地理解和应用这一关键技术。 SOLIDWO…

国际刑警组织逮捕 14 名涉嫌盗窃 4000 万美元的网络罪犯

Bleeping Computer 网站披露&#xff0c;4 月份&#xff0c;国际刑警组织发动了一起为期四个月&#xff0c;横跨 25 个非洲国家的执法行动 “Africa Cyber Surge II”&#xff0c;共逮捕 14 名网络犯罪嫌疑人&#xff0c;摧毁 20000 多个从事勒索、网络钓鱼、BEC 和在线诈骗的犯…

第七次作业 运维高级 docker容器进级版

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 (1)拉取相应镜像 docker pull mysq:5.6 docker pull owncloud:latest(2)运行mysql&#xff1a;5.6容器 docker run --name mysql -e MYSQL_ROOT_PASSWORD12345 -d mysql:5.6(3)运行owncloud容器 docker run…

【转】CentOS7安装GUI界面及远程连接的实现

用基于浏览器(webdriver)的selenium技术爬取数据&#xff0c;所以程序需运行在GUI环境下。本文分三个部分简要介绍安装GUI界面及远程连接的步骤。 安装GUI界面 大多数云服务器厂商提供的镜像都无GUI界面&#xff0c;所以要先安装图形环境。本文使用GNOME桌面环境&#xff1a;…

小程序 - 人脸核验遇到的问题 startFacialRecognitionVerifyAndUploadVideo:fail:access denied

目录 报错 - 没有权限代码解决 【补充】接口调用正常页面展示 报错 - 没有权限 {"errno": 102, "errMsg": "startFacialRecognitionVerifyAndUploadVideo:fail:access denied"} 代码 import Taro, { Component } from tarojs/taroclass Home e…

DeepHow首席执行官兼联合创始人郑三博士谈运用人工智能加速视频创作,实现视频界的“降本增效”

原创 |文 BFT机器人 DeepHow的首席执行官兼联合创始人Sam Zheng领导着一家快速发展的初创公司&#xff0c;并得到了受人尊敬的投资者的支持。DeepHow通过创新的、人工智能驱动的、以视频为中心的知识捕获和传输平台&#xff0c;彻底改变了熟练的劳动力培训。 在加入DeepHow之前…

YOLO目标检测——猫狗识别数据集下载分享

猫狗识别数据集是一个常用的用于猫和狗图像分类任务的数据集&#xff0c;包含了大量的猫和狗的图像样本 数据集点击下载&#xff1a;YOLO猫狗识别数据集5000图片.rar

了解AI智能问答的流程之后!使用起来更简单了

AI智能问答流程主要是按照自然语言理解、对话管理、自然语言生成这3个步骤&#xff0c;通过这些步骤之后&#xff0c;就可以将语言进行转换&#xff0c;转换成计算机能够理解的意思&#xff0c;再根据当前对话管理判断应该采取的策略。接下来looklook会详细来讲讲具体是如何实现…

<数据结构与算法>堆的应用二叉树的链式实现

目录 前言 一、堆的应用 1. 堆排序 1.1 排升序&#xff0c;建大堆 1.2 时间复杂度计算 2. Top k问题 二、 二叉树的链式实现 1. 二叉树的遍历 2. 二叉树基础OJ 3.DFS && BFS 总结 前言 学习完堆的数据结构&#xff0c;我们要清楚&#xff0c;它虽然实现了排序功能&am…

Wlan——STA上线流程与802.11MAC帧讲解

目录 802.11MAC帧基本概念 802.11帧结构 802.11MAC帧的分类 管理帧 控制帧 数据帧 STA接入无线网络流程 信号扫描—管理帧 链路认证—管理帧 用户关联—管理帧 用户上线 802.11MAC帧基本概念 802.11协议在802家族中的角色位置 其中802.3标准属于以太网的一种帧格式…

Redis 分布式锁存在什么问题 ?如何解决 ?

目录 1. 如何实现分布式锁 2. Redis 分布式锁存在什么问题 2.1 解决死锁问题 2.2 解决锁误删问题 1. 如何实现分布式锁 Redis 天生就可以作为一个分布式系统来使用&#xff0c;所以它实现的锁都是分布式锁。 Redis 可以通过 setnx&#xff08;set if not exists&#xff09…

vant4 自定义垂直步骤条时间线组件几行css代码改造完成(附效果图)

直接上效果图片 <template><!-- 审批流程 --><div><van-steps direction="vertical" active="-1"><van-step><template #inactive-icon><div class="relative"><img :src="girlIcon" /&…

JavaScript-console:JavaScript控制台(Console)常用方法

一、理解 console JavaScript 控制台&#xff08;console&#xff09;是一个开发人员在编写 JavaScript 代码时常用的工具。它是浏览器提供的一种界面&#xff0c;让开发人员能够追踪代码执行的状态和结果。JavaScript 控制台可以记录代码输出的信息、警告和错误&#xff0c;并…

opencv 进阶13-Fisherfaces 人脸识别-函数cv2.face.FisherFaceRecognizer_create()

Fisherfaces 人脸识别 PCA 方法是 EigenFaces 方法的核心&#xff0c;它找到了最大化数据总方差特征的线性组合。不可否认&#xff0c;EigenFaces 是一种非常有效的方法&#xff0c;但是它的缺点在于在操作过程中会损失许多特征信息。 因此&#xff0c;在一些情况下&#xff0c…

外贸网站怎么做推广优化

外贸网站的推广和优化是关键&#xff0c;因为它可以帮助您扩展国际市场并吸引更多的客户。以下是e6zz seo多年经验总结出一些有效的方法&#xff0c;可以帮助您推广和优化外贸网站&#xff1a; 优化网站内容&#xff1a; 确保您的网站内容对国际客户有吸引力&#xff0c;包括产…

lab4 traps

在开始做lab之前务必弄清楚 所谓的系统调用&#xff0c;本质上就是内核态和用户态之间的切换内核态和用户态的区别本质上就是一些关键属性的区别&#xff0c;比如页表 而他们的运行方式都一样&#xff0c;就是取指执行&#xff0c;没有魔法 只不过内核态和用户态的执行不在一个…