【C++】 探索程序 详细解读程序在运行过程中都发生了什么

2023-10-27

目录

头文件-源代码

头文件重复包含

问题

解决方案

程序生成过程

预处理Preprocessi

编译Compilation

汇编Assembly

链接Linking

编译期-运行期

 编译期确定

运行期确定

编译期错误

运行期错误

类和对象

宏的其他用法

inline内联


头文件-源代码

头文件(.h)和源文件(.cpp)两者的区别:

  1. 默认情况下,头文件不参与编译,而每个源文件自上而下独立编译。
  2. 通常我们将声明的变量、类型、函数、宏、结构体和类的定义等放于头文件(.h文件),将变量的定义初始化、函数的定义实现放于源文件中,这样方便于我们去管理、规划,更重要的是避免了重定义的问题。

 正常变量的声明与定义

//test.h
extern int b;   //变量的声明
//test.cpp
int b = 1;

注意:变量不要在头文件中直接定义

正常函数的声明与定义

//test.h
void fun(int );   //声明
//test.cpp
void fun(int a) {
	cout << __FUNCTION__ << endl;
}

类中的成员函数在对应的源文件中定义时,一定要加上类名作用域

//test.h
class CTest{
public:
    void fun();
};
//test.cpp
void CTest::fun(){
 	cout << __FUNCTION__ << endl;
}

类中普通成员属性const成员属性构造函数中的初始化参数列表进行初始化定义,而静态成员属性要在源文件中单独定义

//test.h
class CTest {
public:
	int m_a;
	static int m_b;
	const int m_c;


	CTest(); //只声明
	~CTest();
};
//test.cpp
int CTest::m_b = 5;

//类成员函数在类外定义,需要在函数名前加上类名作用域
CTest::CTest():m_a(4) ,m_c(6){
	cout << __FUNCTION__ << endl;
}
CTest::~CTest() {
	cout << __FUNCTION__ << endl;
}

常函数的声明与定义(保留const关键字)

//test.h
	void funConst() const;
//test.cpp
//关键字需要保留
void CTest::funConst(/* const CTest* const this */) const {
	cout << __FUNCTION__ << endl;
}

静态成员函数的声明与定义(去掉static关键字)

//test.h
	static void funStatic();
//test.cpp
void CTest::funStatic() { //去掉static 关键字
	cout << __FUNCTION__ << endl;
}

虚函数的声明与定义(去掉virtual关键字)

//test.h
	virtual void funVirtual();
//test.cpp
//去掉 virtual关键字
void CTest::funVirtual() {
	cout << __FUNCTION__ << endl;
}

纯虚函数(不需要实现)

//test.h
virtual void fun() = 0;

头文件重复包含

问题

如果在一个头文件中创建一个类,然后另外两个头文件又都用到了这个头文件,在源文件中使用了另外两个头文件,那么第一个头文件中的类就会被创建两次,会出现重定义的错误。

//AA.h
class AA {
public:
	int m_a = 0;
};
//BB.h
#include"AA.h"

class BB {
public:
	AA m_aa;
};
//CC.h
#include"AA.h"

class CC {
public:
	AA m_aa;
};

在源文件中使用BB和CC两个头文件打开后就是这样:

class AA {
public:
	int m_a = 0;
};

class BB {
public:
	AA m_aa;
};


class AA {
public:
	int m_a = 0;
};

class CC {
public:
	AA m_aa;
};

那么这种现象就称为头文件重复包含

解决方案

