记一次难忘的排错debug经历(找了5天左右)(涉及内存覆盖)
strcpy和memcpy都没有处理内存覆盖问题。
函数描述
The memcpy function copies count bytes of src to dest. If the source and destination overlap, this function does not ensure that the original source bytes in the overlapping region are copied before being overwritten. Use memmove to handle overlapping regions.
memcpy函数将src的字节数复制到dest。如果源和目标重叠,这个函数不能确保重叠区域的原始源字节在被覆盖之前被复制。
这里已经提到了内存覆盖的问题,而在C语言却并没有对这种现象做相关的规定或检查,也就是说对于这种现象C语言是缺省。后边会详细分析如何处理在字符串拷贝函数中内存重叠的问题。
---------------------
memcpy和memmove()都是C语言中的库函数,在头文件string.h中,作用是拷贝一定长度的内存的内容,原型分别如下:
void *memcpy(void *dst, const void *src, size_t count);
void *memmove(void *dst, const void *src, size_t count);
他们的作用是一样的,唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。
一、memcpy函数
Memcpy原型:
void *memcpy(void *dest, const void *src, size_t n);
假如考虑dst和src内存重叠的情况,strcpy该怎么实现
char s[10]="hello";
strcpy(s, s+1); //应返回ello,
//strcpy(s+1, s); //应返回hhello,但实际会报错,因为dst与src重叠了,把'\0'覆盖了
所谓重叠,就是src未处理的部分已经被dst给覆盖了,只有一种情况:src<=dst<=src+strlen(src)
char *my_memcpy(char *dst, const char* src, int cnt)
{
assert(dst != NULL && src != NULL);
char *ret = dst;
if (dst >= src && dst <= src+cnt-1) //内存重叠,从高地址开始复制
{
dst = dst+cnt-1;
src = src+cnt-1;
while (cnt--)
*dst-- = *src--;
}
else //正常情况,从低地址开始复制
{
while (cnt--)
*dst++ = *src++;
}
return ret;
}

