linux应用层c语言开发assert函数用法以及踩坑导致应用程序崩溃段错误总结

本文详细介绍了如何在C语言中使用assert函数检查数组索引是否越界和函数的前置条件,并探讨了NDEBUG编译选项对assert行为的影响,以及一个实际项目中遇到的由于不规范assert使用导致的问题案例。
摘要由CSDN通过智能技术生成

介绍

assert函数是C语言中的一个调试宏,用于在程序中进行断言检查。它接受一个表达式作为参数,如果表达式为假(即为0),则会触发断言失败,并输出错误信息。

注意,在发布版本的程序中,assert通常被禁用或移除,因为它主要用于开发和调试阶段。

例子1 检查数组索引是否越界

代码

#include <stdio.h>
#include <assert.h>

#define SIZE 5

int main()
{
    int arr[SIZE] = {1, 2, 3, 4, 5};
    int index = 5;
    assert(index >= 0 && index < SIZE);
    printf("The value at index %d is: %d\n", index, arr[index]);
    return 0;
}

Makefile

TARGET := assert_test

CROSS_COMPILE = arm-linux-gnueabihf-

STRIP = $(CROSS_COMPILE)strip
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar

CFLAGS += -fPIC -O2 -Wall -std=gnu99
CFLAGS += -DLinux -D_GNU_SOURCE
CFLAGS += -g -rdynamic -funwind-tables -ffunction-sections

OBJS += main.o  

all: $(TARGET)
$(TARGET):$(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)
    
clean:
	-rm $(OBJS) $(TARGET) -f

结果

# ./assert_test 
assert_test: main.c:9: main: Assertion `index >= 0 && index < 5' failed.
Aborted

分析

代码中定义了一个大小为5的整数数组,然后试图访问索引为5的元素。在C语言中,数组的索引是从0开始的,所以一个大小为5的数组的有效索引范围是0到4。因此,索引5是越界的。

使用assert语句来确保索引的有效性,即它必须大于等于0且小于数组的大小(这里是5)。在以上代码中,index的值是5,这不满足assert的条件(index >= 0 && index < SIZE),因此assert触发了一个断言失败,并终止了程序。

断言失败的错误消息输出告诉我们在哪个文件(main.c)、哪一行(9)、哪个函数(main)中的哪个断言失败了,以及失败的断言是什么(index >= 0 && index < 5)。

例子2 检查函数的前置条件

代码

#include <stdio.h>
#include <assert.h>

int divide(int dividend, int divisor)
{
    assert(divisor != 0);
    return dividend / divisor;
}

int main()
{
    int result = divide(10, 0);
    printf("Result: %d\n", result);
    return 0;
}

Makeflie同例子一

结果

# ./assert_test 
assert_test: main.c:6: divide: Assertion `divisor != 0' failed.
Aborted

分析

代码中定义了一个名为divide的函数,该函数接受两个整数参数dividend(被除数)和divisor(除数),并返回他们的商。使用assert语句来确保除数不为零。

main函数中,试图用0作为除数调用divide函数。这显然不满足divide函数内部的assert条件(divisor != 0),因此assert触发了一个断言失败,并终止了程序。

断言失败的错误消息输出告诉我们在哪个文件(main.c)、哪一行(6)、哪个函数(divide)中的哪个断言失败了,以及失败的断言是什么(divisor != 0)。

踩坑

在实际项目中,发现一个奇怪的问题:
发布版本时,测试人员执行某一步骤必现死机,但是开发人员自己编译出的固件则无此问题。

排查发现固件代表使用的编译脚本和开发人员使用的编译脚本有一些不同,其中,和此问题有关系的是:

编译版本的脚本定义了 NDEBUG 选项
此选项的作用为控制assert()的行为,开启了此选项后,代码中加了这句 define assert(x) ((void)0),导致assert内的内容不会被执行。

而排查发现某一模块的开发人员使用assert不规范,如下所示:

assert(function_a(a,b) != 0);

他将关键流程性的函数放在assert里面,如上所述,定义了 NDEBUG 选项后,assert内的关键函数不会被执行,后面的函数产生严重错误导致死机。

例子

这个例子演示了当NDEBUG被定义时,关键函数未执行导致段错误的情况。
假设我们有一个函数initialize_array,它负责初始化一个全局的数组。这个函数被放在assert中调用,因此在NDEBUG被定义时,它不会被执行。然后,我们尝试访问这个数组的一个元素,这将导致段错误,因为数组没有被初始化。

代码

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int* array;

void initialize_array(int size)
{
    printf("Initializing array\n");
    array = malloc(size * sizeof(int));
    for(int i = 0; i < size; i++)
    {
        array[i] = i;
    }
}

int main()
{
    int size = 10;
    assert((initialize_array(size), size > 0));
    printf("array[0]: %d\n", array[0]);
    free(array);
    printf("Program execution success!\n");
    return 0;
}

Makefile

TARGET := assert_test

CROSS_COMPILE = arm-linux-gnueabihf-

STRIP = $(CROSS_COMPILE)strip
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar

CFLAGS += -fPIC -O2 -Wall -std=gnu99
CFLAGS += -DLinux -D_GNU_SOURCE
CFLAGS += -g -rdynamic -funwind-tables -ffunction-sections

OBJS += main.o  

all: $(TARGET)
$(TARGET):$(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)
    
clean:
	-rm $(OBJS) $(TARGET) -f

结果

# ./assert_test 
Initializing array
array[0]: 0
Program execution success!

可以看到正常执行结束

修改Makefile,加入NDEBUG

TARGET := assert_test

CROSS_COMPILE = arm-linux-gnueabihf-

STRIP = $(CROSS_COMPILE)strip
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar

CFLAGS += -fPIC -O2 -Wall -std=gnu99
CFLAGS += -DLinux -D_GNU_SOURCE
CFLAGS += -g -rdynamic -funwind-tables -ffunction-sections
CFLAGS += -DNDEBUG # 加入NDEBUG

OBJS += main.o  

all: $(TARGET)
$(TARGET):$(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)
    
clean:
	-rm $(OBJS) $(TARGET) -f

结果

# ./assert_test 
Segmentation fault

出现了段错误

分析

通过gdb命令查看coredump文件,可以看到程序挂在main.c第21行

Core was generated by `./assert_test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0001049c in main () at main.c:21

对应这行代码,原因就是initialize_array函数没执行,导致array未定义,访问到了非法内存。

printf("array[0]: %d\n", array[0]);

参考链接

NDEBUG 、DEBUG宏 和assert() 的用法

  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值