解决头文件重复包含问题有两种方法:

  1. #pragma once    :  告诉编译器,当前的头文件在其他的源文件中只包含一次
  2. 宏逻辑判断(#ifndef #define #endif)

对比:

  • #pragma once直接告诉编译器这个文件在源文件中只包含一次,相对来说效率比较高。
  • 基于宏逻辑判断,在大量头文件时,编译速度降低,耗时增加。而且需要考虑宏重名的问题,一般情况下宏的名字与当前文件名对应,但时并不能保证一定不重名,如果不同路径下存在相同的文件,也可能会重复。

程序生成过程

预处理Preprocessi

将原文件(.cpp)初步处理,生成预处理文件(.i):

  1. 解析#include头文件展开替换。
  2. 宏定义指令:#define宏的替换,#undef等。
  3. 预处理指令:解析#if、#ifndef、#ifdef、#else、#elif、#endif等。
  4. 删除所有注释。 

编译Compilation

将预处理后的文件(.i)进行一系列词法分析、语法分析、语义分析及优化,产生相应的汇编代码文件(.asm)。

汇编Assembly

将编译后的汇编代码文件(.asm)汇编指令逐条翻译成目标机器指令,并生成可重定位目标程序的.obj文件,该文件为二进制文件,字节编码是机器指令。

链接Linking

通过链接器将多个目标文件(.obj)和库文件链接在一起生成一个完整的可执行程序。

编译期-运行期

编译期:是指把源程序交给编译器编译、生成的过程,最终得到可执行文件。

运行期:是指将可执行文件交给操作系统执行、直到程序退出,把在磁盘中的程序二进制代码放到内存中执行起来,执行的目的是为了实现程序的功能。

 编译期确定

#ifdef __cplusplus

#define A 1

#else
#define A 2

#endif

	int a = A; //编译期确定
	cout << a << endl;  //1

运行期确定

	int b = 0;
	cin >> b;

	if (b) {
		b = 10;
	}
	else {
		b = 20;
	}

	int c = b; //运行期确定
	cout << c << endl;

编译期错误

	int len = 10;
	int arr[len];  //编译期错误

表达式的计算结果不是常数,编译期分配内存,因为必须要确定len的大小,但它是变量在编译期无法确定其具体值。

运行期错误

    int len1 = 0;
	cin >> len1;
	int* p = new int[len1];  //运行期

	//p[100] = 10; //数组越界:运行期错误

程序崩溃:数组越界,在编译期是检查不出来的,在真正运行时可能会报错。

类和对象

类:编译期的概念,包括:类成员函数、静态属性,作用域  访问修饰符

对象:运行期的概念实例,引用,指针

class CFather {
public:
	virtual void fun() {
		cout << "CFather::fun" << endl;
	}
};

class CSon :public CFather {
private: //编译期的限制
	virtual void fun() {
		cout << "CSon::fun" << endl;
	}
};
	CFather* pFa = new CSon;

	pFa->fun();   //Cson::fun

编译器在检查代码时,他认为pFa->fun()调用的是父类中public属性的函数,那自然是通过编译的。但是在运行期时由于多态的作用,结果调用的是子类的fun函数,即使子类的fun函数是private但由于访问修饰符是编译期的限制,所以在运行时无效,子列的fun函数自然也能调用。

宏起到替换作用(预处理阶段),一般写法:

#define A 10

一个标识符被宏定义后,在用到宏A的地方替换为10,再程序编译前预处理阶段进行替换,替换后才进行编译。

宏是可以传参数的,在宏名字后面加(PARAM),参数的作用也是一个替换。

#define N(PARAM) int a = PARAM;

一般情况下,宏替换当前这一行的内容,替换多行可以使用\这个字符

作用:用来连接当前行和下一行。

注意:一般最后一行不加\\后面不能有任何字符,包括空格、tab、注释等。

#define B\
	for (int i = 0; i < A; i++){\
		cout << i << "  ";\
	}

使用宏替换需要注意,宏及参数并不会像函数参数一样自动计算,也不做表达式求解,它只是单纯的复制粘贴。

#define N 2+3
int a = N*2; //2+3*2

可以加上()来解决

#define N (2+3)
int a = N*2; //(2+3)*2

#undef宏:取消宏定义,限制宏的作用范围

#define AB 10
	cout << AB << endl;
#undef AB  //取消宏定义

	int AB = 20;
	cout << AB << endl;

优点

  1. 使用宏可以替换在程序中经常使用的常量或表达式,在后期程序维护时,不用对整个程序进行修改,只需要维护、修改一份宏定义的内容即可。
  2. 宏在一定程度上可以代替简单的函数,这样就省去了调用函数的各种开销,提高程序的运行效率。

缺点

  1. 不方便调试。
  2. 没有类型安全的检查
  3. 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,也并不会计算求解,存在一定的安全隐患。

宏的其他用法

我们先来创建三个重载函数用以测试

void fun(int a) {
	cout << __FUNCSIG__ << "  " << a << endl;
}

void fun(const char* p) {
	cout << __FUNCSIG__ << "  " << p << endl;
}

void fun(char c) {
	cout << __FUNCSIG__ << "  " << c << endl;
}

# 将宏参数转为字符串,相当于加了 双引号

#define D(PARAM) #PARAM
	fun(D(123));
	fun(D("abc"));

 #@ 将宏参数转为字符,相当于加了 单引号

#define E(PARAM) #@PARAM
	fun(E(1));

 ## 拼接作用,常用于宏参数与其他内容的拼接

#define F(PARAM) int a##PARAM = 100;
	F(1)
		cout << a1 << endl;

inline内联

内联函数是C++为了提高程序的运行速度做的一项改进,普通函数和内联函数主要区别不在于编写方式,而在于C++编译器如何将他们组合到程序中的。编译器将使用相应的函数代码替换到内联函数的调用处,所以程序无需跳转到另一个位置执行函数体代码,所以会比普通的函数稍快,代价是需要占用更多的内存,空间换时间的做法。

执行函数之前需要做一些准备工作i,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码,代码执行完毕后还要将之前压入栈中的数据都出栈。这个过程涉及到空间和时间的开销问题,如果函数体中的代码比较多,逻辑也比较复杂,那么执行函数体占用大部分时间,而函数调用、释放空间过程花费的时间占比很小可以忽略;如果函数体中的代码非常少,逻辑也非常简单,那么相比于函数体代码的执行时间,函数调用机制所花费的时间就不能忽略了。

int add(int a,int b){
    return a+b;
}
int c = add(1,2);

 所以为了消除函数调用的时间开销,C++提供一种提高效率的方法:inline函数,上例中的add函数可以变为内联函数,如下,内联函数在编译时将函数调用处用函数体替换(类似于宏)。

inline int add(int a, int b) {
	return a + b;
}
int c = add(1,2);

 注意:

  1. inline是一种空间换时间的做法,内联在一定程度上能提高函数的执行效率,这并不意味着所有函数都要成为内联函数,如果函数调用的开销时间远小于函数体代码执行的时间,那么效率提高的并不多,如果该函数被大量调用时,每一处调用都会复制一份函数体代码,那么将占用更多的内存,得不偿失。所以一般函数体代码比较长,函数体出现内循环(for、while),switch等不应为内联函数。
  2. 并非我们加上inline关键字,编译器就一定会把它当作内联函数进行替换。定义inline函数只是程序员对编译器提出的一个建议,而不是强制性的,编译器有自己的判断能力,他会根据具体的情况决定是否把它认为是内联函数。编译器不会把虚函数、递归函数视为内联函数。
  3. 类、结构中在的类内部声明并定义的函数默认为内联函数,如果类中只给出声明,在类外定义的函数,那么默认不是内联函数,除非我们手动加上inline关键字。
class CTest{
    void fun1(){}  //默认内联
    void fun2();   //声明
};
void CTest::fun2(){}  //默认不是内联函数
CTest tst;
tst.fun1();  //内联替换
tst.fun2();  //未替换
void fun1(){
    int a =10;
}
void fun2(){
    int b= 20;
}

int main(){
    fun1();
    fun2();
    return 0;
}

汇编文件如下:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++】 探索程序 详细解读程序在运行过程中都发生了什么 的相关文章

  • fopen_s 怎么会比 fopen 更安全呢?

    我正在处理遗留代码Windows平台 当我编译代码时VS2013 它给出以下警告 错误 C4996 fopen 该函数或变量可能不安全 考虑使用fopen s反而 要禁用弃用 请使用 CRT SECURE NO WARNINGS 详情请参见
  • Linq - 从表达式 创建表达式

    我有一个谓词Expression
  • 生成多个随机数

    我想生成 25 个唯一的随机数并将它们列在控制台中 数字的长度应至少为 10 个字符 有什么简单的方法可以做到这一点吗 尝试将数字构建为字符串 并使用 HashSet 确保它们是唯一的 Random random new Random Ha
  • ASP.NET Web 应用程序中的身份验证遇到问题

    我正在尝试对从登录页面登录我的 Web 应用程序的用户进行身份验证 我正在使用本教程 http support microsoft com kb 301240作为指南 它几乎准确地解释了我希望做什么 但是当我输入用户名和密码时 验证不起作用
  • .crt 部分?这个警告是什么意思?

    我最近收到此警告 VC 2010 warning LNK4210 CRT section exists there may be unhandled static initializers or terminators 我假设这是关键部分
  • 将字符串作为 PChar 从 CSharp 传递到 Delphi DLL

    我正在尝试将字符串从 C 传递到 Delphi 构建的 DLL Delphi DLL 需要 PChar 这是Delphi导出 procedure DLL Message Location PChar AIntValue integer st
  • C# 无法捕获 SerializationException

    我的程序在加载序列化文件的部分遇到问题 如果文件无法反序列化 我希望很好地失败 但由于某种原因 我的程序将中断而不是进入 catch 子句 这是我的代码 using FileStream fs new FileStream openFile
  • Linq 合并列表

    我的课 public class Foo public int A get set public List
  • 我在使用 ado.net 时收到错误 Argument 2 may not be pass with ref keywords

    int t 0 cmd Parameters AddWithValue Res ref t 我在第二行收到错误 参数 2 不能与 ref 关键字一起传递 您只能通过引用传递参数ref if the 范围 is a ref参数也是如此 Add
  • 如何构建一棵与或树?

    我需要一个支持 与 和 或 的树结构 例如 给定一个正则表达式 如ab c d e 我想把它变成一棵树 所以 一开始我们有两个 或 分支 它可以向下ab or c d e 如果你低头ab分支 你得到两个节点 a and b or a其次是b
  • 使用数据绑定,如何将包含表情符号的文本绑定到标签并使其正确显示?

    我正在编写一个应用程序来连接 WordPress BuddyPress API 该应用程序将允许用户通过 API 相互发送消息 当这些消息包含表情符号时 我很难正确显示它们 以下是 API 返回的消息文本的简短示例 Hi x1f642 ho
  • DataGridView 行背景颜色没有改变

    我想根据加载时的特定条件更改 DGV 行的背景颜色 即使在 Windows 窗体中也是如此 但我看不到任何 DGV 行的颜色有任何变化 谁能告诉我如何解决这个问题 private void frmSecondaryPumps Load ob
  • 如何在 C# 中更改公共 IP 地址

    我正在创建一个 C winform 应用程序 我想在其中更改公共 IP 地址 而不是像 Hotspot Shield ZenMate OpenVPN 等那样更改 IPv4 地址 我已经检查了以下链接 但没有找到足够的帮助 所以我发布了这个问
  • 选择合适的IDE

    您会推荐使用以下哪种 IDE 语言来在 Windows 下开发涉及识别手势并与操作系统交互的项目 我将使用 OpenCV 库来执行图像处理任务 之后 我将使用 win32 API 或 NET 框架与操作系统交互 具体取决于您建议的工具 性能
  • Xcode 7 调试器不会中断内联标头函数

    过去五年我一直在各种 C 项目中使用 Xcode 没有出现这个问题 今天 我打开了一个较旧的项目 大约 2 年前 并尝试通过在该函数中放置一个活动断点来调试头文件中的内联函数 由于某种原因 调试器不会中断此代码 但是 如果我在调用该函数的
  • 卸载程序

    我正在尝试使用此代码卸载程序 但它似乎不起作用 我尝试过其他答案 但似乎也不起作用 有人可以帮助我吗 我正在尝试按给定名称 displayName 卸载该程序 例如 我给出 displayName Appname 那么此代码应该从我的计算机
  • 当我的进程被终止时到底会发生什么?

    我有一个包含本机代码和托管代码的混合进程 在 Windows Server 2003 上运行 当我从进程资源管理器中终止进程时 它会进入 100 cpu 的状态 并在消失之前保持这种状态一段时间 有时甚至 10 分钟 在此期间我无法 杀死
  • 具有四个 && 的 LINQ Where 子句

    我正在尝试在Where 子句中创建一个带有4 个参数的LINQ 查询 这是一个 Windows 8 应用程序项目 我正在使用 SQLite 数据库 SQLite 实现 https github com praeclarum sqlite n
  • Boost.asio和异步链,unique_ptr?

    我对异步编程不太熟悉 我有一个问题 我的问题如下 给出 boost asio 中 C 11 的 echo server 示例 http www boost org doc libs 1 60 0 doc html boost asio ex
  • 如何在 C 中创建最低有效位设置为 1 的掩码

    这个功能如何运作 最低有效 n 位设置为 1 的掩码 Example n 6 gt 0x2F n 17 gt 0x1FFFF 我根本不明白这些 尤其是 n 6 gt 0x2F 另外 什么是面膜 通常的方法是采取1 并将其左移n位 这会给你类

随机推荐

  • angularJs中的发送请求例子

    http 发送请求 url http localhost 8080 teacher api login method post data obj success function data rootScope data data 返回结果
  • MacBookAir M1 遇到 nodesass不兼容问题的解决方案

    引言 在工作中接手了一个新项目 项目采用了nodesass 想尽方法让他跑起来 却抛出一个错误 Node Sass does not yet support your current environment OS X Unsupported
  • [1151]python连接 redis cluster集群

    使用pip search查看可安装的Redis模块版本 PS D code gt pip install pip search 安装pip search模块 PS D code gt pip search redis 利用pip searc
  • 从DEMO到完成项目过程的流程

    一 项目评审 认真看demo需求是关键 根据项目demo原型 将项目功能点细分 按每个功能点实现的大致期限 去估计整个项目的期限 一旦项目评估预期确定 需要严格按照预期来实现 决不能拖拉 二 项目实施中 项目实施中 尽量以实现demo功能为
  • QQ机器人相关指令实现-对接小夹子

    代码地址以及视频地址 代码地址 视频地址 实现小夹子网的对接 打开小夹子网了解如何对接 小夹子网 小夹子API对接文档 完成认证的功能 通过小夹子网编写相关常量信息 public interface ClipWebConstants 基础路
  • 判断链表有环并返回入环的第一个节点

    这个问题可以两部分组成 1 首席判读链表是否有环 2 有环的话 在公共点拆开 设在ptr1 ptr2 那么ptr2前进一步 ptr2 ptr2 gt next ptr1拆链表 ptr1 gt next NULL 此时 就有两个链表了 一个是
  • 9008小米售后权限账号_小米救砖教程

    今早在头条看到个微头条 大概意思是小米9有锁机只需要几百块 然后就有人在下边评论说 为什么要出这期解锁教程呢 是因为某些沙雕说小米9进9008刷机不需要售后权限 当然并没有在上图出现 我当场笑喷 首先普及一下 mi8及以上用9008刷机需要
  • Camera根据鼠标操作观察物体(移动,转动,滑动)

    领导安排了一个任务 客户需求根据鼠标操作可以更详细的观察物体 于是开始研究Camera的使用 客户要求 1 按住右键可以旋转观察物体 2 按住左键可以平移的上下拖动视角 3 滚动滑轮可以拉近距离 思路 需要移动的是摄像机而不是物体 因为物体
  • 猜数字游戏

    define CRT SECURE NO WARNINGS 1 include
  • 在 Spring 中 Mock RestTemplate

    如果我们程序中使用了 RestTemplate 进行 HTTP API 调用 通常在编写单元测试时 为了让测试可控 会将 RestTemlate 调用进行 mock 而不是进行真实的 HTTP API 调用 这里 我们将介绍两种 mock
  • 【华为OD机试 2023】 打印机队列(C++ Java JavaScript Python)

    华为od机试题库 华为OD机试2022 2023 C Java JS Py https blog csdn net banxia frontend category 12225173 html 华为OD机试2023最新题库 更新中 C Ja
  • 【模板】高精度除低精度

    文章目录 1 数组 2 vector 推荐 1 数组 include
  • blender中的灯光和相机学习笔记

    目录 环境光 灯光属性 比如高反 编辑灯光基础 灯光基本属性 EV渲染器 点光 日光 面光 自发光 环境光 在渲染视图中 没有任何光时 物体只受环境光的影响 灯光属性 比如高反 在视图层属性中设置 灯光基础 1 四种灯光 点光 日光 聚光
  • 计算机视觉学习总结:基本的图像操作和处理(二)

    Numpy NumPy http www scipy org NumPy 是非常有名的 Python 科学计算工具包 其中 包含了大量有用的思想 比如数组对象 用来表示向量 矩阵 图像等 以及线性代数函数 1 图像数组表示 from PIL
  • 随想录(程序员的缺点)

    The original address of the document http blog csdn net feixiaoxing article details 7555315 声明 版权所有 欢迎转载 请勿用于商业用途 联系信箱 f
  • nvcc 编译并行程序时报错gcc: error trying to exec ‘cc1plus‘: execvp: 没有那个文件或目录

    一 nvcc 编译程序时报错 gcc error trying to exec cc1plus execvp 没有那个文件或目录 cc1plus 是gcc编译工具链中用到的一个程序 报错非常直白 就是说gcc在执行时找不到这个程序 二 问题
  • 北京航空航天计算机考研科目,2020考研北京航空航天大学计算机考研考试科目...

    2020考研 名校一直是考研学子的首选 北京航空航天大学作为全国名校 也是深受考研学子热衷追捧 那么下面启航小编就来介绍一下 2020考研 北京航空航天大学计算机考研科目 的相关内容 供考研学子参考 一起来了解一下吧 1 北京航空航天大学计
  • vue-admin 详细注释,必须手把手做项目系列之(二)

    文章将会自动保存至草稿 更新 麻雀虽小五脏俱全 项目地址 https github com whylisa vue admin step by step git 前言 这两天项目上线楼主要一大推的事要忙 要和产品撕逼 要和后台讨论一系列的问
  • selenium面试题

    今天有同学问到seleinum面试的时候会问到的问题 随便想了想 暂时纪录一下 欢迎大家在评论中提供更多问题 回复是我自己的答案 如果大家有什么好的看法 可以留言交流 去哪儿的面试题 selenium中如何判断元素是否存在 selenium
  • 【C++】 探索程序 详细解读程序在运行过程中都发生了什么

    目录 头文件 源代码 头文件重复包含 问题 解决方案 程序生成过程 预处理Preprocessi 编译Compilation 汇编Assembly 链接Linking 编译期 运行期 编译期确定 运行期确定 编译期错误 运行期错误 类和对象