https://blog.csdn.net/Teemo_king/article/details/77655550
https://www.cnblogs.com/17bdw/p/6133315.html
https://blog.csdn.net/wh_0727/article/details/80294107
遇到的bug:
打印日志:
结算成绩时可以看到有时候玩家下注的值
m_lPlayBet[i][j]
这个值,有时候为负数???
按照道理来讲,玩家下注数不可能为负数,到底什么时候导致为负数的呢?
另外还有一点规律:当出现异常为负数时 总是前面的玩家为负数。
也就是 m_lPlayBet 索引靠前的玩家为负数。
玩家下注m_lPlayBet 这个东西搜索所有代码修改的只有两个地方:
class A{
...
//下注数
protected:
LONGLONG m_lAllBet[AREA_MAX]; //总下注
LONGLONG m_lPlayBet[GAME_PLAYER][AREA_MAX]; //玩家下注
...
}
一个是赋值为0,一个是下注 加上下注数。
m_lPlayBet[wChairID][cbBetArea] += lBetScore; (lBetScore 是正数,不可能是负数)
怎么m_lPlayBet这个值怎么为负数呢?
查了几天发现在这个地方可疑:
//游戏结束
bool CTableFrameSink::OnEventGameConclude(WORD wChairID, IServerUserItem * pIServerUserItem, BYTE cbReason)
{
...
case GER_USER_LEAVE: //用户离开
{
if (wChairID == m_wCurSuperRobBankerUser)
{
m_wCurSuperRobBankerUser = INVALID_CHAIR; CMD_S_CurSuperRobLeave CurSuperRobLeave;
ZeroMemory(&CurSuperRobLeave, sizeof(CurSuperRobLeave)); //设置变量
CurSuperRobLeave.wCurSuperRobBankerUser = m_wCurSuperRobBankerUser; //发送消息
m_pITableFrame->SendTableData(INVALID_CHAIR, SUB_S_CURSUPERROB_LEAVE, &CurSuperRobLeave, sizeof(CurSuperRobLeave));
m_pITableFrame->SendLookonData(INVALID_CHAIR, SUB_S_CURSUPERROB_LEAVE, &CurSuperRobLeave, sizeof(CurSuperRobLeave));
} //闲家判断
if (m_wCurrentBanker != wChairID)
{
//变量定义
LONGLONG lRevenue = ; Debug("oneventgameconclude: cbReason %d", cbReason);
Debug("oneventgameconclude: gameid:%d", pIServerUserItem->GetGameID()); //写入积分
if (m_pITableFrame->GetGameStatus() != GAME_SCENE_END)
{
for (WORD wAreaIndex = AREA_XIAN; wAreaIndex <= AREA_ZHUANG_DUI; ++wAreaIndex)
{
Debug("oneventgameconclude wAreaIndex:%d", wAreaIndex); if (m_lPlayBet[wChairID][wAreaIndex] != )
{
CMD_S_PlaceBetFail PlaceBetFail;
ZeroMemory(&PlaceBetFail, sizeof(PlaceBetFail));
PlaceBetFail.lBetArea = (BYTE)wAreaIndex;
PlaceBetFail.lPlaceScore = m_lPlayBet[wChairID][wAreaIndex];
PlaceBetFail.wPlaceUser = wChairID; //游戏玩家
for (WORD i = ; i < GAME_PLAYER; ++i)
{
IServerUserItem *pIServerUserItem = m_pITableFrame->GetTableUserItem(i);
if (pIServerUserItem == NULL)
continue; m_pITableFrame->SendTableData(i, SUB_S_PLACE_JETTON_FAIL, &PlaceBetFail, sizeof(PlaceBetFail));
} m_lAllBet[wAreaIndex] -= m_lPlayBet[wChairID][wAreaIndex];
m_lPlayBet[wChairID][wAreaIndex] = ; Debug("oneventgameconclude: m_lPlayBet[wChairID][wAreaIndex]:%d", m_lPlayBet[wChairID][wAreaIndex]);
Debug("oneventgameconclude: m_lPlayBet[wChairID][wAreaIndex] %d", m_lPlayBet[wChairID][wAreaIndex]); }
}
}
else {
....
}
return true
看这行:
for (WORD wAreaIndex = AREA_XIAN; wAreaIndex <= AREA_ZHUANG_DUI; ++wAreaIndex)
实际上 玩家的下注区域只有3个:从AREA_XIAN到
AREA_ZHUANG(值为3)
AREA_ZHUANG_DUI
#define AREA_MAX 3 //最大区域
这里for循环截止到7,导致访问
m_lAllBet 和m_lPlayBet
越界。
m_lAllBet[wAreaIndex] -= m_lPlayBet[wChairID][wAreaIndex]; m_lPlayBet[wChairID][wAreaIndex] = 0;
protected:
LONGLONG m_lAllBet[AREA_MAX]; //总下注
LONGLONG m_lPlayBet[GAME_PLAYER][AREA_MAX]; //玩家下注
...
这2个成员变量是相连的。
m_lAllBet减去玩家的下注数,这个wAreaIndex越界,导致实际上修改的是
m_lPlayBet。
所以:
m_lPlayBet为负数就不足为齐了。 另外,这个也解释了之前发现异常时出现的规律: 当出现异常为负数时 总是前面的玩家为负数。 总结:问题产生的原因就是访问变量越界导致修改的实际上是下一个变量。
记一次难忘的排错debug经历(找了5天左右)(涉及内存覆盖)的更多相关文章
- 最难忘的Bug调试经历
摘要:目前,著名的社区问答网站Quora上出现一个很火的讨论:你调试过最难的Bug是什么?大家纷纷留言,把自己最痛苦的一次调试经验写下来. 相信每位程序员都有过一段不堪回首地Bug调试经历,程序员一听 ...
- debug.keystare找不到的解决办法[转]
重装系统之后,丢失了debug.keystore,找了很久都没有找到,根据网上所讲的只要重新运行一个android项目;就会在avd中生成一个新的debug.keystroe,此法也没解决,索性直接重 ...
- 记一次有关GET/POST请求的Debug经历
Bug描述: 电商网站, 产品列表页面,加入购物车按钮,当连续点击“加入购物车”按钮时,在MAC上的Safari上,只会有部分请求通过 Ajax 被发送出去,而在 Chrome/IE/Firefox ...
- 【实习记】2014-08-18使用curl排错http头的content-length
总结一,用curl排错Content-Length设置错误,误导了客户端. 访问/cgi-bin/txproj_list时,firebug显示总是不多不少15秒,调试其他问题时郁闷. fire ...
- 记2016腾讯 TST 校招面试经历,电面、笔试写代码、技术面、hr面,共5轮
(出处:http://www.cnblogs.com/linguanh/) 前序: 距离 2016 腾讯 TST 校招面试结束已经5天了,3月27日至今,目前还在等待消息.从投简历到两轮电面,再到被 ...
- DEBUG经历
在两年有余的学习生活中,我不仅在课堂上学到了很多东西,我也在一次次的错误中得到了宝贵的经验和教训.Bug和debug,构成了我生活中不可或缺的一部分. 我在编程中犯过的错误很多,无法一一阐述,再次说一 ...
- 记一次Apache Carbondata PR的经历
前言 前段时间有幸接触到Apache Carbondata,试用过程中发现了一个小小的问题,并且又很快的定位到了问题.然后在社区群里反映了下,负责人问愿不愿意提个JIRA,PR,然后我在没有任何开源 ...
- 记一次PHP“Segmentation fault”调试经历
遇到的问题: 在linux上安装php5.5.26.phalcon2.0扩展.xhprof扩展,均正常安装,并可单独运行.但放在一起运行时出现“Segmentation fault”错误.注:xhpr ...
- 记一次调试串口设备Bug的经历
最近花了差不多1天的时间在折腾一个Bug,该Bug的表象如下: 这个Bug还特别独特,在开发电脑中无提示,在终端用户那里每次使用软件的时候都报这个.仔细思考了一下最近在源码中新添加的功能,没发现有啥特 ...
随机推荐
- Docker安装redis的使用
1.Docker安装redis的使用. docker search redis检索redis的服务.先检索再拉取. 2.这里我们拉取官方的镜像,标签为5.0.5.[root@slaver4 ~]# d ...
- Oracle 查询练习
非常经典的一些日常醒脑练习内容!! 如有更高效的写法欢迎赐教! .已知Oracle的Scott用户中提供了三个测试数据库表,名称分别为dept,emp和salgrade.使用SQL语言完成以下操作 ) ...
- .NET WebFrom跨时区项目时间问题处理方法
前段时间因为公司的一个 WebFrom 项目设计到跨时区的问题,处理了一段时间,终于解决了,写个博客记录一下,方便以后回顾以及给他人提供一个参考的方法. 本次的项目因为跨越了多个时区,在一些时间上会受 ...
- .net基础加强
1.冒泡排序 请通过冒泡排序法对整数数组{ 1, 3, 5, 7, 90, 2, 4, 6, 8, 10 }实现升序排序 , , , , , , , , , }; BubbleSort(num); C ...
- Java开发在线考试系统 使用ssh框架编写源码
开发工具: Eclipse, Tomcat, MySql 1. 登录页面登录功能, 输入用户名与密码, 选择角色, 滑动箭头拉到最右边才可以点击登录 2. 学生角色登录 ...
- vue Router——进阶篇
vue Router--基础篇 1.导航守卫 正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航.有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的 ...
- ANDROID培训准备资料之四大组件的简单介绍
Android四大组件是一个android app 最基本的组成部分,这篇博客主要给大家简单的介绍一下四种组件 (1)Activities (2)Services (3)BroadcastReceiv ...
- 7 CentOS 7网卡配置
首先重中之重:修改前一定要进行系统备份,如果是虚拟机进行快照 查看虚拟机的网卡配置 注意桥接模式和NAT模式 桥接模式:网络层面,虚拟机和PC处于同级地位,虚拟机直接和路由器相连 NA ...
- this.$route和this.$router区别
this.$route 和 this.$router 这两个对象有什么区别: this.$route 是当前路由跳转对象,包含当前路由的name.path.query.params等属性 this.$ ...
- JavaScript深入浅出第4课:V8引擎是如何工作的?
摘要: 性能彪悍的V8引擎. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一等 ...