常用的调试方法(段错误产生原因)
C 语言中常用的调试技巧和 demo
C语言中常用的调试方法
打印调试信息
GDB 调试器
编写单元测试
段错误产生原因
初学时两种常用的段错误调试方法
C 语言中常用的调试技巧和 demo
当程序员进行调试时,他们通常会使用一些调试语句或技巧来帮助他们理解代码的执行过程以及识别问题。以下是一些在 C 语言中常用的调试技巧和 demo:
1. 使用 printf 进行调试
#include <stdio.h>int main() {int a = 5;printf("Value of a: %d\n", a);// ...return 0;
}
2. 使用 assert 进行断言
#include <assert.h>int divide(int a, int b) {assert(b != 0);return a / b;
}
3. 使用 gdb 进行命令行调试
gcc -g -o my_program my_program.c
gdb ./my_program
在 GDB 中可以使用诸如 break, run, print, step, backtrace 等命令进行调试。
4. 输出变量地址
#include <stdio.h>int main() {int a = 5;printf("Address of a: %p\n", (void*)&a);// ...return 0;
}
5. 使用 #ifdef 和宏定义进行条件编译
#include <stdio.h>#define DEBUG 1int main() {#ifdef DEBUGprintf("Debugging information\n");#endif// ...return 0;
}
6. 手动触发程序崩溃
#include <stdio.h>
#include <stdlib.h>int main() {int* ptr = NULL;*ptr = 5; // This will cause a segmentation fault// ...return 0;
}
7. 使用 errno 打印错误信息
#include <stdio.h>
#include <errno.h>int main() {FILE *file = fopen("nonexistent_file.txt", "r");if (file == NULL) {perror("Error opening file");}// ...return 0;
}
8. 使用 valgrind 进行内存检查
valgrind ./my_program
Valgrind 可以检测内存泄漏和其他内存错误。
这些都是简单而有效的调试技巧,可以帮助你更好地理解代码并找到潜在问题。
C语言中常用的调试方法
在C语言中,常用的调试方法包括打印调试信息、使用调试器和编写单元测试等。下面是一些用于调试的示例代码:
- 打印调试信息
#include <stdio.h>// 在代码中插入打印语句
void example_function() {printf("Debug: This is a debug message.\n");// ... 其他代码 ...
}
- 条件打印
#include <stdio.h>// 使用条件打印语句
#ifdef DEBUG
#define DEBUG_PRINT(fmt, args...) printf(fmt, ##args)
#else
#define DEBUG_PRINT(fmt, args...) // 空定义,即不执行任何操作
#endifvoid example_function() {DEBUG_PRINT("Debug: This is a debug message.\n");// ... 其他代码 ...
}
在编译时,可以通过定义或未定义 DEBUG 宏来控制是否打印调试信息。
- 使用
assert断言
#include <assert.h>void example_function(int x) {assert(x > 0 && "x must be greater than 0");// ... 其他代码 ...
}
assert 宏用于检查一个表达式是否为真,如果为假,则触发一个断言失败。
- 使用调试器(GDB)
#include <stdio.h>int main() {int x = 10;int y = 0;// 计算除法,可能导致除零错误int result = x / y;printf("Result: %d\n", result);return 0;
}
在终端中使用 GDB 调试程序:
gcc -g -o my_program my_program.c
gdb my_program
在 GDB 中运行程序,当程序崩溃时,可以使用 backtrace 和其他命令来查看调用栈和变量值。
- 编写单元测试
使用测试框架进行单元测试,例如 C 中的 Unity 框架。编写测试用例来验证函数的预期行为。
#include "unity.h"void test_addition() {TEST_ASSERT_EQUAL(5, add(2, 3));
}int main() {UNITY_BEGIN();RUN_TEST(test_addition);return UNITY_END();
}
以上示例代码提供了一些常见的调试方法。选择合适的方法取决于问题的性质和复杂性。
打印调试信息
在C语言中,开发者经常使用printf语句进行调试。下面是一些在不同情境下用于调试的printf语句的示例:
-
打印变量值:
int x = 42; printf("The value of x is: %d\n", x); -
调试文件、函数、行号:
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__); -
打印字符串:
char message[] = "Hello, World!"; printf("Message: %s\n", message); -
打印指针地址:
int *ptr = NULL; printf("Pointer address: %p\n", (void*)ptr); -
条件打印:
#ifdef DEBUGprintf("Debug information\n"); #endif -
格式化输出:
double pi = 3.14159265359; printf("Value of pi: %.2f\n", pi); // 保留两位小数 -
调试标志:
#define DEBUG 1#if DEBUGprintf("Debugging is enabled\n"); #endif -
打印多个变量:
int a = 10, b = 20; printf("Values: a=%d, b=%d\n", a, b); -
打印特殊字符:
printf("This is a newline: \n"); -
查看函数执行流程:
printf("Entering function...\n"); // Function code printf("Leaving function...\n");
这些语句可以根据需要添加或组合,根据代码的结构和特定的调试需求选择合适的调试语句。在实际的开发中,有时候也会使用专门的调试工具,比如gdb(GNU Debugger)等。
GDB 调试器
GDB(GNU Debugger)是一款强大的调试器,可以用于调试C、C++等程序。以下是一些基本的 GDB 使用方法:
编译程序以便调试
在编译程序时,需要包含调试信息。使用 -g 选项告诉编译器生成调试信息:
gcc -g -o my_program my_program.c
启动 GDB
gdb ./my_program
基本命令
-
运行程序
run或者可以带参数:
run arg1 arg2 -
设置断点
在函数或者某一行设置断点:
break function_name break file_name:line_number -
执行程序直到断点
continue -
单步执行
next # 执行一行代码,不进入函数 step # 执行一行代码,进入函数 -
查看变量值
print variable_name -
查看函数调用栈
backtrace -
跳转到某一行
jump line_number -
终止程序
kill
示例
考虑以下简单的程序:
// my_program.c
#include <stdio.h>int main() {int x = 5;int y = 0;int result = x / y;printf("Result: %d\n", result);return 0;
}
使用 GDB 调试:
-
编译程序:
gcc -g -o my_program my_program.c -
启动 GDB:
gdb ./my_program -
在 GDB 中运行程序:
run程序将在除以零的地方崩溃。
-
查看变量值:
print x print y -
查看函数调用栈:
backtrace -
设置断点并运行:
break main run在
main函数的入口设置了断点,程序会在进入main函数时停下。 -
单步执行:
step逐行执行代码,可以看到在除以零的地方停下。
-
退出 GDB:
quit
这只是 GDB 的基本用法。对于更复杂的程序,可能需要更多高级的调试技术。 GDB 的命令和功能非常丰富,可以根据需要查阅 GDB 的文档。
编写单元测试
编写单元测试是一种验证代码的方法,以确保每个独立单元的功能都按预期工作。下面是一份简要的指南,帮助你编写 C 语言程序的单元测试。
选择测试框架
在 C 语言中,一些流行的测试框架包括:
- Check: Check Testing Framework
- Unity: Unity Testing Framework
- Google Test: Google Test
安装测试框架
下载并按照测试框架的文档安装框架。通常,这涉及到将测试框架的头文件和库文件添加到你的项目中。
编写测试用例
使用测试框架编写测试用例。测试用例是检查特定函数或模块的代码块,确保其行为符合预期。
示例测试用例(使用 Unity 框架):
#include "unity.h"
#include "my_functions.h" // 你的代码文件头文件void test_addition() {TEST_ASSERT_EQUAL_INT(5, add(2, 3));
}void test_subtraction() {TEST_ASSERT_EQUAL_INT(2, subtract(5, 3));
}int main() {UNITY_BEGIN();RUN_TEST(test_addition);RUN_TEST(test_subtraction);UNITY_END();return 0;
}
编写被测试的代码
在编写测试用例之前,首先编写要测试的代码。例如,你可能有一个包含两个函数的头文件 my_functions.h:
// my_functions.h
#ifndef MY_FUNCTIONS_H
#define MY_FUNCTIONS_Hint add(int a, int b);
int subtract(int a, int b);#endif
对应的实现文件 my_functions.c 可能如下所示:
// my_functions.c
#include "my_functions.h"int add(int a, int b) {return a + b;
}int subtract(int a, int b) {return a - b;
}
构建测试
构建测试时,需要链接测试框架和被测试的代码。
gcc -o my_tests my_tests.c my_functions.c -lunity
运行测试
./my_tests
测试框架将执行所有测试用例,并显示每个测试用例的结果。
分析测试结果
测试通过时,你会看到一条消息表明所有测试用例都通过。如果有测试失败,框架通常会提供详细的输出,说明哪个测试用例失败以及失败的原因。
增加测试覆盖率
编写测试用例时,尽量涵盖不同的输入情况,以确保代码的鲁棒性。覆盖率工具(例如 gcov)可以帮助你确定代码中哪些部分被测试覆盖。
持续集成
将单元测试集成到你的持续集成(CI)流程中,以确保每次代码更改都会触发测试。这有助于捕获引入错误的更改,并确保代码库的整体稳定性。
以上是一个简单的单元测试流程,实际情况可能因项目的复杂性而有所不同。
段错误产生原因
1.访问不存在的内存地址
如下面代码,ptr没有申请空间就直接拷贝数据:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char *ptr = NULL;//This is the wrong implementation:strncpy(ptr, "abc", 3);//ptr没有申请空间就直接使用//The right way://ptr = (char *)malloc(sizeof(char) * 10);//memset(ptr, 0, 10);//strncpy(ptr, "abc", 3);return 0;
}
2.访问只读的内存地址
错误做法:往字符串常量空间拷贝新的数据
#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{char *ptr = "test";strcpy(ptr, "TEST1");return 0;
}
3.访问系统保护的内存地址
如:
#include <stdio.h>int main(int argc, char *argv[])
{int *ptr = (int *)0;*ptr = 100;return 0;
}
4.栈溢出
如:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{main(argc, argv);
}
初学时两种常用的段错误调试方法
1.使用printf输出调试信息
这个是看似最简单但往往很多情况下是十分有效的调试方式,也许可以说是程序员用的最多的调试方式。简单来说,就是在程序的重要代码附近加上printf输出信息,这样可以跟踪并打印出段错误在代码中最可能出现的位置。
使用案例:
以下面这段错误代码为例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char *ptr = NULL;//This is the wrong implementation:strncpy(ptr, "abc", 3);//ptr没有申请空间就直接使用return 0;
}
可以在代码中添加printf调试信息:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{printf("line:%d\n", __LINE__); //单纯打印代码行数char *ptr = NULL;//This is the wrong implementation:printf("line:%d\n", __LINE__); //单纯打印代码行数strncpy(ptr, "abc", 3);//ptr没有申请空间就直接使用printf("line:%d\n", __LINE__); //单纯打印代码行数return 0;
}
编译运行后,会有如下输出:
line:7
line:10
Segmentation fault (core dumped)
通过日志信息,就可以看到strncpy(ptr, “abc”, 3);语句没有被执行,我们就可以根据定位到的位置来排除ptr是否是未分配空间或者分配的空间不足引发的问题
2.使用gcc和gdb
调试步骤:
1、为了能够使用gdb调试程序,在编译阶段加上-g参数,还是以下面这段错误代码为例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char *ptr = NULL;//This is the wrong implementation:strncpy(ptr, "abc", 3);//ptr没有申请空间就直接使用return 0;
}
编译代码添加-g选项
gcc -g -o test test.c
编译出test程序,然后用gdb调试:
linux@linux:~/test$ gdb ./test
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./test...done.
(gdb) r
Starting program: /home/linux/test/test Program received signal SIGSEGV, Segmentation fault.
0x0000555555554611 in main (argc=1, argv=0x7fffffffe528) at test.c:9
9 strncpy(ptr, "abc", 3);//ptr没有申请空间就直接使用
(gdb) quit
A debugging session is active.Inferior 1 [process 15829] will be killed.Quit anyway? (y or n) y
从输出看出,程序到SIGSEGV信号,触发段错误,并提示地址0x0000555555554611以及代码第9行出错。
当然,还有利用core文件和gdb配置配合调试、使用objdump以及catchsegv命令的其他更为复杂的调试手段。
相关文章:
常用的调试方法(段错误产生原因)
C 语言中常用的调试技巧和 demo C语言中常用的调试方法 打印调试信息 GDB 调试器 编写单元测试 段错误产生原因 初学时两种常用的段错误调试方法 C 语言中常用的调试技巧和 demo 当程序员进行调试时,他们通常会使用一些调试语句或技巧来帮助他们理解代码的执行过程…...
[云原生] Docker 入门指南:镜像、容器、卷和网络解析
Docker 是一种流行的容器化平台,它以其强大的功能和易用性在软件开发和部署领域广受欢迎。本文将带领您逐步探索 Docker 中的四个核心概念:镜像、容器、卷和网络。通过了解这些概念的是什么、为什么以及如何使用,您将能够更好地理解和利用 Do…...
机器学习-聚类问题
前言 聚类算法又叫做”无监督分类“,目标是通过对无标记训练样本来揭示数据的内在性质及 规律,为进一步的数据分析提供基础。 Kmeans 作为聚类算法的典型代表,Kmeans可以说是最简单的聚类算法,没有之一,那她是怎么完…...
leetcode9.回文数java解法
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数是指正序(从左向右)和倒序(从右向左&…...
图论专栏一《图的基础知识》
图论(Graph Theory)是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些实体之间的某种特定关系,用点代表实体,用连接两点的线表示两个实体间具有的…...
得帆云为玉柴打造CRM售后服务管理系统,实现服务全过程管理|基于得帆云低代码的CRM案例系列
广西玉柴机器股份有限公司 广西玉柴机器股份有限公司始建于1992年,是国内行业首家赴境外上市的中外合资企业,产品远销亚欧美非等180多个国家和地区。公司总部设在广西玉林市,下辖11家子公司,生产基地布局广西、江苏、安徽、山东等…...
智能优化算法应用:基于蝠鲼觅食算法3D无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于蝠鲼觅食算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于蝠鲼觅食算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蝠鲼觅食算法4.实验参数设定5.算法结果6.…...
vue2 以及 vue3 自定义组件使用 v-model使用默认值以及自定义事件
vue2 以及 vue3 自定义组件使用 v-model使用默认值以及自定义事件 1. vue2 自定义组件的 v-model vue2官网,自定义组件官方解释:一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件上代码代码中使用了 element-ui 子组件 使用默…...
《PCL多线程加速处理》-滤波-统计滤波
《PCL多线程加速处理》-滤波-统计滤波 一、效果展示二、实现方式三、代码一、效果展示 提升速度随着点云越多效果越明显 二、实现方式 1、原始的统计滤波实现方式 #include <pcl/filters/statistical_outlier_removal.h>pcl::PointCloud<pcl::PointXYZ...
插入排序——直接插入排序和希尔排序(C语言实现)
文章目录 前言直接插入排序基本思想特性总结代码实现 希尔排序算法思想特性总结代码实现 前言 本博客插入排序动图和希尔排序视频参考大佬java技术爱好者,如有侵权,请联系删除。 直接插入排序 基本思想 直接插入排序是一种简单的插入排序法ÿ…...
【Linux系统化学习】进程地址空间 | 虚拟地址和物理地址的关系
个人主页点击直达:小白不是程序媛 Linux专栏:Linux系统化学习 代码仓库:Gitee 目录 虚拟地址和物理地址 页表 进程地址空间 进程地址空间存在的意义 虚拟地址和物理地址 我们在学习C/C的时候肯定都见过下面这张有关于内存分布的图片&a…...
Navicat 技术指引 | 适用于 GaussDB 分布式的模型功能
Navicat Premium(16.3.3 Windows 版或以上)正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能,还提供强大的高阶功能(如模型、结…...
四十五、Redis主从
目录 1、数据同步原理 (1)全量同步 (2)增量同步 (3)优化Redis主从集群 (4)什么时候执行全量同步 (5)什么时候执行增量同步 2、流程 1、数据同步原理 &…...
Spring源码学习一
IOC容器概述 ApplicationContext接口相当于负责bean的初始化、配置和组装的IoC容器. Spring为ApplicationContext提供了一些开箱即用的实现, 独立的应用可以使用 ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext,web应用在web.xml配置监 听&am…...
小红书种草和抖音传播区别是什么?
目前品牌较为关注的2大平台小红书和抖音,两者在种草方面存在一些明显的区别。本次就存量竞争、种草形式和种草策略这三个方面入手进行分析,今天和大家分享下小红书种草和抖音传播区别是什么? 一、存量竞争下的2大平台 2个都是属于存量竞争下的…...
论文阅读《Parameterized Cost Volume for Stereo Matching》
论文地址:https://openaccess.thecvf.com/content/ICCV2023/papers/Zeng_Parameterized_Cost_Volume_for_Stereo_Matching_ICCV_2023_paper.pdf 源码地址:https://github.com/jiaxiZeng/Parameterized-Cost-Volume-for-Stereo-Matching 概述 现有的立体匹…...
解决nuxt3中vue3生命周期钩子onMounted不执行的问题
看到这篇文章算你运气好!因为只有我才能给你答案!看到就赚到,这就是缘分 因为vue3迁移nuxt3是一个非常困难和痛苦的过程,中间会有各种报错,各种不兼容,各种乱七八糟但是你又找不到答案的问题。 而且你一定…...
Win32 HIWORD和LOWORD宏学习
HIWORD是High Word的缩写,作用是取得某个4字节变量(即32位的值)在内存中处于高位的两个字节,即一个word长的数据; LOWORD是Low Word的缩写,作用是取得某个4字节变量(即32位的值)在内存中处于低位的两个字节,即一个word长的数据; Win32编程常用; Win32窗口编程中,收到 WM_S…...
Axure官方软件安装、汉化保姆级教程(带官方资源下载)
1.下载汉化包 百度云链接:https://pan.baidu.com/s/1lluobjjBZvitASMt8e0A_w?pwdjqxn 提取码: jqxn 2.解压压缩包 3.安装Axure 进行安装 点击next 打勾,然后next, 默认是c盘,修改成自己的文件夹(不要什么都放c盘里…...
qt-C++笔记之addAction和addMenu的区别以及QAction的使用场景
qt-C笔记之addAction和addMenu的区别以及QAction的使用场景 code review! 文章目录 qt-C笔记之addAction和addMenu的区别以及QAction的使用场景1.QMenu和QMenuBar的关系与区别2.addMenu和addAction的使用场景区别3.将QAction的信号连接到槽函数4.QAction的使用场景5.将例1修改…...
jsontop.cn使用全攻略:免费无广告的在线工具站,电脑手机通用
你是否经常遇到这些问题: 拿到一堆杂乱 JSON 看不懂,想格式化却不会?需要转 Base64、算 MD5、转时间戳,却要装复杂软件?想测试正则、预览 HTML,还要搭环境、找插件?网上工具全是广告࿰…...
线激光手眼标定里,欧拉角和四元数到底怎么选?一个案例讲清机器人姿态的‘坑’
线激光手眼标定中欧拉角与四元数的抉择:从理论误区到工程实践 在机器人视觉系统中,手眼标定是连接感知与执行的关键桥梁。当激光传感器安装在机械臂末端时,如何准确描述传感器坐标系与机器人坐标系之间的姿态关系,直接决定了后续视…...
Sentaurus实战解析:SiC NMOS仿真中的关键参数设置与优化
1. SiC NMOS仿真基础与Sentaurus环境搭建 碳化硅(SiC)功率器件因其优异的耐高温、高压特性,正在电力电子领域掀起一场革命。作为第三代半导体材料的代表,SiC的临界击穿电场强度达到硅的10倍,热导率更是硅的3倍。但在实际器件开发中࿰…...
嵌入式开发中开源组件的战略价值与使用策略
1. 嵌入式开发中开源组件的战略价值在当今嵌入式系统开发领域,开源软件已经成为不可或缺的战略资源。作为一名从业十余年的嵌入式工程师,我亲眼见证了开源生态如何彻底改变这个行业的开发模式。从早期的闭源商业解决方案主导,到现在几乎每个项…...
给客户发固件,别再傻傻传源码了!手把手教你用ESP32 Download Tool烧录PlatformIO生成的bin文件
专业级ESP32固件交付方案:从PlatformIO编译到客户安全烧录全流程 当我们需要将开发完成的ESP32固件交付给客户时,直接发送源代码往往不是最佳选择。这不仅涉及知识产权保护问题,还可能因为客户缺乏开发环境而导致沟通成本激增。本文将详细介绍…...
Vita3K模拟器终极指南:免费跨平台畅玩PSVita游戏
Vita3K模拟器终极指南:免费跨平台畅玩PSVita游戏 【免费下载链接】Vita3K Experimental PlayStation Vita emulator 项目地址: https://gitcode.com/gh_mirrors/vi/Vita3K 想要在电脑上重温《女神异闻录4黄金版》的经典剧情,或是体验《A Rose in …...
AsyncServoLib:嵌入式非阻塞舵机控制库详解
1. AsyncServoLib:面向嵌入式实时系统的非阻塞舵机控制库深度解析1.1 设计动机与工程痛点在基于Arduino或兼容MCU(如STM32F103、ESP32)的机器人、云台、机械臂等实时控制系统中,舵机(Servo)的精确运动控制是…...
用OpenMV和STM32F765VI做个追球小车:从硬件接线到PID调参的保姆级避坑指南
从零打造智能追球小车:OpenMV与STM32F765VI实战全解析 1. 项目构思与硬件选型 第一次尝试用视觉识别做智能小车时,我对着满桌子的开发板和传感器发愁——到底哪些组合才能既省钱又高效?经过三个版本的迭代,这套基于STM32F765VI和O…...
爱毕业aibye发布六大权威平台排名,智能改写与高效写作功能一键完成,科研必备的AI工具
工具名称 核心功能 特色优势 Aibiye 论文生成降AI率 全学科覆盖、仿写优化、自动图表生成 Aicheck AI检测文献综述辅助 精准查新、3分钟高效成文 GPT学术版 润色/翻译/代码解释 多模型协同、PDF深度解析 摆平论文 大纲生成降重改写 三步出稿、本硕博通用 QuillB…...
嵌入式设备参数存储方案设计与优化
嵌入式设备参数存储方案设计与实现1. 项目概述在嵌入式系统开发中,参数存储是一个基础但至关重要的功能模块。合理的参数存储方案直接影响产品的可靠性、可维护性和升级扩展能力。本文将深入分析嵌入式设备中常见的参数存储方案,重点解决结构体存储方式存…...
