精华内容
下载资源
问答
  • 提到这两个函数,大家 函数原型 void *memcpy(void *dest, const void *src, size_t n); void *memmove(void *dest, const void *src, size_t n);

           终于找了个机会可以写下这两个函数的区别了,不然怕是要忘了,另外,感觉C语言中的小的知识点不要太多,仍需要自己不断地编程,不断地去发掘、、、

    1. 函数介绍

           说到memcpy()和memmove()这两个函数,可能大家从名称上认为二者是两个不同的函数。其实不然,事实上,这两个函数功能是类似的,都是对内存进行拷贝(千万不要被memmove()函数中的move给欺骗了,不要想当然的认为它就是移动),二者的区别仅仅是对于内存重叠这一现象的处理。
           如果要拷贝的两个内存空间不重叠的话,那么使用memcpy()和memmove()是等价的!

    首先先来看看两个函数的函数原型。
    二者函数原型如下:

    //dest:拷贝到的目的地址	src:拷贝的起始地址	n:表示拷贝多少个字节
    void *memcpy(void *dest, const void *src, size_t n);
    void *memmove(void *dest, const void *src, size_t n);
    

           咋一看,这两个函数除了函数名不同,函数的形参列表以及返回值均相同。然后,当你知道两者的函数定义时,你就会理解上面那句“如果要拷贝的两个内存空间不重叠的话,那么使用memcpy()和memmove()是等价的!”话的原因了,这里先不直接列出两者的函数定义,而是先讲一下内存重叠的问题。

    2. 内存重叠

           首先要知道什么是内存重叠?它是指要拷贝的起始地址(即src)与要拷贝到的目的地址(即dest)有重叠,内存重叠有两种表现形式,如下图所示:
    在这里插入图片描述
    对于覆盖情况一:
           此时将src拷贝到dest,不会出现差错。我们可以一位一位的进行拷贝,首先src中的1拷贝到dest的1的位置,2拷贝到2的位置,3拷贝到3的位置,然后此时4就拷贝的4的位置,但此时src处位置1处就不再是1了,而是变成了4。同样,src中位置2处就变成了5。
           至此便完成了5个位的内存拷贝,本次内存拷贝的结果是正确的,将“12345”五个数拷贝到了目的地址上。但是此时源地址上相应的五个数字中的前两个数字分别被替换成了“4”和“5”。

    对于覆盖情况二:
           此时将此时将src拷贝到dest,就会发生错误了。同样我们还是一位一位的进行拷贝。首先将src中的1拷贝到dest的1的位置,但dest中位置1处对应的是src的位置4,这时src位置4处的值4就被修改为1!同样将src中的2拷贝到dest中位置2处,但dest中位置2处对应的是src的位置5,这时src位置4处的值5就被修改为2!接下来在进行按位拷贝的话,将会得到错误的结果“12312”。
           事实上操作系统对于第二种覆盖情况是未定义的,我们按位拷贝获得的结果只是用来认识第二种覆盖情况,但在代码中碰到这种情况,得到的结果是未知的,不一定是“12312”,可以自行编写程序进行验证。

           对于第一种覆盖情况,使用memcpy()和memmove()均不会出现拷贝出错的情况,但memcpy()不能正确处理第二种情况,只能使用memmove()才能进行正确的内存拷贝!这样一来,在不知道内存是否重叠的情况下,为了保证内存拷贝的正确执行,使用memmove()是最为稳妥的,但作为牺牲,程序的执行效率会比memcpy()要低(函数memmove()的定义要比memcpy()的定义复杂,这个区别在最后阐述)。因此,在我们能够保证内存不会重叠的前提下,使用memcpy()会更高效。一般而言,内存一般是不会重叠的,但有时在不经意间,我们代码中所使用的一些操作就会导致内存重叠。

    3. 实例验证

    下面举个例子来做一个简单说明。
    注:最好将拷贝过程在纸上画出来,这样会加深理解

    //首先定义一个字符串数组str
    char str[11] = "0123456789";
    
    /*
    	下面分别使用memmove和memcpy对这个数组进行操作
    	① 覆盖情况一(src是高地址,dest是低地址,由高地址向低地址进行内存拷贝)
    */
    memmove((void *)&str[0], (void *)&str[3], 5);
    memcpy((void *)&str[0], (void *)&str[3], 5);
    /*
    	两个函数的结果是相同的,得到的结果均为“3456756789”
    	即实现了将“34567”这五个数字拷贝到数组的前五个位置上
    */
    /*
    	② 覆盖情况二(src是低地址,dest是高地址,由低地址向高地址进行内存拷贝)
    */
    memmove((void *)&str[3], (void *)&str[0], 5);
    memcpy((void *)&str[3], (void *)&str[0], 5);
    /*
    	这次,两个函数的结果完全不同,
    	使用memmove()得到结果“0120123489”是我们事先想得到的,
    	而memcpy()得到的结果却不是我们所预期的,结果是“0120120189”
    */
    

           以上例子需要我们注意的是,我们在对数组操作的时候,容易导致内存重叠,从而导致我们得到错误的结果。所以在以后的编程中需要注意这一点。

    测试代码如下:

    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char *argv[])
    {
        int 	i;
        char 	str[11] = "0123456789";
    
        for (i = 0; i < 10; i++)
        {
            printf("%c", str[i]);
        }
        printf("\n");
        
        //memmove((void *)&str[3], (void *)&str[0], 5);
        //memcpy((void *)&str[3],(void *)&str[0], 5);
        //memmove((void *)&str[0],(void *)&str[3], 5);
        memcpy((void *)&str[0], (void *)&str[3], 5);
    
        for (i = 0; i < 10; i++)
        {
            printf("%c", str[i]);
        }
        printf("\n");
        
        return 0;
    }
    

    4. 函数定义

    ① memcpy()函数:

    void* memmove(void* str1,const void* str2,size_t n)
    {
    	size_t 	i;
        char	*pStr1 = (char *)str1;
        char	*pStr2 = (char *)str2;
        
        for(size_t i = 0;i != n; i++)
        {
        	*(pStr1++) = *(pStr2++);
        }
    
        return str1;
    }
    

    ② memmove()函数:

    void* memmove(void* str1,const void* str2,size_t n)
    {
    	size_t 	i;
        char	*pStr1 = (char *)str1;
        char	*pStr2 = (char *)str2;
        if  (pStr1 < pStr2) 
        {
            for(size_t i = 0;i != n; i++)
            {
                *(pStr1++) = *(pStr2++);
            }
        }
        else
        {
            pStr1 += n-1;
            pStr2 += n-1;
            for(size_t i = 0;i != n; i++)
            {
                *(pStr1--) = *(pStr2--);
            }
        }
        return str1;
    }
    

           可见memcpy()函数的函数定义是memmove()函数的函数定义的一部分,这也是为什么在内存不重叠以及第一种重叠情况(src是高地址,dest是低地址,由高地址向低地址进行内存拷贝)下均可使用memcpy()和memmove()函数的原因了。但在第二种重叠情况下(src是低地址,dest是高地址,由低地址向高地址进行内存拷贝),就只能使用memmove()函数了,可以仔细看一下memmove()的代码逻辑,转换的很巧妙!

    展开全文
  • Linux 内存拷贝

    2020-03-27 06:09:37
    什么叫零拷贝? 简单来说就是避免多余的...read函数返回从系统态返回到用户态,将数据从内核缓冲区拷贝到user缓冲区(用户) 调用write函数,从用户态转变成系统态,将数据从用户缓冲区拷贝到socket缓冲区(内核) 在...

    什么叫零拷贝?

    简单来说就是避免多余的拷贝(不是不发生拷贝)。

    传统来说,我们发生网络调用或文件进行操作的时候,一般会经过以下四个步骤:

    1. 调用read函数,从用户态转变成系统态,将数据从磁盘拷贝到内核缓冲区(内核)
    2. read函数返回从系统态返回到用户态,将数据从内核缓冲区拷贝到user缓冲区(用户)
    3. 调用write函数,从用户态转变成系统态,将数据从用户缓冲区拷贝到socket缓冲区(内核)
    4. 在进行其他的操作,将内核缓冲区的数据拷贝到相应的地方(内核)
    copy
    copy
    copy
    copy
    Hard Drive
    Kernel Buffer
    User Buffer
    Socket Buffer
    protocol engine

    使用mmap主要是完成了文件到内核空间的映射,用户缓冲区和内核缓冲区共享了:

    copy
    copy
    copy
    Hard Drive
    KB and UB
    Socket Buffer
    protocol engine

    使用sendfile从文件进入到网络协议栈,只需 2 次拷贝:第一次使用 DMA 引擎从文件拷贝到内核缓冲区,第二次从内核缓冲区将数据拷贝到网络协议栈;内核缓存区只会拷贝一些 offset 和 length 信息到 SocketBuffer,基本无消耗。

    copy
    little
    little
    copy
    Hard Drive
    Socket Buffer
    Socket Buffer
    protocol engine
    参考

    http://www.freesion.com/article/7040248729/

    https://blog.csdn.net/keil_wang/article/details/86688271

    展开全文
  • Linux DMA 内存拷贝与memcpy 速率比较

    千次阅读 2019-02-28 14:41:23
    linux/kernel.h&amp;gt; #include &amp;lt;linux/fs.h&amp;gt; #include &amp;lt;linux/init.h&amp;gt; #include &amp;lt;linux/module.h&amp;gt; #include &amp;lt;linux/...

    驱动层代码:

    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/device.h> 
    #include <linux/cdev.h> 
    #include <linux/dmaengine.h>
    #include <linux/wait.h>
    #include <linux/string.h>
    #include <linux/dma-mapping.h>
    #include <linux/slab.h>
    #include <linux/jiffies.h>
    
    
    #define DEBUG_PRINT printk
    
    #define MEMCPY_NO_DMA 0
    #define MEMCPY_DMA    1
    #define BUFF_SIZE     (512*1024)
    
    struct cdev my_cdev;
    static int major_ret;
    static struct class *pdma_class;
    static struct device *pdma_device;
    
    static dma_addr_t *src = NULL;
    static dma_addr_t src_phys ;
    static dma_addr_t *dst = NULL;
    static dma_addr_t dst_phys ;
    
    static volatile int dma_finished = 0;
    static DECLARE_WAIT_QUEUE_HEAD(wq);
    
    
    static void do_memcpy_no_dma(void)
    {
    	unsigned long t1 , t2,diff,msec;
    	int i ;
    	t1  = jiffies;
    	for(i = 0;i < 1000;i++)
    	{
    		memcpy(dst,src,BUFF_SIZE);	
    	}
    	t2 = jiffies;
    
    	diff = (long)t2 - (long)t1;
    	msec = diff *1000/HZ;
    
    	DEBUG_PRINT("used:%ld ms\n",msec);
    	
    }
    
    static void tx_callback(void *dma_async_param)
    {
    	//DEBUG_PRINT("callback here\n");
    	dma_finished = 1;
    	wake_up_interruptible(&wq);
    }
    
    static int do_memcpy_with_dma(void)
    {
    	struct dma_chan *chan = NULL;
    	dma_cap_mask_t mask;
    	
    	struct dma_async_tx_descriptor *tx = NULL;
    
    	dma_cookie_t dma_cookie;
    	
    	memset(src,0xAA,BUFF_SIZE);
    	memset(dst,0x55,BUFF_SIZE);	
    	
    	dma_cap_zero(mask);
    	
    	dma_cap_set(DMA_MEMCPY, mask);
    	
    	chan = dma_request_channel(mask, NULL, NULL);
    	if(NULL == chan )
    	{
    		printk("err:%s:%d\n",__FILE__,__LINE__);		
    		return -1;
    	}
    	
    
    	
    	
    	unsigned long t1 , t2,diff,msec;
    	int i ;
    	t1  = jiffies;
    	for(i=0;i<1000;i++)
    	{
    		dma_finished = 0;
    		//tx = dmaengine_prep_dma_cyclic(chan, src_phys, BUFF_SIZE, 1024, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT|DMA_CTRL_ACK);
    		tx = dmaengine_prep_dma_memcpy(chan, dst_phys, src_phys, BUFF_SIZE, DMA_PREP_INTERRUPT|DMA_CTRL_ACK);
    
    		if(NULL == tx)
    		{
    			printk("err:%s:%d\n",__FILE__,__LINE__);	
    			dma_release_channel(chan);
    			return -1;
    		}
    
    		tx->callback = tx_callback;
    		
    		dma_cookie = dmaengine_submit(tx);
    		if (dma_submit_error(dma_cookie))
    		{
    			printk("Failed to do DMA tx_submit");
    		}
    		
    		dma_async_issue_pending(chan);	
    
    		wait_event_interruptible(wq, dma_finished);
    		
    	}
    
    	t2  = jiffies;
    	diff = (long)t2 - (long)t1;
    	msec = diff *1000/HZ;
    
    	DEBUG_PRINT("used:%ld ms\n",msec);
    
    	printk("ok !\n");
    	if(memcmp(src, dst, BUFF_SIZE) == 0)
    	{
    		printk("memcpy succ !\n");
    	}
    	else
    	{
    		printk("memcpy failed !\n");
    		int i = 0;
    		for(i=0;i<8;i++)
    		{
    			printk("%x | %x\n",src[i],dst[i]);
    		}
    	}	
    
    	
    	dma_release_channel(chan);
    	
    }
    
    
    static long dma_ioctl(struct file *file, unsigned int cmd, unsigned long data)
    {
    	switch (cmd)
    	{
    		case MEMCPY_NO_DMA:
    			do_memcpy_no_dma();
    			break;
    		case MEMCPY_DMA:
    			do_memcpy_with_dma();
    			break;
    	}
    	return 0;
    }
    
    
    static const struct file_operations fops =
    {
    	.owner = THIS_MODULE,
    	.unlocked_ioctl = dma_ioctl,
    };
     
    static int __init dma_init(void)
    {
    	dev_t devno = 0;
    	
    	alloc_chrdev_region(&devno, 0, 1, "my-dma");
    	major_ret = MAJOR(devno);
    	cdev_init(&my_cdev, &fops);
    	cdev_add(&my_cdev, devno, 1);
    
    	pdma_class = class_create(THIS_MODULE, "my-dma-class");
    
    	pdma_device = device_create(pdma_class, NULL, MKDEV(major_ret,0), NULL, "my-dma");
    
    	src = dma_alloc_coherent(NULL, BUFF_SIZE, &src_phys, GFP_KERNEL);
    
    	if(NULL == src)
    	{
    		printk("err:%s:%d\n",__FILE__,__LINE__);
    		goto _FAILED_ALLOC_SRC;
    	}
    	
    	dst = dma_alloc_coherent(NULL, BUFF_SIZE, &dst_phys, GFP_KERNEL);	
    
    	if(NULL == dst)
    	{		
    		printk("err:%s:%d\n",__FILE__,__LINE__);
    		goto _FAILED_ALLOC_DST;
    	}
    	
    	return 0;
    _FAILED_ALLOC_DST:	
    	
    	dma_free_coherent(NULL, BUFF_SIZE, src, src_phys);
    _FAILED_ALLOC_SRC:
    	device_destroy(pdma_class, MKDEV(major_ret,0)); 
    	class_destroy(pdma_class);	
    	cdev_del(&my_cdev);
    	unregister_chrdev_region(MKDEV(major_ret, 0), 1);
    
    	return -1;
    	
    }
     
    static void __exit dma_exit(void)
    {	
    	//printk("hello dma openwrt exit\n");
    	device_destroy(pdma_class, MKDEV(major_ret,0));	
    	class_destroy(pdma_class);
    
    	dev_t devno = MKDEV(major_ret, 0);
    	cdev_del(&my_cdev);
    	unregister_chrdev_region(devno, 1);	
    
    	dma_free_coherent(NULL, BUFF_SIZE, src, src_phys);
    	dma_free_coherent(NULL, BUFF_SIZE, dst, dst_phys);	
    	
    }
     
    module_init(dma_init);
    module_exit(dma_exit);
     
    MODULE_AUTHOR("hello world");
    MODULE_DESCRIPTION("dma driver");
    MODULE_LICENSE("GPL");
    //MODULE_ALIAS("platform:" DRV_NAME);
    
    
    

    应用层测试代码:

    #include <stdint.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <getopt.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <linux/types.h>
    
    #define MEMCPY_NO_DMA 0
    #define MEMCPY_DMA    1
    
    
    void print_usage(char *argv)
    {
    	printf("usge:\n");
    	printf("%s <nodma|dma> \n");
    }
    
    
    int main(int argc ,char **argv)
    {
    	if(argc < 2)
    	{
    		print_usage(argv[0]);
    		return -1;
    	}
    
    	int fd = open("/dev/my-dma",O_RDWR);
    
    	if(fd < 0)
    	{
    		printf("open /dev/my-dma failed!\n");
    		return -1;
    	}
    
    	if(strcmp("dma", argv[1]) == 0)
    	{
    		ioctl(fd,MEMCPY_DMA);
    	}
    	else
    	{
    		ioctl(fd,MEMCPY_NO_DMA);
    	}
    
    
    
    	close(fd);
    	
    }
    
    
    

    测试结果:(H3 平台)
    DMA 比 memcpy 快一倍!
    在这里插入图片描述

    疑问:

    tx = dmaengine_prep_dma_memcpy(chan, dst_phys, src_phys, BUFF_SIZE, DMA_PREP_INTERRUPT|DMA_CTRL_ACK);
    

    这里的 tx 没有看到 在哪 free 的,是否会内存泄漏?
    答:不会,传输完成后由中断处理函数自动释放内存。

    代码跟踪:

    dmaengine_prep_dma_memcpy
      sun6i_dma_prep_dma_memcpy
        return vchan_tx_prep(&vchan->vc, &txd->vd, flags);
          list_add_tail(&vd->node, &vc->desc_allocated);
            
    

    先看 struct virt_dma_chan

    struct virt_dma_chan {
    	struct dma_chan	chan;
    	struct tasklet_struct task;
    	void (*desc_free)(struct virt_dma_desc *);
    
    	spinlock_t lock;
    
    	/* protected by vc.lock */
    	struct list_head desc_allocated;
    	struct list_head desc_submitted;
    	struct list_head desc_issued;
    	struct list_head desc_completed;
    
    	struct virt_dma_desc *cyclic;
    };
    

    里面包含了void (*desc_free)(struct virt_dma_desc *); desc_free 函数。
    来看一下desc_free 函数在哪被调用?

    sun6i_dma_probe
        vchan->vc.desc_free = sun6i_dma_free_desc;
        vchan_init
          tasklet_init(&vc->task, vchan_complete, (unsigned long)vc)
            vchan_complete
    		  list_for_each_entry_safe(vd, _vd, &head, node) {
    				dmaengine_desc_get_callback(&vd->tx, &cb);
    		
    				list_del(&vd->node);
    				if (dmaengine_desc_test_reuse(&vd->tx))
    					list_add(&vd->node, &vc->desc_allocated);
    				else
    					vc->desc_free(vd);		     //最终调用 sun6i_dma_free_desc
    				dmaengine_desc_callback_invoke(&cb, NULL);
    			}
    
      ret = devm_request_irq(&pdev->dev, sdc->irq, sun6i_dma_interrupt, 0,
    			       dev_name(&pdev->dev), sdc);
        sun6i_dma_interrupt                               // dma 中断处理函数
          vchan_cyclic_callback 
            tasklet_schedule(&vc->task)              //这里之后调用 vchan_complete
    
    
    static void sun6i_dma_free_desc(struct virt_dma_desc *vd)
    {
    	struct sun6i_desc *txd = to_sun6i_desc(&vd->tx);
    	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(vd->tx.chan->device);
    	struct sun6i_dma_lli *v_lli, *v_next;
    	dma_addr_t p_lli, p_next;
    
    	if (unlikely(!txd))
    		return;
    
    	p_lli = txd->p_lli;
    	v_lli = txd->v_lli;
    
    	while (v_lli) {
    		v_next = v_lli->v_lli_next;
    		p_next = v_lli->p_lli_next;
    
    		dma_pool_free(sdev->pool, v_lli, p_lli);
    
    		v_lli = v_next;
    		p_lli = p_next;
    	}
    
    	kfree(txd);    //释放内存
    }
    
    展开全文
  • c++中内存拷贝函数(C++ memcpy)详解

    万次阅读 多人点赞 2012-09-17 21:30:26
    原型:void*memcpy(void*dest, const void*src,unsigned int count);  功能:由src所指内存区域复制count个字节到dest...说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。  举例:  // memcpy.c

    原型:void*memcpy(void*dest, const void*src,unsigned int count); 

    功能:由src所指内存区域复制count个字节到dest所指内存区域。  

    说明:srcdest所指内存区域不能重叠,函数返回指向dest的指针。    

    举例:           

    //   memcpy.c                       
    #include   <stdlib.h>           
    #include   <string.h>           
    main()           
    {               
    	char *s= "Golden  Global   View ";
    	char d[20];
    	clrscr();
    	memcpy(d,s,strlen(s));
    	d[strlen(s)]=0;
    	printf( "%s ",d);
    	getchar();
    	return   0;
    }

    下面自行实现这个函数

    程序清单 1 V0.1版程序 

    void MyMemMove(char *dst,char *src,int count) 
    { 
    	while(count--) 
    		*dst++ = *src++; 
    } 
    程序清单 2 测试V0.1用例 
    void Test() 
    { 
    	char p1[256] = ”hello,world!”; 
    	char p2[256] = {0}; 
    	MyMemMove(p2,p1,strlen(p1)); 
    	printf(“%s”,p2); 
    } 
        客观地讲,相比那些交白卷或者函数声明都不会写的同学来说,能够写出这段代码的同学已经非常不错了,至少在C语言这门课程上已经达到了现行高校的教育目标,但是离企业的用人要求还有一定的距离。我们不妨将上面的程序称为V0.1版本,看看还有没有什么地方可以改进。 
       首先我们看看函数声明是否合理,V0.1版的程序将源地址和目的地址都用char *来表示,这样当然也没有什么问题,但是让其他人使用起来却很不方便,假如现在要将count个连续的结构体对象移动到另外一个地方去,如果要使用v0.1的程序的话,正确的写法如下: 
        MyMemMove((char *)dst,(char *)src,sizeof(TheStruct)*count); 
    也就是说我们需要将结构体指针强制转换成char * 才能够正常工作,这样除了字符串以外其它的类型都不可避免地要进行指针强制转换,否则编译器就会呱呱叫,比如在VC++2008下就会出现这样的错误: 
    error C2664: 'MyMemMove' : cannot convert parameter 1 from 'TheStruct *'to 'char *' ;那么如何解决这个问题呢?其实很简单,我们知道有一种特别的指针,任何类型的指针都可以对它赋值,那就是void *,所以应该将源地址和目的地址都用void*来表示。当然函数体的内容也要作相应的改变,这样我们就得到了V0.2版的程序。 
    程序清单 3 V0.2版程序 

    void MyMemMove(void *dst,void *src,int count) 
    { 
    	while (count--) 
    	{ 
    		*(char *)dst = *(char *)src; 
    		dst = (char *)dst + 1; 
    		src = (char *)src + 1; 
    	} 
    } 
    有的同学可能会问,这里面不是还有指针强制转换吗?只不过是换了地方。没错,强制指针转换确实是从使用者的代码转移到了库的代码里,但我们可以将 MyMemMove理解为库,而将Test理解为使用者,事实上通过调整之后的效果却有天壤之别,V0.1是一逸永劳,而V0.2是一劳永逸! 
         还有几个细节需要注意,为了实现链式表达式,我们应该将返回值也改为void *。此外,如果我们不小心将“*(char *)dst = *(char *)src;”写反了,写成“*(char *)src =*(char *)dst;”编译照样通过,而为了找出这个错误又得花费不少时间。注意到src所指向的内容在这个函数内不应该被改变,所有对src所指的内容赋值都应该被禁止,所以这个参数应该用const修饰,如果有类似的错误在编译时就能够被发现: 
    error C3892: 'src' : you cannot assign to a variable that is const ;作为程序员犯错误在所难免,但是我们可以利用相对难犯错误的机器,也就是编译器来降低犯错误的概率,这样我们就得到了V0.3版的程序。 
    程序清单 4 V0.3版程序 

    void * MyMemMove(void *dst,const void *src,int count) 
    { 
    	void *ret=dst; 
    	while (count--) 
    	{ 
    		*(char *)dst = *(char *)src; 
    		dst = (char *)dst + 1; 
    		src = (char *)src + 1; 
    	} 
    	return ret;
    } 
         现在再来考虑这样一种情况,有使用者这样调用库:MyMemMove(NULL,src, count),这是完全可能的,因为一般来说这些地址都是程序计算出来的,那就难免会算错,出现零地址或者其它的非法地址也不足为奇。可以预料的是,如果出现这种情况的话,则程序马上就会down掉,更糟糕的是你不知道错误出在哪里,于是不得不投入大量的精力在浩瀚的代码中寻找bug。解决这类问题的通用办法是对输入参数作合法性检查,也就是V0.4版程序。 
    程序清单 5 V0.4版程序 

    void * MyMemMove(void *dst,const void *src,int count) 
    { 
    	void *ret=dst; 
    	if (NULL==dst||NULL ==src) 
    	{ 
    		return dst; 
    	} 
    	while (count--) 
    	{ 
    		*(char *)dst = *(char *)src; 
    	    dst = (char *)dst + 1; 
    		src = (char *)src + 1; 
    	} 
    	return ret; 
    } 

         上面之所以写成“if(NULL==dst||NULL ==src)”而不是写成“if (dst == NULL || src == NULL)”,也是为了降低犯错误的概率。我们知道,在C语言里面“==”和“=”都是合法的运算符,如果我们不小心写成了“if (dst = NULL || src = NULL)”还是可以编译通过,而意思却完全不一样了,但是如果写成“if (NULL=dst||NULL =src)”,则编译的时候就通不过了,所以我们要养成良好的程序设计习惯:常量与变量作条件判断时应该把常量写在前面。V0.4版的代码首先对参数进行合法性检查,如果不合法就直接返回,这样虽然程序dwon掉的可能性降低了,但是性能却大打折扣了,因为每次调用都会进行一次判断,特别是频繁的调用和性能要求比较高的场合,它在性能上的损失就不可小觑。如果通过长期的严格测试,能够保证使用者不会使用零地址作为参数调用MyMemMove函数,则希望有简单的方法关掉参数合法性检查。我们知道宏就有这种开关的作用,所以V0.5版程序也就出来了。 

    程序清单 6 V0.5版程序

    void * MyMemMove(void *dst,const void *src,int count) 
    { 
    	void *ret=dst; 
    #ifdef DEBUG 
    	if (NULL==dst||NULL ==src) 
    	{ 
    		return dst; 
    	} 
    #endif 
    	while (count--) 
    	{ 
    		*(char *)dst = *(char *)src; 
    		dst = (char *)dst + 1; 
    		src = (char *)src + 1; 
    	} 
    	return ret; 
    } 
         如果在调试时我们加入“#defineDEBUG”语句,增强程序的健壮性,那么在调试通过后我们再改为“#undef DEBUG”语句,提高程序的性能。事实上在标准库里已经存在类似功能的宏:assert,而且更加好用,它还可以在定义DEBUG时指出代码在那一行检查失败,而在没有定义DEBUG时完全可以把它当作不存在。assert(_Expression)的使用非常简单,当_Expression为0时,调试器就可以出现一个调试错误,有了这个好东西代码就容易多了。 
    程序清单 7 V0.6版程序 

    void * MyMemMove(void *dst,const void *src,int count) 
    { 
    	assert(dst); 
    	assert(src); 
    	void *ret=dst; 
    	while (count--) 
    	{ 
    		*(char *)dst = *(char *)src; 
    		dst = (char *)dst + 1; 
    		src = (char *)src + 1; 
    	} 
    	return ret; 
    } 
        到目前为止,在语言层面上,我们的程序基本上没有什么问题了,那么是否真的就没有问题了呢?这就要求程序员从逻辑上考虑了,这也是优秀程序员必须具备的素质,那就是思维的严谨性,否则程序就会有非常隐藏的bug,就这个例子来说,如果用户用下面的代码来调用你的程序。 
    程序清单 8 重叠的内存测试 

    void Test() 
    { 
    	char p [256]= "hello,world!"; 
    	MyMemMove(p+1,p,strlen(p)+1); 
    	printf("%s\n",p); 
    } 
        如果你身边有电脑,你可以试一下,你会发现输出并不是我们期待的“hhello,world!”(在“hello world!”前加个h),而是“hhhhhhhhhhhhhh”,这是什么原因呢?原因出在源地址区间和目的地址区间有重叠的地方,V0.6版的程序无意之中将源地址区间的内容修改了!有些反映快的同学马上会说我从高地址开始拷贝。粗略地看,似乎能解决这个问题,虽然区间是重叠了,但是在修改以前已经拷贝了,所以不影响结果。但是仔细一想,这其实是犯了和上面一样的思维不严谨的错误,因为用户这样调用还是会出错: 
    MyMemMove( p, p+1, strlen(p)+1); 所以最完美的解决方案还是判断源地址和目的地址的大小,才决定到底是从高地址开始拷贝还是低地址开始拷贝,所以V0.7顺利成章地出来了。 
    程序清单 9 V0.7版程序 

    void * MyMemMove(void *dst,const void *src,int count) 
    { 
    	assert(dst); 
        assert(src); 
    	void * ret = dst; 
    	if (dst <= src || (char *)dst >= ((char *)src + count)) { 
    		while (count--) { 
    			*(char *)dst = *(char *)src; 
    			dst = (char *)dst + 1; 
    			src = (char *)src + 1; 
    		} 
    	} 
    	else { 
    		dst = (char *)dst + count - 1; 
    		src = (char *)src + count - 1; 
    		while (count--) { 
    			*(char *)dst = *(char *)src; 
    			dst = (char *)dst - 1; 
    		src = (char *)src - 1; 
    		} 
    	} 
    	return(ret); 
    } 
        经过以上7个版本的修改,我们的程序终于可以算是“工业级”了。回头再来看看前面的测试用例,就会发现那根本就算不上是测试用例,因为它只调用了最正常的一种情况,根本达不到测试的目的。有了上面的经历,测试用例也就相应地出现了,我们不妨用字符数组来模拟内存。 
    程序清单 10 相对全面的测试用例 

    void Test() 
    { 
    	char p1[256] = "hello,world!"; 
    	char p2[256] = {0}; 
    	MyMemMove(p2,p1,strlen(p1)+1); 
    	printf("%s\n",p2); 
    	MyMemMove(NULL,p1,strlen(p1)+1); 
    	MyMemMove(p2,NULL,strlen(p1)+1); 
    	MyMemMove(p1+1,p1,strlen(p1)+1); 
    	printf("%s\n",p1); 
    	MyMemMove(p1,p1+1,strlen(p1)+1); 
    	printf("%s\n",p1); 
    }  

    void * memcpy ( void * dst,const void * src,size_t count)
    {
    	void * ret = dst;
    	while (count--) {
    		*(char *)dst = *(char *)src;
    		dst = (char *)dst + 1;
    		src = (char *)src + 1;
    	}
    	return(ret);
    }
    char *strcpy(char *des, const char *src){
    	assert((des != NULL) && (src != NULL));
    	char *ret = des; // 防止改变des的地址
    	while ((*des++ = *src++) !='\0') ;
    	return ret;
    }


    展开全文
  • Linux memcpy 内存拷贝 注意问题

    千次阅读 2019-04-24 11:22:31
    在使用Linux C 编程时,用到了 memcpy 函数,主要是实现把文件1通过环形缓冲区拷贝到文件2,在使用时,由于函数第二个元素定义了一个 int 类型的数组,导致复制结果怎么都不对,查看上述文章才找到...
  • Linux 内存映射函数 mmap()函数详解

    万次阅读 多人点赞 2016-08-07 00:01:11
    mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。 头文件 函数原型 void* mmap...
  • Linux内核中内存相关的操作函数

    千次阅读 2014-02-28 17:09:33
     Linux内核中采 用了一种同时适用于32位和64位系统的内 存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系 统中,用到了四级页表,如图2-1所示。四级页表分别为:  * 页全局目录(Page Global ...
  • 内存拷贝的注意事项

    千次阅读 2017-06-19 12:29:01
    有道面试题是让写出memcpy的实现,memcpy是c和c++使用的内存拷贝函数,功能是从源地址所指的内存地址的起始位置开始拷贝n个字节到目标地址所指的内存地址的起始位置中。与此类似的,在使用strcpy的时候,也应该需要...
  • 避免不必要的内存拷贝和清0

    千次阅读 2018-09-09 21:50:57
    对于网络编程中,一般都喜欢使用memset清0和memcpy拷贝操作,举个例子: char buffer[1024]; memset(buffer, 0, 1024); memcpy(buffer, proxy_hdr, IPC_HEADER_SZ); memcpy(buffer, trans_hdr, TRANMIT_HEADER_SZ);...
  • linux内核零拷贝技术

    千次阅读 2021-11-19 17:42:33
    1.用户进程通过 read() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space) 2.CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read ...
  • LinuxLinux的共享内存

    万次阅读 多人点赞 2018-08-10 19:17:45
    实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号...
  • Linux c字符串拷贝函数

    千次阅读 2013-11-01 20:45:32
    函数说明:memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节, 不会因为遇到字符串结束'\0'而结束。 返回值:返回指向dest的指针。 附加...
  • Linux虚拟内存空间分布

    千次阅读 2019-03-03 16:38:03
    程序只是一段可以执行的代码文件,通俗讲在 linux 上就是一个可执行文件。当一个程序运 行时就被称为进程,即进程是运行状态的程序。 程序存储了一系列文件信息,这些信息描述了如何在运行时创建一个进程,包含...
  • Linux内核之mmap()内存映射函数

    千次阅读 2017-09-15 11:10:17
    linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_...
  • 日期 内核版本 架构 作者 GitHub ... Linux内存管理 在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换
  • linux内存分配与回收

    千次阅读 2017-10-30 12:51:52
    之前在实习时,听了OOM的分享之后,就对linux内核内存管理充满兴趣;但是这块知识非常庞大,没有一定积累,不敢写下,担心误人子弟;...之前也有写过linux内存管理,那篇文章主要是linux内存管理,这篇
  • Linux小知识--零拷贝技术相关函数

    千次阅读 2021-11-22 11:36:04
    介绍了零拷贝技术的三个常用函数
  • 本文讨论的是关于内存复制中内存重叠和不重叠的情况: memmove /** * memmove - Copy one area of memory to another * @dest: Where to copy to * @src: Where to copy from * @count: The size of the area. ...
  • 基础知识:内核内存地址 3G-4G;用户内存地址 0G-3G 虚拟指针的最高两位[00 -11) 说明时用户虚拟地址;最高两位11 内核虚拟地址 根据虚拟地址的最高两位 来判断访问TTBR0_EL1 或者TTBR0_EL0; TTBR0_EL 存放页表基...
  • 原来刚刚开始做Linux下面的多进程编程的时候,对于下面这段代码感到很奇怪:  #include  #include  #include  #include  #include  #include  #define LEN 2  void err_exit(char*fmt,…);  int ...
  • linux内存映射(一)

    千次阅读 2019-06-07 11:30:21
    内存映射原理 由于所有用户进程总的虚拟地址空间比可用的物理内存大很多,因此只有最常用的部分才与物理页帧关联。这不是问题,因为大多数程序只占用实际可用内存的一小部分。在将磁盘上的数据映射到进程的虚拟...
  • linux内存实际占用分析

    千次阅读 2018-07-27 18:07:23
    ”导致大多数人认为许多Linux应用程序,特别是KDE或GNOME程序都象ps报告一样臃肿...【51CTO.com独家译文】本文是为那些经常疑惑的人准备的,“为什么一个简单的KDE文本编辑器要占用25M内存?”导致大多数人认为许多...
  • Linux函数调用与栈

    千次阅读 2017-06-05 17:31:36
    栈与函数调用惯例(又称调用约定)— 基础篇 记得一年半前参加百度的校招面试时,被问到函数调用惯例的问题。当时只是懂个大概,比如常见函数调用约定类型及对应的参数入栈顺序等。最近看书过程中,重新回顾了这些...
  • 共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU...
  • 内存拷贝详解

    千次阅读 2021-11-25 10:32:47
    拷贝拷贝之mmap 零拷贝之sendfile 前置知识 标准设备 一个标准的硬件设备包括两部分: 面向系统其他部分展现的硬件接口(类似于软件的接口),供其他硬件或软件调用 内部结构,包括设备相关的特定实现,...
  • 在copy_from_user里如何判断参数const void __user *from是哪个进程的用户空间地址?
  •  当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。 但当数据成员中有指针...
  • linux内存管理原理深入理解段式页式

    万次阅读 多人点赞 2017-07-20 08:52:39
    前一段时间看了《深入理解Linux内核》对其中的内存管理部分花了不少时间,但是还是有很多问题不是很清楚,最近又花了一些时间复习了一下,在这里记录下自己的理解和对Linux内存管理的一些看法和认识。  我比较...
  • linux内存管理函数mmap和brk

    千次阅读 2015-03-10 15:42:10
    linux内核提供mmap和brk用于管理线性内存: 一,brk linux man page描述如下: brk为系统调用,sbrk给glibc对于系统调用brk的封装: 从man里面可以看到这brk的原理主要是通过修改program break(数据段的结尾,the...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 108,043
精华内容 43,217
关键字:

linux内存拷贝函数

linux 订阅