转C++之stl::string写时拷贝导致的问题
前几天在开发某些数据结构到文件的 Dump 和 Load 功能的时候, 遇到的一个 bug 。
【问题复现】
问题主要出在 Load 过程中,从文件读取数据的时候, 直接使用 fread 的去操作 string 的内部指针地址 (char*)s.c_str() 。 简化后的示例代码如下( testdata1 文件内容是12345):
void Load(string& s, size_t offset, size_t size) {s.resize(size);FILE* fp = fopen("testdata1", "r");assert(fp != NULL);fseek(fp, offset, SEEK_SET);fread((char*)s.c_str(), sizeof(char), size, fp);fclose(fp);}
通过 string::resize() 分配内存空间。 通过 string::c_str() 直接获取内存空间的起始地址并写入数据。
这样的用法是典型的使用 string 当数据缓冲区的用法, 省去了 malloc(new) 和 free(delete) 的过程。 通常来讲不会遇到什么问题。
不过这次遇到问题了。
简化问题代码示例如下:
string s;Load(s, 0, 3);assert(s == "123"); // successstring s2 = s;Load(s2, 1, 3);assert(s2 == "234"); // successassert(s == "123"); // failed
注: 因为 testdata1 文件内容是 12345 的纯文本文件。
所以 Load(s, 0, 3) 内容就是 “123” ,依此类推。
但是当后面的 string s2 = s; 定义了一个和 string 变量 s2 。 此时 Load(s2, 1, 3); 时 s2 内容是 “234” 符合预期。
但是问题出在之后 s 的内容也变成了 “234” , 而不是保持原来的 “123” 。
【原因分析】
其实示例代码写成那样,问题也清楚了很多了, 问题就出在
string s2 = s;
和之前 Load 函数中的
fread((char*)s.c_str(), sizeof(char), size, fp);
也就是 string 的 copy-on-write 实现上。
(之前的问题是隐藏在各种代码之间,甚至都很难定位到原来是 string 的问题。)
C++ stl::string 有两种常见的主流实现方式:
『eager-copy』
每个 string 都是一个独立申请的内存空间,每次拷贝都是深拷贝, 哪怕内容是一模一样的, 所以每个 string 的 c_str() 指针地址都是 不一样 的。 这样的优点是内存空间互不干扰, 缺点是内存浪费。
『copy-on-write』
string 之间拷贝时不是深拷贝,只拷贝了指针, 也就是共享同一个字符串内容, 只有在内容被修改的时候, 才真正分配了新的内存并 copy 。 比如 s[0]='1' 之类的修改字符串内容的一些write操作, 就会申请新的内容,和之前的共享内存独立开。 所以称之为 『copy-on-write』
最显然的就是 string s2 = s; 拷贝后, s 和 s2 的 c_str() 返回的指针地址是 一样 的。 这样的优点就是节省内存开销, 当string字符串占用内存较大时, 也可以省去深拷贝时较大的性能开销。
不同的stl标准库实现不同, 比如 Centos 6.5 默认的 stl::string 实现就是 『copy-on-write』, 而 Mac OS X (10.10.5) 实现就是 『eager-copy』。
而这次的 bug 就是和 『copy-on-write』有关,
因为 s2 和 s 的 c_str() 指针是同一个, 所以 Load 函数里面的这行代码:
fread((char*)s.c_str(), sizeof(char), size, fp);
我们以为只是在操作一个字符串, 其实是 s 和 s2 两个字符串的内容都被修改了。 所以就会导致一系列的问题。
完整示例代码请看 stringload
【总结】
总之,原因的源头在于 (char*)s.c_str() , 虽然我在 StackOverFlow 上有些高票答案也经常使用类似的把 string 当成内存缓冲区的写法。 毕竟方便嘛。但是考虑到 stl 的 copy-on-write 实现,会导致把 stl 容器当内存缓冲区的写法变得有隐藏陷阱。
虽然我在解决这个 bug 之前就知道 stl 有 『copy-on-write』 实现这么一说。 但是开发时候往往出现问题的地方并不是直接在有问题的代码那里就出现问题, 导致很难查,更何况不知道 『copy-on-write』这回事的开发者,可能就容易踩大坑了。
转C++之stl::string写时拷贝导致的问题的更多相关文章
- String写时拷贝实现
头文件部分 1 /* 版权信息:狼 文件名称:String.h 文件标识: 摘 要:对于上版本简易的String进行优化跟进. 改进 1.(将小块内存问题与大块分别对待)小内存块每个对象都有,当内存需 ...
- String类的实现(4)写时拷贝浅析
由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间.这种方法就是写时拷贝.这也是一种 ...
- String 类的实现(2)引用计数与写时拷贝
1.引用计数 我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N ...
- 标准C++类std::string的内存共享和Copy-On-Write(写时拷贝)
标准C++类std::string的内存共享,值得体会: 详见大牛:https://www.douban.com/group/topic/19621165/ 顾名思义,内存共享,就是两个乃至更多的对象 ...
- Linux写时拷贝技术(copy-on-write)
COW技术初窥: 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内 ...
- 【转】Linux写时拷贝技术(copy-on-write)
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html 源于网上资料 COW技术初窥: 在Linux程序中,fork()会 ...
- 计算机程序的思维逻辑 (73) - 并发容器 - 写时拷贝的List和Set
本节以及接下来的几节,我们探讨Java并发包中的容器类.本节先介绍两个简单的类CopyOnWriteArrayList和CopyOnWriteArraySet,讨论它们的用法和实现原理.它们的用法比较 ...
- 并发容器之写时拷贝的 List 和 Set
对于一个对象来说,我们为了保证它的并发性,通常会选择使用声明式加锁方式交由我们的 Java 虚拟机来完成自动的加锁和释放锁的操作,例如我们的 synchronized.也会选择使用显式锁机制来主动的控 ...
- 深拷贝&浅拷贝&引用计数&写时拷贝
(1).浅拷贝: class String { public: String(const char* str="") :_str(]) { strcpy(_str,str); } ...
随机推荐
- #内存不够,swap来凑# Linux上创建SWAP文件/分区
转自:https://www.vmvps.com/how-to-create-a-swap-file-on-the-linux-os.html 很久很久以前,电脑的内存是个珍贵东西,于是乎就有了swa ...
- jdk1.8 -- 方法推导 静态方法、实例方法、构造方法推导
一.静态方法的推导 public class MainTest { public static void main(String[] args) { // 正常情况下我们需要通过写一个consumer ...
- 【ActiveReports 大数据分析报告】2019国庆旅游出行趋势预测
今年国庆假期全国接待国内游客人数有望达到8亿人次! 随着2019国庆小长假的临近,不少游客已经开始着手规划假期出游路线.据权威机构发布的<2019国庆旅游趋势预测报告>显示,今年“十一黄金 ...
- 解决Python查询Mysql中文乱码问题
前段时间,自己瞎动手用Django写了一个更新zip包和sql到远程服务器的工具.但Python从Mysql中读取出来的中文字符会乱码,如下图: 解决办法:Python连接Mysql时指定charse ...
- 架构设计之CAP定理
一.什么是 CAP? 「 CAP定理 」又被称为 布鲁尔定理,它提出对于一个分布式系统而言,不能同时满足以下三点: Consisteny(一致性) Availability(可用性) Partitio ...
- STL queue 常见用法详解
<算法笔记>学习笔记 queue 常见用法详解 queue翻译为队列,在STL中主要则是实现了一个先进先出的容器. 1. queue 的定义 //要使用queue,应先添加头文件#incl ...
- 【操作记录】搭建虚拟机下 java 开发环境
第一步,配置虚拟机 virtualBox 官网下载最新版,安装,选择简体中文. CentOS 官网下载 最小版iso. 新建虚拟机并配置,挂载iso光盘,然后启动. 因为是最小版,安装时可能不支持使用 ...
- linux + qt 环境搭建
下载地址 install qt huqian@huqian-Lenovo-IdeaPad-Y400:~/XRF$ ls qt-opensource-linux-x64-.run SourceFile ...
- ale.js 对比其他框架
欢迎!我们相信你来这里的目的就是为了解 Ale 与其他大型框架的区别,这也正是我们想要在此回答的. 客观来说,作为 Ale 的核心开发者,我们肯定会更偏爱 Ale,认为对于某些问题来讲用 Ale 解决 ...
- Spring 的 AOP 概述和底层实现
Spring 的 AOP 概述和底层实现 1. 什么是 AOP AOP (Aspect Oriented Programing),即面向切面编程 AOP 采取横向抽取机制,取代了传统纵向继承体系重复性 ...