问题描述
某个项目中,有一个内部函数需要执行某一个自己写的字符串分割函数,执行时出现了段错误。
问题分析
检查之后发现是使用了strtok,会修改原字符串(插入\0)且线程不安全(使用静态变量存储信息),改为strtok_r后应该就没问题了。
于是我修改了分割字符的函数的实现,然后编写了单元测试的测试用例。
但是问题依然存在?一般来说段错误这终问题会出现在野指针等内存问题上,我直接使用了valgrind
valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./your_program
用例竟然通过了!而且也没有任何内存问题?!吓得我揉了揉眼睛,再在valgrind之外运行一次,还是不对?这真的邪门了。
honest@LAPTOP-7VH4BO1M:/mnt/c/Users/HONEST/Desktop/emsa/micoservice/output/test/unit_test$ valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./unit_test_split_str
==60916== Memcheck, a memory error detector
==60916== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==60916== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==60916== Command: ./unit_test_split_str
==60916==
=== 测试基本分割 ===
=== 1 ===
=== 2 ===
=== 3 ===
基本分割测试通过
=== 测试多个空格 ===
argv[0] = "/bin/ls"
argv[1] = "-l"
argv[2] = "/tmp"
argv[final] = NULL
多个空格测试通过
=== 测试制表符和换行符 ===
argv[0] = "/bin/grep"
argv[1] = "-r"
argv[2] = "test"
argv[3] = "/home"
argv[final] = NULL
制表符和换行符测试通过
=== 测试空字符串 ===
argv[final] = NULL
空字符串测试通过
=== 测试尾随空格 ===
argv[0] = "/usr/bin/ps"
argv[1] = "aux"
argv[final] = NULL
尾随空格测试通过
=== 测试单个命令 ===
argv[0] = "/bin/pwd"
argv[final] = NULL
单个命令测试通过
所有测试用例通过!
==60916==
==60916== HEAP SUMMARY:
==60916== in use at exit: 0 bytes in 0 blocks
==60916== total heap usage: 32 allocs, 32 frees, 1,455 bytes allocated
==60916==
==60916== All heap blocks were freed -- no leaks are possible
==60916==
==60916== For lists of detected and suppressed errors, rerun with: -s
honest@LAPTOP-7VH4BO1M:/mnt/c/Users/HONEST/Desktop/emsa/micoservice/output/test/unit_test$ ls
unit_test_node_exec unit_test_split_str
honest@LAPTOP-7VH4BO1M:/mnt/c/Users/HONEST/Desktop/emsa/micoservice/output/test/unit_test$ ./unit_test_split_str
=== 测试基本分割 ===
=== 1 ===
=== 2 ===
Segmentation fault (core dumped)
没办法,上GDB吧。GDB调试发现字符串分割的很顺利,查看内存也没有任何问题。 唯一的疑点在于函数返回之后,返回的分割好的字符数组的地址长度发生了变化,变短了
0x5555555606d0 -> 0x555606d0
这种行为一般是定义的返回值数据类型不匹配导致的截断,截断之后成了一个32位的数据类型(int?)。然后再被用在指针上(64位),发生了0填充,最后访问的实际是0x00000000555606d0。这确实得访问非法内存段错误。
问题解决
我仔细核对了一下函数的定义和用例的返回类型,没问题啊,都是int*。有什么地方的头文件重定义了这个函数?也不对啊我是一个内部文件都没有头…
等一下,好像找到问题了。我的内部函数所在的.c被编成了一个库,但是它没有向外暴露接口,我的用例使用时没有extern,也没有引用对应的头文件(库里也没有)。也就说它实际上没有获得一个明确的函数声明?但是他也运行起来了啊。
加上extern后问题解决。
问题的成因在没有明确声明函数的定义时,较低版本的C编译器会允许出现隐式的声明,c会自动把返回值指定为int。在我这里应该是一个地址,发生了截断后零填充,最后的返回值不是预想的地址,地址非法出现了段错误。