精华内容
下载资源
问答
  • 2019-06-19 17:08:59

    写时拷贝技术

      写时拷贝技术实际上是一种拖延战术,是为了提高效率而产生的技术,这怎么提高效率呢?实际上就是在需要开辟空间时,假装开了空间,实际上用的还是原来的空间,减少开辟空间的时间,等到真正要使用新空间的时候才去真正开辟空间。
      举一个例子理解写时拷贝技术:我们小时候经常会遇到这种情况:家里面有哥哥姐姐的,大人们经常会让我们穿哥哥姐姐穿剩的衣服,这样看起来我们就有了新衣服,但实际上我们穿的还是哥哥姐姐的旧衣服,等到我们真的长大了,才会给我们买属于自己的新衣服,这样节省了给我们买衣服的时间和财力。从而节省了很多资源(提高效率)。等我们真的需要时才不得不买新衣服(拖延战术)。
    在这里插入图片描述

    写时拷贝技术原理

      写时拷贝技术实际上是运用了一个 “引用计数” 的概念来实现的。在开辟的空间中多维护四个字节来存储引用计数。
    有两种方法:
    ①:多开辟四个字节(pCount)的空间,用来记录有多少个指针指向这片空间。
    ②:在开辟空间的头部预留四个字节的空间来记录有多少个指针指向这片空间。
      当我们多开辟一份空间时,让引用计数+1,如果有释放空间,那就让计数-1,但是此时不是真正的释放,是假释放,等到引用计数变为 0 时,才会真正的释放空间。如果有修改或写的操作,那么也让原空间的引用计数-1,并且真正开辟新的空间。

    linux 下的 fork() 就是用的写时拷贝技术,引用计数不光在 string 这里用到,还有智能指针 shared_ptr 也用到了引用计数来解决拷贝问题。

    举个例子

    string 的写时拷贝(维护一个指针):

    class String
    {
    public:
    	//构造
    	String(const char* str)
    		:_str(new char[strlen(str) + 1])
    		,_pCount(new int(1))
    	{
    		strcpy(_str, str);
    	}
    
    	//拷贝构造
    	String(const String& s)
    		:_str(s._str)
    		,_pCount(s._pCount)
    	{
    		(*_pCount)++;
    	}
    
    	//赋值运算符重载
    	String& operator=(const String& s)
    	{
    		if(_str != s._str)
    		{
    			if(--(*_pCount) == 0)
    			{
    				delete[] _str;
    				delete _pCount;
    			}
    			_str = s._str;
    			_pCount = s._pCount;
    			(*_pCount)++;
    		}
    		return *this;
    	}
    
    	~String()
    	{
    		if(--(*_pCount) == 0)
    		{
    			delete[] _str;
    			delete _pCount;
    		}
    	}
    
    	char& operator[](size_t pos)
    	{
    		if(*_pCount > 1)
    		{
    			char* newstr = new char[strlen(_str) + 1];
    			strcpy(newstr, _str);
    			--(*_pCount);
    			_str = newstr;
    			_pCount = new int(1);
    		}
    		return _str[pos];
    	}
    
    	const char* c_str()
    	{
    		return _str;
    	}
    private:
    	char* _str;
    	int* _pCount;
    };
    

    源码中的写法:在空间的头部维护四个字节的空间,记录引用的个数。放在头部维护效率能高一些,如果放在尾部维护的话,每次开辟新的空间都要讲这四个字节也向后挪动相应的位置,所以放在前面效率高点

    class String
    {
    public:
    	//构造
    	String(const char* str)
    		:_str(new char[strlen(str) + 4 + 1])
    	{
    		_str += 4;		//前四个字节放引用计数
    		strcpy(_str, str);
    		GetRefCount() = 1;
    	}
    
    	//拷贝构造
    	String(const String& s)
    		:_str(s._str)
    	{
    		GetRefCount()++;
    	}
    
    	//赋值运算符重载
    	String& operator=(const String& s)
    	{
    		if(_str != s._str)
    		{
    			if(--GetRefCount() == 0)
    			{
    				delete[] (_str - 4);
    			}
    			_str = s._str;
    			GetRefCount()++;
    		}
    		return *this;
    	}
    
    	~String()
    	{
    		if(--GetRefCount() == 0)
    		{
    			delete[] (_str - 4);
    			_str = nullptr;
    		}
    	}
    
    	char& operator[](size_t pos)
    	{
    		if(GetRefCount() > 1)
    		{
    			--GetRefCount();
    			char* newstr = new char[strlen(_str) + 4 + 1];
    			newstr += 4;
    			strcpy(newstr, _str);
    			_str = newstr;
    			GetRefCount() = 1;
    		}
    		return _str[pos];
    	}
    
    	int& GetRefCount()
    	{
    		return *((int*)(_str - 4));		//前四个字节为引用计数
    	}
    private:
    	char* _str;
    };
    
    更多相关内容
  • 写时拷贝 何为写时拷贝? 前面我说过深拷贝拷贝,今天我们来探究一下写时拷贝。深拷贝是补充了浅拷贝的不足,写时拷贝其实也就是补充一点深拷贝的不足。其实写时拷贝的意思就是: 当你读取到这个空间的时候,并不...
  • 主要介绍了C++写时拷贝实现原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 本文主要给大家介绍了关于c++中深浅拷贝写时拷贝实现的相关内容,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: 一:浅拷贝&深拷贝拷贝:在拷贝构造的时候,直接将原内容的地址交给要拷贝的类...
  • 如果对象中管理资源,编译器只是将对象中的值拷贝过来,就会导致多个资源共享一份资源,当一个对象销毁就会将该资源释放,而这时另一些对象不知道该资源已经被释放,以为还有效,所以,对资源继续操作,就会发生...
  • 行业分类-设备装置-写时拷贝快照方法及装置.zip
  • 本人采用C++实现的string类,采用的内存管理策略是隐式共享写时拷贝,其实现参考了智能指针的思想。
  • 探秘写时拷贝的真相

    千次阅读 2017-02-07 13:03:46
    其实我们对写时拷贝并不陌生,Linux fork和STL string是比较典型的写时拷贝应用,本文只讨论STL string的写时拷贝。 string类的实现必然有个 char* 成员变量,用以存放string的内容,写时拷贝针对的对象就是这个 ...

    声明:本文来自腾讯增值产品部官方公众号小时光茶社,为CSDN原创投稿,未经许可,禁止任何形式的转载。
    作者:梁少华,QQ动漫后台开发,腾讯高级工程师。从事后台开发4年多,参与过QQ秀、手Q红点系统、手Q游戏公会、QQ动漫等项目,有丰富的后台架构经验,擅长海量服务设计。
    责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件qianshg@csdn.net,另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qianshuguangarch申请入群,备注姓名+公司+职位。

    什么是写时拷贝

    写时拷贝(copy-on-write, COW)就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝。

    其实我们对写时拷贝并不陌生,Linux fork和STL string是比较典型的写时拷贝应用,本文只讨论STL string的写时拷贝。

    string类的实现必然有个char*成员变量,用以存放string的内容,写时拷贝针对的对象就是这个char*成员变量。通过赋值或拷贝构造类操作,不管派生多少份string“副本”,每个“副本”的char*成员都是指向相同的地址,也就是共享同一块内存,直到某个“副本”执行string写操作时,才会触发写时拷贝,拷贝一份新的内存空间出来,然后在新空间上执行写操作。显然,那些只读的“副本”节省了内存分配的时间和空间。

    听起来有点懵,对于没了解过写时拷贝的同学,会感觉完全颠覆平常对string的认知,下面我们来看一下实际例子。

    写时拷贝例子

    图片描述

    如上代码所示,调用拷贝构造函数生成str2,调用赋值操作符生成str3,那么str2与str3是否有分配内存空间来存储内容“abc”呢?

    图片描述

    运行结果告诉我们,str1、str2与str3是共享内存空间的(char*成员指向相同的地址)。那么问题来了,对str1、str2或str3内容的修改是否会互相影响呢?答案是,只要遵守STL的约定来修改,是会触发写时拷贝的,不会互相影响(毕竟平时一直这样用也没有问题)。

    图片描述

    图片描述

    可以看到,对str1重新复制,修改str3的值,都会触发写时拷贝,分配了新的空间。由于str1、str3都分配了新的空间,str2就可以继续使用原来的空间了。

    写时拷贝原理

    看了上面的例子,相信大家都已明白写时拷贝的表象了。但我们不能满足于现象,还要知道实现原理。应该很多同学都能猜到,string肯定是使用计数器来记录引用数,当有新的string对象共享内存块时,计数器+1,当有对象触发写时拷贝或析构时,计数器-1。

    那么计数器存放在哪里呢?这是对象级别的计数器,由若干个对象共享,string类成员变量、静态变量或全局变量都不能满足要求。最合适的就是在堆里分配空间专门存储这个计数器,由第一个创建的对象分配并初始化计数器,其他对象按照约定引用计数器。我们知道string的内存空间就在堆上,那么直接在这块区上多分配一个空间来存储计数器是最方便的,所有共享这块内存的string对象都能访问计数器。事实上STL就是这么实现的,在string内存空间的最前面分配了空间存储计数器,如下图所示(图片摘自引文):

    图片描述

    string的所有赋值、拷贝构造操作,计数器都会+1;修改string数据时,先判断计数器是否为0(0代表没有其他对象共享内存空间),为0则可以直接使用内存空间(如例子中的str2),否则触发写时拷贝,计数器-1,拷贝一份数据出来修改,并且新的内存计数器置0;string对象析构时,如果计数器为0则释放内存空间,否则计数器也要-1。

    STL源码分析

    我们稍微走读下STL源码,看看写时拷贝的实现,以赋值操作符为例(拷贝构造函数类似):

    1. 赋值操作符事实上是调用assign函数

    图片描述

    1. _M_grab完成引用计数器更新,返回string数据内存地址

    图片描述

    1. _M_rep返回Rep指针,Rep保存在string数据内存前面,所以使用-1下标索引。计数器_M_refcount就在Rep中。

    图片描述

    图片描述

    1. 实际执行_M_refcopy

    图片描述

    1. 引用计数器+1,返回数据内存地址(因为rep在数据前面,所以指针+1)

    图片描述

    图片描述

    写时拷贝是一把双刃剑

    写时拷贝能减少不必要的内存操作,提高程序性能,但同时也是一把双刃剑,如果没按STL约定使用string,可能会导致极其严重的bug,而且通常是很隐蔽的,因为一般不会把注意力放到一个赋值语句。

    那么STL的约定用法是怎样呢?可以概括为两点:一,使用string提供的写操作,包括操作符与成员函数修改内容,都能正常触发写时拷贝,不会有“坑”;二,c_str()与data()返回const char*指针,只用来读取数据,不要强制转成char*指针直接修改内存。写时拷贝惹的祸都是因第二点使用不当导致的,“有经验”的程序员喜欢直接操作内存,硬是把const指针改成非const,殊不知这样修改内存,string对象是不感知的,没有办法触发写时拷贝,后果就是所有共享同一内存的string对象内容都被篡改了。

    图片描述

    所以,应该从来都不把c_str()data()返回的指针转换成非const,从源头上杜绝写时拷贝惹的祸

    但是有时却不得不应付已弄脏的源头,比如底层库实现有问题,传string对象进去,里面却通过指针修改string内容,导致写时拷贝机制失效。举个列子:

    图片描述

    假设有上面一个Decode函数(为了方便描述,str默认空间够大),通过指针操作把data的数据拷贝到str。如果只调用一次,通常不会有什么问题,但是如果多次调用Decode,并且把str结果保存下来,那就出大bug,看下面代码:

    图片描述

    图片描述

    可以看到,每次调用Decode后,之前保存的结果(str1、str2)都会“被覆盖”了。那么该如何应对这种已经有问题的底层函数呢?可以强制触发写时拷贝,下面继续分析。

    强制触发写时拷贝

    下面这些方法都可以强制触发写时拷贝:

    1. 调用reserve函数

    图片描述

    图片描述

    注意:reserve一定是在赋值后调用,不然提前触发写时拷贝是没用的

    2. 调用resize函数

    图片描述

    图片描述

    注意:resize大小一定要跟原来不一样,不然string会认为无需重新分配空间,请看下面resize源码。

    图片描述

    另外,resize也要在赋值后调用。

    3. 调用[]操作符

    图片描述

    图片描述

    string[]操作符返回char&,允许调用者修改数据,所以会触发写时拷贝。

    4. 调用char*参数版本assign

    图片描述

    图片描述

    图片描述

    还要重点提醒,string参数版本的assign等价于赋值,不会触发写时拷贝的。

    图片描述

    图片描述

    图片描述

    相关参考资料


    编辑推荐:架构技术实践系列文章(部分):

    展开全文
  • COW技术,爆出了巨大的漏洞,让父子进程间可以向对方泄露过的新数据,成为了Linux内核的惊天大瓜。

    写时拷贝的原理我们没什么好赘述的,就是当P1 fork出来P2后,P1和P2会以只读的形式共享page,直到P1或者P2写这个page的内容,才发生page fault导致写的进程得到一份新的数据拷贝。 下面的代码演示了它的效果:

    int data = 10;
    
    int child_process()
    {
    	printf("Child process %d, data %d\n", getpid(), data);
    	data = 20;
    	printf("Child process %d, data %d\n", getpid(), data);
    	_exit(0);
    }
    
    int main(int argc, char *argv[])
    {
    	int pid;
    	pid = fork();
    
    	if (pid == 0) {
    		child_process();
    	} else {
    		sleep(1);
    		printf("Parent process %d, data %d\n", getpid(), data);
    		exit(0);
    	}
    }

    上面的代码,执行的时候打印:

    baohua@baohua-VirtualBox:~$ ./a.out 
    Child process 3498, data 10
    Child process 3498, data 20
    Parent process 3497, data 10

    子进程把10改为20后,父进程1秒后打印,得到的仍然是10。如果到这里为止,你看不懂,这篇文章不适合你这样的Linux初学者,请勿继续往下阅读。

    从技术上来讲,在父进程写过数据后,子进程应该读不到父进程新写的数据;在子进程写过数据后,父进程也应该读不到子进程新写的数据。这才符合“进程是资源封装的单位”的本质定义。

    如果都是上面的经典模型,那么岁月静好,与君白头偕老。但是,总会有人在花田里犯了错,破晓前仍然没有忘掉。这个COW技术,就爆出了巨大的漏洞,让父子进程间可以向对方泄露写过的新数据,成为了Linux内核的惊天大瓜。

    我们先来看看是怎样的一个程序,让COW的人设崩塌了呢?

        static void *data;
    
        posix_memalign(&data, 0x1000, 0x1000);
        strcpy(data, "BORING DATA");
    
        if (fork() == 0) {
    	// child
    	int pipe_fds[2];
    	struct iovec iov = {.iov_base = data, .iov_len = 0x1000 };
    	char buf[0x1000];
    
    	pipe(pipe_fds);
    	vmsplice(pipe_fds[1], &iov, 1, 0);
    	munmap(data, 0x1000);
    
    	sleep(2);
    	read(pipe_fds[0], buf, 0x1000);
    	printf("read string from child: %s\n", buf);
       } else {
    	// parent
    	sleep(1);
    	strcpy(data, "THIS IS SECRET");
       }

    上面的程序,父子进程最初共享了data指向的0x1000这么大1个page的内容。然后父进程在data里面写“BORING DATA”,之后,父进程fork子进程。子进程接下来创建了一个pipe,并用vmsplice,把data指向的buffer拼接到了pipe的写端,而后子进程通过munmap()去掉data的映射,再睡眠2秒制造机会让父进程在data里面写"THIS IS SECRET"。2秒后,子进程read pipe的读端,这个时候,神奇的事情发生了,子进程读到了父进程写的秘密数据。

    为什么会发生这种事情呢?魔鬼就在细节里。这里面有2个细节:

    1. 子进程munmap,导致data的mapcount减-1,这样欺骗了Linux内核,使得父进程在写THIS IS SECRET的时候,并不会发生COW,因为内核理解data只有1个进程有map,制造拷贝显然是多余的。

    2.子进程调用vmsplice,这是一种0拷贝技术,避免管道的写端从userspace往kernel space进行拷贝。vmsplice的底层,会通过传说中的GUP(get_user_pages)技术,来增加page的引用计数,导致page不会被回收和释放。

    所以,子进程通过pipe的写端hold住了老的page,然后通过read(),把这个page经过父进程写后的新内容读出来了。这真地很神奇有木有!这个漏洞的编号是CVE-2020-29374,它的官方描述如下:

    An issue was discovered in the Linux kernel before 5.7.3, related to mm/gup.c and mm/huge_memory.c. The get_user_pages (aka gup) implementation, when used for a copy-on-write page, does not properly consider the semantics of read operations and therefore can grant unintended write access, aka CID-17839856fd58.

    这个瓜大地直接惊动了祖师爷Linus Torvalds发patch来进行“修复”,Linus的“修复”patch编号是17839856fd58 ("gup: document and work around 'COW can break either way' issue")。祖师爷的修复方法比较简单直接,对于任何要COW的page,如果你做GUP,哪怕你后面对这个page的行为是只读的,也要得到一份新的copy。对应前面的参考代码,其实就是子进程调用vmsplice的行为,打破了COW的常规逻辑,之后子进程read(pipe[0])的时候,读到的是新的page。

    所以没有Linus的patch的时候,data的内存在父子进程分布如下:

    有了Linus的patch后,data的内存在父子进程分布如下:

    显然,这样之后,父进程写data后,写的是蓝色区域,子进程读的是黄色的区域,这样子进程是肯定读不到SECRET数据了。

    Linus是永远正确的?必须是!当Linus把这个patch合入5.8内核的时候,人们以为故事就此结束了,却没想到瓜才刚刚开始。作为Linus内核的吃瓜群众,我们的激情从不曾磨灭,因为“吃在嘴里,甜在心里”,吃瓜的甜蜜诱惑引诱我们一步步走入Linux内核的深渊,误了一生。

    redhat的Peter Xu童鞋,在2020年8月报了一个bug,直指祖师爷的patch造成了问题,因为它破坏了类似userfaultfd-wp和umapsort这样的应用程序。注意,子曾经曰过,“If a change results in user programs breaking, it's a bug in the kernel. We never EVER blame the user programs”,有图有真相:

    一个典型的umap代码仓库在:

    GitHub - LLNL/umap: User-space Page Management

    这种app利用userfaultfd的原理,在userspace处理page fault,从而提供userspace特定的page cache evict机制。关于userfaultfd的原理和用法,你可以阅读我之前的文章

    宋宝华:论一切都是文件之匿名inode_宋宝华-CSDN博客

    简单来说,umap这样的程序通过3个步骤来evict page。

      (1) 用mode=WP来对即将被evict的page执行写保护,从而block对于page P的写,保持page的clean;
      (2) 把page P写入磁盘;
      (3) 通过MADV_DONTNEED来evict这个page。

    其中的第2步会用到一个read形式的GUP。不过,Linus已经通过他的patch,强迫哪怕是read形式的GUP也要发生COW,这样触发了一个app完全没有预期到的page fault,导致uffd线程出错hang死。显然Linus自己break了userspace,等待他的结局是,他的patch的行为也要被revert掉。这一次仍然是Linus亲自出手,他提交了09854ba94c6a ("patch: mm: do_wp_page() simplification"),导致程序的行为再次发生了翻天覆地的变化。

    前面我们提到,通过Linus的17839856fd58 ("gup: document and work around 'COW can break either way' issue") patch,子进程vmsplice的GUP行为会强迫子进程进行COW,得到新的拷贝。但是,现在Linus不这个干了,vmsplice的pipe写端还是指向老的页面,他重新选择了在父进程进行实际的写的时候,不再只是傻傻地判断page的mapcount,他还会判断是不是有人间接通过GUP等形式,增加了page的引用计数,如果是,则在父进程写的时候,进行copy-on-write,这个时候,父进程写过"THIS IS SECRET"后,data在父子进程的内存分布变成:

     由于父进程是在新的黄色page进行写,而子进程用的是老的蓝色page,所以"THIS IS SECRET"不会泄露给子进程。Linus的最主要修改是直接变更了do_wp_page()函数,逻辑变成:

            struct page *page = vmf->page;
    
            if (page_count(page) != 1)
                    goto copy;
            if (!trylock_page(page))
                    goto copy;
            if (page_mapcount(page) != 1 && page_count(page) != 1) {
                    unlock_page(page);
                    goto copy;
            }
            /* Ok, we've got the only map reference, and the only
             *  page count reference, and the page is locked,
             * it's dark out, and we're wearing sunglasses. Hit it.
             */
            wp_page_reuse(vmf);
            unlock_page(page);
            return VM_FAULT_WRITE

    因为GUP的行为会增加page的refcount,从而触发父进程在写data的wp的page fault里面,进行COW。所以Linus是守信用的,自己提交的patch犯的错,含泪也要revert掉。

    那么故事就此结束了吗?正当所有的吃瓜群众都把西瓜皮扔到垃圾桶准备休息一阵的时候,蕾神再次以惊天之锤,锤向了“花田里犯的错”。

    累了,睡觉了。欲知后事如何,请听下回分解。

    展开全文
  • Linux写时拷贝实现原理

    千次阅读 2018-06-08 10:28:38
     写时拷贝指的是两个任务可以同时自由读取内存,但任意一个任务试图对内存进行修改,内存就会复制一份提供给修改方单独使用,以免影响到其他的任务使用。 Linux的fork()使用写时拷贝页实现。写时拷贝是一种可以...
            传统的fork系统调用直接把所有资源复制给新创建的进程,这种实现过于简单并且效率低下。
            写时拷贝指的是两个任务可以同时自由读取内存,但任意一个任务试图对内存进行修改时,内存就会复制一份提供给修改方单独使用,以免影响到其他的任务使用。
            Linux的fork()使用写时拷贝页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。fork()只能够产生本任务的镜像,因此需要使用exec配合才能够启动别的新的任务。exec可以用新的可执行映像提花当前的可执行映像,因此在fork产生了一个新任务之后,新任务可以调用exec来执行新的可执行文件。fork和exec通常用于产生新任务,而如果要产生新线程,则可以使用clone.
             fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。



    展开全文
  • 【Linux】什么是写时拷贝?

    千次阅读 多人点赞 2018-10-24 22:07:55
    一、写时拷贝顾名思义就是“的时候才分配内存空间”。这实际上是一种拖延战术。 例如: 二、传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因为它拷贝的数据或许可以...
  • 这几天在看《Linux内核设计与实现》,看到fork函数写时拷贝(copy on write)那一节,突然发现以前学习写时拷贝技术的时候只是大概理解了它的原理,并没有深入理解,本来想在网上找找有没有分析写时拷贝技术实现原理的...
  • C++写时拷贝

    千次阅读 2018-08-14 19:57:14
    一个类,如果不写拷贝构造函数,那么它的默认拷贝构造函数为浅拷贝,浅拷贝有什么问题呢? 拿一个简单的String类举例: class String{ public: String(char* str = "\0") :_str(new ...
  • cow(写时拷贝)技术

    千次阅读 2017-08-28 15:37:38
     在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化,才会将父进程的内容...
  • 写时拷贝的方式实现C++中string类

    千次阅读 2016-09-04 22:56:46
    写时拷贝的两种实现方式:计数器和引用计数
  • 1. 写时拷贝的概念 Linux在使用fork()函数进程创建,传统fork()的做法是系统把所有的资源复制给新创建的进程,这种方式不仅单一,而且效率低下。因为所拷贝的数据或别的资源可能是可以共享的。现在Linux的fork()...
  • 写时拷贝

    千次阅读 2016-08-29 17:05:49
    Copy On Write(COW):写时拷贝技术 一、什么是写时拷贝技术: 写时拷贝技术可以理解为“的时候才去分配空间”,这实际上是一种拖延战术。 举个栗子: 二、写时拷贝技术原理:  写时拷贝技术是...
  • 前言一、String类初识1.String类出现的原因2.string类的特性二、String类的简单赋值浅拷贝的内存分析1. 什么是浅拷贝?2. 浅拷贝问题总结三、String类加入引用计数的浅拷贝的分析与实现1....六、写时拷贝...
  • 写时拷贝的两种方案

    千次阅读 2017-07-09 18:07:00
    1. 什么是写时拷贝写时拷贝故名思意:是在的时候(即改变字符串的时候)才会真正的开辟空间拷贝(深拷贝),如果只是对数据的读,只会对数据进行浅拷贝写时拷贝:引用计数器的浅拷贝,又称延时拷贝写时...
  • 浅析C++深浅拷贝写时拷贝

    千次阅读 多人点赞 2021-04-04 09:41:28
    二、写时拷贝总结 前言 之前我们在浅谈6个成员函数中有提到深浅拷贝的问题,现在再回首掏一把。 一、深浅拷贝哪家强? 先给出代码理一理 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include&...
  • Linux 的fork()使用写时拷贝页来实现! 写时拷贝:是一种推迟或者免除拷贝的技术。 内核fork()并不复制整个进程地址空间,而是让父子进程共享一个地址空间---》只有在需要写入,数据才会被复制,从而使各个...
  • Unix的进程创建很特别。许多其他的操作系统都提供了产生进程的机制,首先在新的地址空间创建进程,读入可执行的文件,最后开始执行。Unix采用了与众不同的实现方式,它把上述步骤分解到两个单独...首先fork通过拷贝当前
  • C++中String类的深浅拷贝写时拷贝

    千次阅读 2018-05-02 19:33:11
    1.String类,只给了构造函数和析构函数,拷贝构造函数和赋值运算符重载都是编译器合成。 class String { public: String(const char* str = &quot;&quot;) { if (NULL == str) { _str = new char[1]...
  • 写时拷贝技术:Copy-On-Write

    千次阅读 2016-05-25 10:46:38
    写时拷贝技术 1、概念 Scott Meyers在《More Effective C++》中举了个例子,不知你是否还记得?在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里,做出一副正在复习功课的 样子,...
  • 在前面 大家先看下边这几行代码: int main() { int *p1=new int[10]; int *p2=p1; delete []p1; delete []p2; } 运行程序,我们发现程序无法运行。 通过查看内存,我们发现两个指针指向同...
  • 写时拷贝小结

    千次阅读 2021-11-19 22:31:03
    而深拷贝可以在每一次拷贝时为当前指针重新分配空间,不用再去共享那块空间。虽说深拷贝可以很好的解决这个问题,但它也存在自身的问题。比如相同的数据内容如果每次都重新分配空间去放置数据的话,就会造成空间的...
  • C++ String写时拷贝(Copy On Write)

    千次阅读 2016-04-18 20:50:07
    Copy On Write(写时拷贝)使用了“引用计数”,开辟空间会多开4个字节用于保存引用计数的值。当第一个对象构造,string的构造函数会根据传入的参数从堆上分配内存,当有其它对象需要这块内存,这个计数为自动...
  • 【C++】深拷贝、浅拷贝写时拷贝

    千次阅读 2017-07-17 15:35:01
    大家首先来看一下下面这段代码:void Test() { int *p1 = new int[10]; int *p2 = p1; delete[] p1; delete[] p2; }运行这段代码: ...浅拷贝:也称位拷贝,编译器只是直接将指针的值拷贝过来,结

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 866,627
精华内容 346,650
关键字:

写时拷贝