常用的调试方法(段错误产生原因)

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语言中,常用的调试方法包括打印调试信息、使用调试器和编写单元测试等。下面是一些用于调试的示例代码:

  1. 打印调试信息
#include <stdio.h>// 在代码中插入打印语句
void example_function() {printf("Debug: This is a debug message.\n");// ... 其他代码 ...
}
  1. 条件打印
#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 宏来控制是否打印调试信息。

  1. 使用 assert 断言
#include <assert.h>void example_function(int x) {assert(x > 0 && "x must be greater than 0");// ... 其他代码 ...
}

assert 宏用于检查一个表达式是否为真,如果为假,则触发一个断言失败。

  1. 使用调试器(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 和其他命令来查看调用栈和变量值。

  1. 编写单元测试

使用测试框架进行单元测试,例如 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语句的示例:

  1. 打印变量值

    int x = 42;
    printf("The value of x is: %d\n", x);
    
  2. 调试文件、函数、行号

    printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
    
  3. 打印字符串

    char message[] = "Hello, World!";
    printf("Message: %s\n", message);
    
  4. 打印指针地址

    int *ptr = NULL;
    printf("Pointer address: %p\n", (void*)ptr);
    
  5. 条件打印

    #ifdef DEBUGprintf("Debug information\n");
    #endif
    
  6. 格式化输出

    double pi = 3.14159265359;
    printf("Value of pi: %.2f\n", pi);  // 保留两位小数
    
  7. 调试标志

    #define DEBUG 1#if DEBUGprintf("Debugging is enabled\n");
    #endif
    
  8. 打印多个变量

    int a = 10, b = 20;
    printf("Values: a=%d, b=%d\n", a, b);
    
  9. 打印特殊字符

    printf("This is a newline: \n");
    
  10. 查看函数执行流程

    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

基本命令

  1. 运行程序

    run
    

    或者可以带参数:

    run arg1 arg2
    
  2. 设置断点

    在函数或者某一行设置断点:

    break function_name
    break file_name:line_number
    
  3. 执行程序直到断点

    continue
    
  4. 单步执行

    next      # 执行一行代码,不进入函数
    step      # 执行一行代码,进入函数
    
  5. 查看变量值

    print variable_name
    
  6. 查看函数调用栈

    backtrace
    
  7. 跳转到某一行

    jump line_number
    
  8. 终止程序

    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 调试:

  1. 编译程序:

    gcc -g -o my_program my_program.c
    
  2. 启动 GDB:

    gdb ./my_program
    
  3. 在 GDB 中运行程序:

    run
    

    程序将在除以零的地方崩溃。

  4. 查看变量值:

    print x
    print y
    
  5. 查看函数调用栈:

    backtrace
    
  6. 设置断点并运行:

    break main
    run
    

    main 函数的入口设置了断点,程序会在进入 main 函数时停下。

  7. 单步执行:

    step
    

    逐行执行代码,可以看到在除以零的地方停下。

  8. 退出 GDB:

    quit
    

这只是 GDB 的基本用法。对于更复杂的程序,可能需要更多高级的调试技术。 GDB 的命令和功能非常丰富,可以根据需要查阅 GDB 的文档。

编写单元测试

编写单元测试是一种验证代码的方法,以确保每个独立单元的功能都按预期工作。下面是一份简要的指南,帮助你编写 C 语言程序的单元测试。

选择测试框架

在 C 语言中,一些流行的测试框架包括:

  1. Check: Check Testing Framework
  2. Unity: Unity Testing Framework
  3. 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命令的其他更为复杂的调试手段。

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

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

相关文章

跟风申请香港优才计划的人,很容易进入骗局和被割韭菜!

跟风申请香港优才计划的人&#xff0c;很容易进入骗局和被割韭菜&#xff01; 不得不承认一个事实就是&#xff0c;越来越多内地人正在抢占申请香港身份的份额&#xff01;就因为这个项目门槛低、投入低&#xff0c;简单来说就是多一层身份&#xff0c;多一层福利保障。 从目前…

Pyqt python 界面代码

1、界面拖动代码 # 拖动 def mousePressEvent(self, event):if event.button() QtCore.Qt.LeftButton and self.isMaximized() False:self.m_flag Trueself.m_Position event.globalPos() - self.pos() # 获取鼠标相对窗口的位置event.accept()self.setCursor(QtGui.QCur…

go - 计算CIDR的主机数量

在网络中&#xff0c;CIDR /32 表示该地址只能用作网络地址本身&#xff0c;不能分配给任何主机。因此&#xff0c;在计算主机数量时&#xff0c;应将 CIDR 地址按照其位掩码长度进行区分。对于 /32 子网掩码&#xff0c;主机数量总是为 1&#xff0c;而不是 -1。 以下是修正后…

二.ts基础类型

ts的基础类型包含js的基础类型和ts独有的基础类型 我们一般使用[let | const | val] 变量&#xff1a;类型 值的方式声明一个带有类型的变量 stringlet val:string 1numberlet val:number 1boolearnlet val:boolaern falseundefindlet val:undefind undefindnulllet val:nul…

Kubernetes实战(九)-kubeadm安装k8s集群

1 环境准备 1.1 主机信息 iphostname10.220.43.203master10.220.43.204node1 1.2 系统信息 $ cat /etc/redhat-release Alibaba Cloud Linux (Aliyun Linux) release 2.1903 LTS (Hunting Beagle) 2 部署准备 master/与slave主机均需要设置。 2.1 设置主机名 # master h…

成都工业学院Web技术基础(WEB)实验五:CSS3动画制作

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

使用PyTorch II的新特性加快LLM推理速度

Pytorch团队提出了一种纯粹通过PyTorch新特性在的自下而上的优化LLM方法&#xff0c;包括: Torch.compile: PyTorch模型的编译器 GPU量化:通过降低精度操作来加速模型 推测解码:使用一个小的“草稿”模型来加速llm来预测一个大的“目标”模型的输出 张量并行:通过在多个设备…

成都工业学院Web技术基础(WEB)实验三:CSS字体等属性使用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

Oracle 慢查询排查步骤

1. Oracle 慢查询排查步骤 1.1. 前言 记录一次 Oracle 慢查询的排查过程 , 便于以后直接使用。 看了一些文档 , Oracle 中优化的方案和 Mysql 基本上是一致的 , 通常包括一下几个方向 : 基准测试 (吞吐量): 包括 Oracle 本身吞吐量和磁盘 I/O 吞吐量 硬件分析 (资源情况): 包…

rails3 row sql example

refer: https://stackoverflow.com/questions/14824453/rails-raw-sql-example 搜索怎么在Rails3 使用row sql&#xff0c; 打开上面的链接&#xff0c;可以找到这样的答案&#xff0c;如下图&#xff1a; sql "Select * from ... your sql query here" records_ar…

Nginx的server层外层的常见配置语句的解读

有下面的Nginx配置: worker_processes auto; worker_rlimit_nofile 51200;events {use epoll;worker_connections 51200;multi_accept on; }http {include mime.types;default_type application/octet-stream;server_names_hash_bucket_size 512;client_max_body_size 50m;cli…

推荐4个优秀的 Python 时间序列分析库

时间序列分析在金融和医疗保健等领域至关重要&#xff0c;在这些领域&#xff0c;理解随时间变化的数据模式至关重要。在本文中&#xff0c;我们将介绍四个主要的Python库——statmodels、tslearn、tssearch和tsfresh——每个库都针对时间序列分析的不同方面进行了定制。这些库…

最长子字符串的长度 (一) - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 给你一个字符串 s&#xff0c;字符串s首尾相连成一个环形&#xff0c;请你在环中找出字符出现了偶数次最长子字符串的长度。 输入描述 输入是一串小写字母组成的…

VS2015编译GDAL3.2.0+opencl+C#

参考借鉴https://www.cnblogs.com/litou/p/15004877.html 参考借鉴https://www.cnblogs.com/xiaowangba/p/6313903.html 参考借鉴gdal、proj、geos、sqlite等在VS2015下编译和配置_vs2015编译sqlite3-CSDN博客 参考借鉴Windows下GDAL3.1.2编译 (VS2015)_gdal windows编译-CS…

字符串String

字符串字面量 let s "hello";变量s属于字符串字面量&#xff0c;它们属于硬编码进程序的字符串值&#xff0c;属于不可变的类型。但并不是所有字符串的值都能够在编写代码时确定。 String类型 String类型会在堆上分配到自己需要的存储空间&#xff0c;所以它能够…

Nacos简介

1.Nacos简介 1.1.为什么叫Nacos&#xff1f; 前四个字母分别为Naming和Configuration的前两个字母&#xff0c;最后的s为service。 1.2.Nacos是什么&#xff1f; nacos是第二代微服务SpringCloudAlibaba开源的一款注册中心和分布式配置中心组件&#xff0c;其功能上为第一代微…

声明式数据建模、定义简单易懂:下一代 ORM 助你效率倍增 | 开源日报 No.102

prisma/prisma Stars: 34.0k License: Apache-2.0 Prisma 是一个下一代 ORM&#xff0c;包括以下工具&#xff1a; Prisma Client&#xff1a;为 Node.js 和 TypeScript 自动生成的类型安全查询构建器Prisma Migrate&#xff1a;声明式数据建模和迁移系统Prisma Studio&#…

山人求道篇:五、交易中的大道至简与奥卡姆剃刀

量化的剃刀不是减去因子、减去代码&#xff0c;而是减去冗余信息量 市面上有的策略对各品种设置了不同参数&#xff0c;每个品种进场不一样就算了&#xff0c;出场参数还不一样&#xff0c;那你说他到底赚的是什么利润呢&#xff1f; 他自己也不知道&#xff0c;主要目的是为…

鸿蒙应用开发(二)环境搭建

开发流程 IDE下载 首先下载HUAWEI DevEco Studio&#xff0c;介绍首次启动DevEco Studio的配置向导&#xff1a; 运行已安装的DevEco Studio&#xff0c;首次使用&#xff0c;请选择Do not import settings&#xff0c;单击OK。安装Node.js与ohpm。node.js 是基于 V8 引擎构…

JavaScript常用技巧专题二

文章目录 一、前言二、生成随机字符串三、转义HTML特殊字符四、单词首字母大写五、将字符串转换为小驼峰六、删除数组中的重复值七、移除数组中的假值八、获取两个数字之间的随机数九、将数字截断到固定的小数点十、日期10.1、计算两个日期之间天数10.2、从日期中获取是一年中的…