写时拷贝惹得祸

来源:互联网 发布:php 为什么可以跨平台 编辑:程序博客网 时间:2024/05/16 23:53

【问题表现】hydra cgi底层库取出来的参数不对

 

【问题定位】

1. 确认是否库的问题

check out hydra-release_src-3.10.0,拷贝问题参数到单元测试用例,编译运行,表现跟上面贴图一致,确认是库本身的问题。遗憾的是,cgi底层库单元测试本身的用例全都通过。

2. 走查源码

仔细确认检查,cgi底层库依赖hydra_uri_endecoder里面的ParamsStr2Map接口,用以参数解析,参数解析过程会用到DecodeUri接口,用以uri解码。cgi底层库最近增加了DecodeUri重载版本,并把最原始的DecodeUri接口用新的重载版本封装,基本确认就是这个接口有问题。遗憾的是,这个接口的单元测试也是通过。

3. 调试并找规律

修改引起错误的用例,并寻找规律,发现一个“规律”,当连续2个参数中第2个参数的value长度比第一个参数长度要小的时候会触发此问题,第2个参数的值会覆盖第一个参数的值。但是这个规律似乎又不太靠谱,,如果所示,为什么numvalue(40)没有覆盖srcvalue?折腾了很久都没有结果。

4. 转机

当反复修改用例,并加大量debug log辅助检查后,发现一个“真正的规律”,当连续2个参数中第2个参数的value长度比第一个参数长度“小1”的时候会触发此问题,第2个参数的值会覆盖第一个参数的值。修改几组用例并测试,最后证明了此规律的真实性。

5. 反过来再查源码

代码里面使用了string手册不推荐的做法,直接修改stringbuffer,虽然不推荐,但是这个方法本身是“没有”问题的,只要事前事后做好“偏移”即可。现实中,为了减少string拷贝,经常这样做,屡试不爽。再次排除了代码本身有错的可能。

6. 尝试其他重载版本

一开始没有想过要尝试其他重载版本,DecodeUri接口有3个重载版本,是层层依赖的,下次接口有问题,基本确认上层接口也有问题。没辙的时候就死马当活马医了,用最上层的string DecodeUri(conststring&uri) 接口编译单元测试并运行,结果奇迹真的出现了,运行结果居然正常!有点不合常理,但是必须尊重现实。

对比发现,这2个版本的区别是,一个返回应用,一个返回拷贝。难道map[DecodeUri(key)] = DecodeUri(value)这样的操作,对于DecodeUri返回引用还是拷贝有差别吗?按常理,map会在内部容器拷贝一份pair<DecodeUri(key)] , DecodeUri(value)>,事实上确实如此,但是跟预想的并不完全一样。

7. 考虑DecodeUri返回引用与拷贝的区别

stringktmp, vtmp;

key2value[DecodeUri(key, ktmp)] = DecodeUri(value, vtmp);

难道key2value这个map在内部容器直接引用了ktmp, vtmp,而没有拷贝?

加日志检查,证实了猜想,map中“最后”一次插入的<keyvalue>的地址跟外部ktmpvtmp两个string的地址分别相等。

心里终于有点眉目了,原来string使用了"写时拷贝"技术!测试string key = DecodeUri(key, ktmp)这一条语句,打印

查看,发现keyktmp的地址相等,进一步证实写时拷贝确实存在。

8. 为什么resize操作没有引起string的写时拷贝呢

回顾之前的规律:“当连续2个参数中第2个参数的value长度比第一个参数长度“小1”的时候会触发此问题”,结合代码:

stringktmp, vtmp;

key2value[DecodeUri(key, ktmp)] = DecodeUri(value, vtmp);

DecodeUri2个参数res引用的是vtmpvtmp存储的是上一次的结果,第2次的结果(就是这里的uri)长度比上次小1uri.size() + 1后刚好跟上次的结果长度相等! resize的时候,string发现传进来的size跟内部的size是一致的,认为这不是一个“写”操作,从而不会触发拷贝,结果悲剧的一幕就上演了!修改resbuffer会导致map中最后一次插入的valuebuffer被修改,因为它们指向同一个地址。最终导致取出的参数不对。

【问题修复】

1. 用相对低效但是靠谱的重载版本(string拷贝版:string DecodeUri(const string&uri) 

2. 强制触发“写时拷贝”,如:

DecodeUri((char*)res.data(), reslen, uri.data(), uri.size())

改为

DecodeUri((char*)&res[0], reslen, uri.data(), uri.size())

左值引用版本【】重载,会触发string内部的写时拷贝,问题得到根本解决

0 0
原创粉丝点击