glibc memcpy() 源码浅谈
其实我本来只是想搞懂为什么memcpy()函数的参数类型是void *的:
我以为会在memcpy()源码中能找到答案,其实并没有,void *只是在传递参数的时候起了作用,可以让memcpy()接受不同的指针类型,比如char *,double *,struct stu *等等,没错,只是这样,至于memcpy()内部的工作原理,请继续往下看。
没办法,我只好去找找memcpy的源码。代码如下。
备注:glibc-2.8 memcpy.c
#include <string.h>
#include <memcopy.h>
#include <pagecopy.h>
#undef memcpy
void *
memcpy (dstpp, srcpp, len)
void *dstpp;
const void *srcpp;
size_t len;
{
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
/* Copy from the beginning to the end. */
/* If there not too few bytes to copy, use word copy. */
if (len >= OP_T_THRES) //OP_T_THRES=16
{
/* Copy just a few bytes to make DSTP aligned. */
len -= (-dstp) % OPSIZ; //OPSIZ=sizeof(unsigned long int)=8
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
/* Copy whole pages from SRCP to DSTP by virtual address manipulation,
as much as possible. */
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
/* Copy from SRCP to DSTP taking advantage of the known alignment of
DSTP. Number of bytes remaining is put in the third argument,
i.e. in LEN. This number may vary from machine to machine. */
WORD_COPY_FWD (dstp, srcp, len, len);
/* Fall out and copy the tail. */
}
/* There are just a few bytes to copy. Use byte memory operations. */
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}
libc_hidden_builtin_def (memcpy)
整个函数的处理流程如下所示:
1:函数参数
void *
memcpy (dstpp, srcpp, len)
void *dstpp;
const void *srcpp;
size_t len;
忽略这种传参的形式,传入的三个参数分别是目的地址(void *dstpp)、源地址(const void *srcpp)、长度(size_t len)。
2:地址被转换成unsigned long int保存。
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
8个字节的地址被转换成unsigned long int保存,因为sizeof(unsigned long int) = 8,刚好是8字节,如果还是不能理解把地址转成unsigned long int,看下面的程序:
#include<stdio.h>
int main(int argc,char *argv[])
{
int a = 256;
int *p = &a;
unsigned long int ds = (long int)p;
printf("%d %d\n",*(int *)ds,sizeof(unsigned long int));
return 0;
}
地址只有一个唯一的ds,上面的程序是将ds转成int*,是因为我们将它按照int对待了,那如果我想以字节char处理它呢,那是不是转成char *就行了,我们用下面这张图先分析下上面代码的情况。
a 变量的值为256,是一个int 类型的,4 个字节,在内存中的分布就像上面所示。我们将地址转成int *,那么接着我就会处理四个字节,如果我将地址转成char *,那我只处理一个字节不就好了。代码变成这样:
#include<stdio.h>
int main(int argc,char *argv[])
{
int a = 256;
int *p = &a;
unsigned long int ds = (long int)p;
printf("%d %d\n",*(char *)ds,sizeof(unsigned long int));
return 0;
}
果然,输出的值变为了0,就是我们上面图中char *只指向第一个字节。为0。
好了,说了这么多,有什么用呢?
不要着急,我们继续往下走看源码;
3:拷贝数量较小,采用 one byte one byte拷贝。
if (len >= OP_T_THRES) //OP_T_THRES=16 {
......
}
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}
如果 len >= OP_T_THRES(OP_T_THRES在不同的系统或者平台有不同的值,通常为16或者8),那就按照if中的结构处理,否则执行BYTE_COPY_FWD (dstp, srcp, len)。也就是拷贝的长度如果大于16或者8,我们采用if中的策略,如果小于16或者8,就 one byte one byte拷贝。
4:如果拷贝数量较大,采用if中的策略
数量较大势必会牵扯到内存对齐的问题:
/* Copy just a few bytes to make DSTP aligned. */
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
(-dstp) % OPSIZ这个神奇的式子可以算出来对齐需要移动的字节数,就像下面这样:
这一部分比较少的字节仍然采用 one byte one byte拷贝。
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ); //传的参数为(-dstp) % OPSIZ),就是对齐需要拷贝的字节数
此时的len就为对齐之后的剩余大小了,之后的处理方式直接按照虚拟内存页的大小来加快拷贝效率。
/* Copy whole pages from SRCP to DSTP by virtual address manipulation,as much as possible. */
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
如果后面还有剩余部分,还可以采用一个字一个字拷贝:
WORD_COPY_FWD (dstp, srcp, len, len);
/* Fall out and copy the tail. */
大家肯定注意到了源码中的三个函数:
- BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
- PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
- WORD_COPY_FWD (dstp, srcp, len, len);
其中 dstp 和 srcp 都是保存了地址的 unsigned long int类型的数。那为什么传入它们竟然能实现在按照不同的类型byte,page,word来拷贝呢(忽略有没有page 这种类型的细节,我只是想说前面三种类型大小是不同的),那一定在这三个函数中做了类似与我们前面举的例子的转化,我们去找找看,果然,找到一些情况。
在memcopy.h中
转成 byte *再去操作,因为是one byte one byte拷贝。
在pagecopy.h中
转成vm_address_t类型去处理。
在wordcopy.c中
其中 op_t是一个宏定义,为unsigned long int。
5: 总结
说了这么多,我们基本从宏观上缕清了memcpy的流程。
- 先用
unsigned long int保存地址 - 然后判断需要拷贝的大小,如果小于
OP_T_THRES,直接one byte one byte拷贝。 - 如果大于
OP_T_THRES,首先对齐。然后需要对齐的部分按照one byte one byte拷贝。对齐之后的按照one page one page拷贝,最后剩余的还可以按照one word one word拷贝。
至于每种拷贝方式的细节我没有深究下去,主要是能力有限,但是还是学到了memcpy()这种鸡贼的对于地址处理方法,地址只是地址,想具体指向多大范围取决于将它转换成那种类型。看到源码里面有goto语句,我的内心几乎是崩溃的,就先写到这儿吧,欢迎大家评论交流指正~
glibc memcpy() 源码浅谈的更多相关文章
- 【Android测试】【第七节】Monkey——源码浅谈
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4713466.html 前言 根据上一篇我们学会了Monke ...
- 【Android测试】【第三节】ADB——源码浅谈
◆版权声明:本文出自carter_dream的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4651724.html 前言 由于本人精力 ...
- 源码浅谈(一):java中的 toString()方法
前言: toString()方法 相信大家都用到过,一般用于以字符串的形式返回对象的相关数据. 最近项目中需要对一个ArrayList<ArrayList<Integer>> ...
- 源码浅谈(二):java中的 Integer.parseInt(String str)方法
这个方法是将字符串转换为整型 一.parseInt方法 ,可以看到默认又调用了parseInt(s,10) , 第二个参数为基数,默认10 ,当然也可以自己设置 public static int ...
- 结合源码浅谈Spring容器与其子容器Spring MVC 冲突问题
容器是整个Spring 框架的核心思想,用来管理Bean的整个生命周期. 一个项目中引入Spring和SpringMVC这两个框架,Spring是父容器,SpringMVC是其子容器,子容器可以看见父 ...
- Spark 源码浅读-SparkSubmit
Spark 源码浅读-任务提交SparkSubmit main方法 main方法主要用于初始化日志,然后接着调用doSubmit方法. override def main(args: Array[St ...
- Handlebars模板引擎中的each嵌套及源码浅读
若显示效果不佳,可移步到愚安的小窝 Handlebars模板引擎作为时下最流行的模板引擎之一,已然在开发中为我们提供了无数便利.作为一款无语义的模板引擎,Handlebars只提供极少的helper函 ...
- Backbone.js源码浅介
终于看到一个只有一千多行的js框架了,于是抱着一定可以看懂他的逻辑的心态,查看了他的整个源码,进去之后才发现看明白怎么用容易,看懂怎么写的就难了,于是乎有了这篇博客的标题:浅介,只能粗浅的介绍下Bac ...
- 从极速飞艇源码 VantComponent 谈 小程序维护
在开发极速飞艇源码详情咨询Q166848365小程序的时候,我们总是期望用以往的技术规范和语法特点来书写当前的小程序,所以才会有各色的小程序框架,例如 mpvue.taro 等这些编译型框架.当然这些 ...
随机推荐
- Shell系列(23)- 字符截取命令sed
简述 字符替换命令sed 和vi功能相似,但是vi是给用户用的,sed是给脚本用的 sed是一种几乎包括在所有的UNIX平台(包括Linux)的轻量级流编辑器.s sed主要是用来将数据进行选取.替换 ...
- 分布式文件系统FastDFS在CentOS7上的安装及与Springboot的整合
1. 概述 FastDFS 是目前比较流行的分布式文件系统,可以很容易的实现横向扩展.动态扩容.灾备.高可用和负载均衡. FastDFS 的服务分为 tracker 服务 和 storage 服务, ...
- Java对象构造
关于对象构造的一些认识. 默认域初始化 如果在构造器中没有显示地给域赋予初值,那么就会被自动地赋予默认值:数值为0,布尔值为false,对象引用为null.然而,这显然是不安全的,在一个null引用上 ...
- 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 百篇博客分析OpenHarmony源码 | v44.02
百篇博客系列篇.本篇为: v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- P6775-[NOI2020]制作菜品【贪心,dp】
正题 题目链接:https://www.luogu.com.cn/problem/P6775 题目大意 \(n\)种原材料,第\(i\)个有\(d_i\)个,\(m\)道菜品都需要\(k\)个原料而且 ...
- MySQL表空间回收的正确姿势
不知道大家有没有遇到这样的一种情况,线上业务在MySQL表上做增删改查操作,随着时间的推移,表里面的数据越来越多,表数据文件越来越大,数据库占用的空间自然也逐渐增长 为了缩小磁盘上表数据文件占用的空间 ...
- FastAPI(64)- Settings and Environment Variables 配置项和环境变量
背景 在许多情况下,应用程序可能需要一些外部设置或配置,例如密钥.数据库凭据.电子邮件服务凭据等. 大多数这些设置都是可变的(可以更改),例如数据库 URL,很多可能是敏感数据,比如密码 出于这个原因 ...
- ThreadLocal概念以及使用场景
ThreadLocal概念以及使用场景 根据自身的知识深度,这里只限于自己使用和学习的知识点整理,原理的解释还需要再沉淀. 该文章从项目开发中举例,希望能帮助到各位,不了解ThreadLocal的朋友 ...
- Java爬虫系列四:使用selenium-java爬取js异步请求的数据
在之前的系列文章中介绍了如何使用httpclient抓取页面html以及如何用jsoup分析html源文件内容得到我们想要的数据,但是有时候通过这两种方式不能正常抓取到我们想要的数据,比如看如下例子. ...
- Perl 编程 基础用法
Perl 编程 标准头部写法 #!/usr/bin/perl -w # 标准的头部写法,-w意为显示警告 变量 $a=$b+10 # $a和$b都不需要定义,拿过来就用 Note: $flag=0 如 ...