补充前序知识:
1.CMakeLists.txt文件中,如下图,第一行生成的是静态库文件(我们前一讲所使用的),第二行是动态库文件。
静态库与动态库:
静态库(Static Libraries)
定义: 静态库是在编译链接阶段被链接到目标程序的库。这意味着库中的函数和数据会被直接嵌入到最终的可执行文件中。
何时使用:
- 当你希望你的应用程序是一个独立的、不需要额外依赖的二进制文件时。
- 在需要保证应用程序的稳定性和安全性的情况下,避免外部动态库的版本冲突或缺失。
优点:
- 可执行文件可以在没有额外库文件的情况下运行,提高了部署的便捷性。
- 减少了由于外部库文件缺失或版本不匹配导致的运行时错误。
缺点:
- 生成的可执行文件较大,因为包含了所有库的代码。
- 不利于代码更新,每次修改库代码都需要重新编译链接整个应用程序。
- 资源消耗较大,如果多个程序使用相同的静态库,每个程序都会包含一份库的副本。
动态库(Dynamic Libraries)
定义: 动态库是在程序运行时被加载的库。它们不会被直接嵌入到可执行文件中,而是在运行时通过系统调用加载。
何时使用:
- 当你希望你的应用程序能够利用系统上最新的库版本,或者需要与多个不同版本的库兼容时。
- 在开发大型系统或组件化软件时,动态库可以提供更好的模块化和代码重用。
优点:
- 可执行文件较小,因为不会包含完整的库代码。
- 多个应用程序可以共享同一个动态库,节省了系统资源。
- 动态库可以独立更新,而不影响已经部署的程序。
缺点:
- 需要确保目标系统上有正确的动态库版本,否则程序可能无法运行。
- 加载动态库会增加启动时间和运行时的复杂性。
- 如果动态库的API改变,可能需要更新使用该库的所有应用程序。
两者区别:
静态库: 当使用静态库时,编译器和链接器会在链接阶段将静态库中的对象文件(.o或.obj文件)合并到最终的可执行文件中。这实际上意味着静态库的代码和数据会被包含在最终的可执行文件中,就好像它们是原生代码的一部分一样。因此,当可执行文件运行时,它并不需要外部的库文件,因为所有必要的代码都已经存在于可执行文件内部了。
动态库: 相比之下,动态库(或共享库)在链接阶段仅记录了一个引用,而不是实际的代码和数据。当程序运行时,操作系统会查找并加载这些动态库。程序通过动态链接器(或加载器)间接调用动态库中的函数。这意味着动态库的代码和数据存储在独立的文件中,并且在运行时由操作系统动态地连接到正在运行的程序上。如果多个程序使用同一个动态库,那么这个库只被加载一次,从而节省了内存。
这种区分在概念上与“内联”和“函数调用”有点类似,但实际上是关于代码如何在编译和链接阶段被处理,以及在运行时如何被加载和执行的问题。静态库的行为更像是将函数“内联”到每个调用它的位置,而动态库则保持函数调用的独立性,即调用者和实现者分离,直到运行时才将两者结合在一起。
——————————————————————————————————
IDE,全称为Integrated Development Environment,即集成开发环境。它是一种软件应用,旨在提供一个统一的界面,使程序员能够更方便地编写、测试和调试代码。IDE通常包括代码编辑器、编译器、调试器以及其他工具,如版本控制系统、图形用户界面设计工具等。
流行的IDE包括Visual Studio(针对C++、C#等)、Eclipse(针对Java)、PyCharm(针对Python)、IntelliJ IDEA(针对Java和Kotlin)、Xcode(针对Swift和Objective-C)等。
在这里可以使用kdevelop,并自行安装。它支持CMake和C++。
在kdevelop中运行程序
1.加载程序(按理说只需文件夹里有一个配置文件即可,下图中文件夹里有多余文件)
注意:在KDevelop中,当你选择Project - Open/Import Project
路径后,应该选择一个包含CMakeLists.txt
文件的文件夹,而无法选择单个的.txt
文件。这是因为KDevelop是根据CMakeLists.txt
文件来识别和管理项目的。当你选择包含CMakeLists.txt
的文件夹时,KDevelop会自动识别出这是一个CMake项目,并根据其中的配置来设置项目环境。
2.注意将功能选项卡切换至project选项,并检查是否成功导入 (如果第一步文件夹里只有配置文件的话应该只显示红框中的部分)
3.Debug:比如我输入如下图的“不合法”语句,依旧可以通过编译(int *p=0;*p=1;)
PS:下面代码问题为:
这段代码的问题在于它试图访问一个未初始化的指针所指向的内存位置,并对其进行写操作。
int *p = 0;
这一行声明了一个整型指针p
,并将其初始化为0
。在C和C++中,0
(或等价的NULL
)通常用来表示一个空指针,即这个指针不指向任何有效的内存地址。
*p = 1;
这一行尝试通过指针p
去访问某个内存位置,并将1
赋值给该位置。然而,由于p
被初始化为0
,这意味着它实际上并不指向任何合法的内存区域。当你尝试解引用一个空指针或未正确初始化的指针时,程序的行为是未定义的(Undefined Behavior),这意味着程序可能会崩溃,或者表现出其他不可预测的行为,甚至可能看起来正常工作(但这通常是一种误导)。
通俗地说,你可以想象指针就像一把钥匙,它允许你打开并访问内存中的某个“房间”。当你将指针初始化为0
时,你实际上没有一把可以打开任何房间的钥匙。然后,当你尝试使用这把“不存在”的钥匙去打开一个房间并放置一些东西(例如1
)时,你就闯入了一个未知的空间,这可能导致各种问题,包括但不限于程序崩溃。
为了避免这类问题,你应该始终确保你的指针被正确初始化,并且在解引用之前,它们确实指向一个合法的内存地址。例如,我们可能需要分配内存(使用new
或malloc
)或让指针指向一个已存在的变量。
int *p = (int*)malloc(sizeof(int)); // 为整数分配内存,并将地址赋给指针p
if (p != NULL) { // 检查内存分配是否成功
*p = 1; // 现在可以将值1安全地赋给p所指向的内存位置
}
——————————————————————
如下图所示,存在有问题的语句但是却通过了编译。(编译需要点击Build键)
但是当我们在运行时就会出现问题。
如图程序中的报错就代表了程序已经崩溃了,当出现这种报错时,由于它没有显示哪行出现了何种问题的提示,我们就需要对程序进行debug(如放断点)
这时我们需要在项目的CMakeLists.txt文件中直接设置CMAKE_BUILD_TYPE
变量。格式如下:
set(CMAKE_BUILD_TYPE "Debug")
将这行代码放在CMakeLists.txt文件的开始部分,可以确保整个项目以Debug模式构建。
现在的配置文件整体如下:
4.在编辑完配置文件后点击Build编译,随后可以在文件中设置断点。注意要如下图设置,打开边框选项,易添加断点。
随后选择对应行,右键添加断点即可。
5.随后要使程序进入该断点,首先需要告诉它我想执行哪个程序(配置文件中有多个可执行文件)
Configure Launches
允许你为你的项目设置不同的运行配置。这些配置定义了如何启动和运行你的程序,包括指定可执行文件、命令行参数以及执行环境等。当想要运行或调试你的程序时,需要告诉KDevelop如何启动它。这就是Configure Launches
发挥作用的地方。
随后操作如图所示。
6.点击Debug,随后程序就会停在断点处。点击红框处便会显示step out、step over、step into了
若显示当前配置不支持debug,需要查看是否选择正确的执行文件,如下图
调试示意图:当从第八行继续step over后,原本p为0,并弹出错误提示(SIGSEGV)。
SIGSEGV错误,全称为Segmentation Fault,是一种在Unix-like操作系统中常见的程序错误信号。它表示程序试图访问其没有权限访问的内存区域,或者访问的内存地址不存在。
引起SIGSEGV错误的原因可能包括:
- 野指针:访问已经释放或未初始化的指针。
- 空指针解引用:试图访问一个值为NULL的指针所指向的内存。
- 数组越界:访问数组时超出了其边界。
- 非法内存访问:试图写入只读内存区域。
- 内存泄漏:程序中存在内存泄漏,导致内存不足或分配错误。
- 指针操作不当:例如指针越界等。
- 使用未映射到进程内存空间的指针:这可能意味着使用了错误的指针值。
- 栈溢出:函数调用层次过深,超过了栈的最大容量。
- 并发问题:在多线程环境中,不当的同步机制可能导致内存访问冲突。
- 硬件问题:极少数情况下,硬件故障也可能导致SIGSEGV错误。
7.点击Frame Stack就可以看到具体哪里出现错误了
8.修复错误后可以点击Build重新编译或点击Execute运行,看看是否正常运行。