场景描述:
linux通讯服务器工作时,接收第一个客户端消息能正常接收并处理完成;接收第二个客户端消息时就崩溃
1. 发现问题:
进程崩溃一般会产生core文件;
查看coredump文件:gdb gameserver /corefile/core-xxx;
[coredump文件使用的相关命令:
ulimit -a 查看coredump文件是否允许创建 若结果为0,则表示关闭了此功能,不会生成core文件。
ulimit -c unlimited 对创建coredump文件数量不限制
cat /proc/sys/kernel/core_pattern 查看core文件保存位置
]
报错是试图访问一个内容被破坏的map:
std::map with 2 elements<error reading variable: Cannot access memory at address 0x204c42545f524569>
2.查找出错代码:
这个msgMap的定义是:
调用是:
现在知道是msgMap被破坏,但是不知道为什么被破坏,我们只知道第一次处理消息后被破坏。
所以进行单步调试,找到msgMap刚被破坏的那一行代码,我是执行一行,就打印一下 msgMap。
锁定了破坏msgMap的那行代码:
因为当我单步执行到 MYSQL_RES* res = xxx; 这行时【这一行刚到,还未真正执行】,发现msgMap已经被破坏了。
3.分析原因:
初看这只是一行很普通的拼接字符串的代码,但是由于SQL_CONF也是个map结构,所以我一开始的注意力就放在它上面了:
猜测1: 在map保存的函数指针调用内再使用其他map会破坏原map结构。
然后我写了个测试程序:
#include <iostream>
#include <map>
using std::map;
using std::cout;
using std::endl;
#define index_fun 2
#define index_value 2
typedef void (*_handler)(int);
void test_fun(int a);
static map<int,_handler> fun_map = {
{index_fun , &test_fun}
};
static map<int,int> value_map = {
{index_value , 3}
};
int main()
{
fun_map[2](4);
fun_map[2](4);
return 0;
}
void test_fun(int a)
{
cout<<" test_fun begin "<<endl;
cout<<" a = "<<a<<endl;
cout<<" b = "<<value_map[2]<<endl;
cout<<" test_fun end "<<endl;
}
测试结果:
发现并没有异常发生!!
那说明猜测1 不成立。
猜测2【逐行检查代码 发现的】:拼接后存放的char* sql;污染了msgMap的地址, 我没有给它提前分配内存,依赖sprintf的实现。
查看 sprintf 函数的官方说明,给定的存储buff一定要足够容纳拼接后的字符串,看来sprintf 里面是没有再做内存申请的。
4.解决问题:
出问题代码的上一句 char* sql;
修改为 char sql[100] = {0,}; 之后重新编译代码,程序不再崩溃!!
5.总结:
声明buff类指针的时候一定得思考下需不需要提前分配内存。
6.延伸思考和讨论:
- char* sql 我没有分配内存的时候,它为何每次都是指向的 static msgMap的位置,至少是很接近,可以进一步了解下c++的内存模型,也可能依赖于操作系统,编译器的具体实现。
- char* sql 的默认指向,是否是受到函数调用堆栈的影响呢。
- const map不支持中括号操作符,原因是:中括号操作符在无法找到Key时,会插入新的Key,Value的pair,而const不允许此类操作。[更可怕的是 map插入默认值后,不删除 ,不仅占据空间,还污染数据。]
- 或许我们使用 snprintf 更合适。