为什么objc_msgSend必须用汇编实现

译者前言

 

总是看到有人说用汇编实现objc_msgSend是为了速度快,当然这个不可否认。但是难道没有别的原因?于是就看到了这篇文章,遂翻译之!=。=

 

我自己的理解就是,用汇编实现,是为了应对不同的“Calling convention”,把函数调用前的栈和寄存器的参数、状态设置,交给编译器去处理。

 

先看看原文吧。

 

原作者: Ari Grant

原文链接: Why objc_msgSend Must be Written in Assembly

http://arigrant.com/blog/2014/2/12/why-objcmsgsend-must-be-written-in-assembly

 

开始

 

对于Objective-C来说,调用一个对象实例的方法,也叫作向这个对象实例“发送消息”,而每条“消息”,在编译阶段都会转变为一次对objc_msgSend函数的调用,调用的参数不仅有原本消息的所有参数,还有消息的接收者receiver和对应的方法selector。举个例子,下面的语句:

 

[receiver message:foo beforeDate:bar];

 

将会被编译成:

 

objc_msgSend(receiver, @selector(message:beforeDate:), foo, bar);

 

对于objc_msgSend函数的实现原理,前人已经做了大量的探索。所以,本文将会把重点放在objc_msgSend的一个之前没有太受到关注的点上,那就是:

 

objc_msgSend是不可能用Objective-C、C或者C++实现的。

 

THE RETURN TYPE – 返回类型

 

先看看如下两行代码:

 

NSUInteger n = [array count];

id obj = [array objectAtIndex:6];

 

直观上看,将会被编译成

 

NSUInteger n = objc_msgSend(array,  @selector(count));

id obj = objc_msgSend(array, @selector(objectAtIndex:), 6);

 

但是实际上这是不可能的,因为没有函数可以同时满足这两个调用。而且它的返回值也不能同时是NSUInteger和id。

 

而且,上面的代码也是无法编译通过的。那么,加上类型转换怎么样?

 

NSUInteger n = (NSUInteger (*)(id, SEL))objc_msgSend(array,  @selector(count));

id obj = (id (*)(id, SEL, NSUInteger))objc_msgSend(array, @selector(objectAtIndex:), 6);

 

这下可以编译通过了,虽然看起来不直观。。。

objc_msgSend是一个Public的函数,在里声明,如果你想直接调用它,就必须按照上面的格式加上强制类型转换,要不然是无法编译通过的。但是objc_msgSend到底是如何实现,来支持各种返回类型的?本文后面会讲到。

 

THE IMP – 方法对应的函数指针

 

objc_msgSend函数的本质很简单,传入一个接受者对象实例receiver和方法名selector,它就会按照以下步骤执行:(译者注:只是最粗略的步骤=。=)

 

  • 获取receiver得类Class

  • 在Class的方法列表method table里面查找对应selector的方法实现

  • 找到的话就调用,返回

  • 找不到就在其父类中找,重复前面的步骤(直到没有父类为止)

 

整个流程很简单,沿着继承链,向上找到方法selector对应的函数指针即可,也就是IMP。同时,在每层Class中都有缓存,加快后续的方法查找。但是,这也只是objc_msgSend的实现细节,所以,接着往下看。

 

THE ARG TYPES AND COUNT – 参数类型和数量

 

简单来说,当objc_msgSend找到对应的函数指针后,只要用传入的参数调用这个函数即可。剩下来的就是找到一种方法,可以调用任意参数类型、数量的任意函数。

 

参数的数量很容易计算。然后我们可以把所有的参数都放入varargs,然后调用函数时传入即可。但是这样的话,每个Objective-C的方法都必须在其prologue(译者注:函数执行具体的“任务”前,所做的准备环节)里面把所有的参数从varargs里面提取出来。

 

这种把参数打包到varargs里面然后又取出来的办法显然是非常糟糕的,同时也是不必要的。

 

在C语言中,调用一个函数会被编译成对应的汇编语言指令,首先是设置参数(把参数放到寄存器、栈上),然后用如jump或者call的指令,跳到具体的函数代码地址处。如果我们想支持任意类型的函数类型,我们就必须写一个switch语句,把所有的参数组合情况都包含起来,这样才能正确的为任何形式的函数设置参数(译者注:即按照某种“规范”、“约定”,把参数依次存放到“约定”的寄存器、栈上),这显然是没有扩展性的,更是不可能的。

 

UNWINDING THE CALL – 拆解调用

 

objc_msgSend的解决办法,主要依据的是:当objc_msgSend被调用时,所有的参数已经被设置好了。

 

换一种方式来说,就是:在objc_msgSend开始执行时,栈帧(stack frame)的状态、数据,和各个寄存器的组合形式、数据,跟调用具体的函数指针(IMP)时所需的状态、数据,是完全一致的!

 

如下这行代码:

 

id obj = objc_msgSend(array, @selector(objectAtIndex:), 6);

 

在调用objc_msgSend时,需要设置三个参数,分别是被调用方receiver、方法名selector和最后一个整型参数6。这和具体的方法函数IMP的参数顺序、类型是完全一致的,也就是说,调用objc_msgSend前,设置的栈、寄存器的状态、数据正是调用具体的方法函数时需要的状态!

 

所以,当objc_msgSend找到要调用的函数实现IMP后,只需要把所有的对栈、寄存器的操作“倒”回到objc_msgSend执行开始的状态(类似于函数执行完成return返回前,做的“收尾处理”工作一样,即epilogue),直接jump/call到IMP函数指针对应的地址,执行指令即可,因为所有的参数已经被设置好了。

 

同时,当selector对应的IMP执行完成后,返回值也被正确的设置好了(在x86平台上,返回值被设置到了指定的寄存器eax/rax里,在arm上,则是r0寄存器),所以,我们也不必担心前文提到的不同类型的返回值问题了。

 

WRAP UP – 总结

 

把上面提到的所有解释综合起来,就是:在C语言里面调用函数,必须在编译时就知道调用的“状态”;而这些“状态”在运行时是无法得出或正确处理的,所以必须往底层走,用汇编处理。(译者注:这里不知道咋翻译好=。=,原文是:calling a function in C requires the signature to be known for each call-site at compile-time;doing so at run-time is not possible and so one must drop down into assembly and party there instead.)

 

UPDATE – 后续

 

有人指出objc_msgSend有可能是用GCC的扩展方法__builtin_apply_args,__builtin_apply,和__builtin_return实现的。这也正指出了一个事实,就是这些builtins方法是非常有必要的,因为单靠语言本身无法实现这些功能。实现objc_msgSend所需要的技巧,也正是实现这些builtins方法所需要的技巧。本文的目的并不是非要将什么是真正的C、什么不是真正的C分个清楚,只是为了指出objc_msgSend特殊罢了。

 

译者总结

 

开头也说了,我的理解是:用汇编实现,是为了应对不同的“Calling convention”,把函数调用前的栈和寄存器的参数、状态设置,交给编译器去处理。

 

嗯,以后不要再说用汇编实现只是为了快了=。=

转载于:https://www.cnblogs.com/fengmin/p/5619115.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/418362.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端学习(1989)vue之电商管理系统电商系统之渲染商品列表数据

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

前端学习(1990)vue之电商管理系统电商系统之自定义时间过滤器

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

【开源】.net 分布式架构之监控平台

开源地址: http://git.oschina.net/chejiangyi/Dyd.BaseService.Monitor .net 简单监控平台,用于集群的性能监控,应用耗时监控管理,统一日志管理等多维度的性能监控分析。集群的性能监控:(需要服务器部署监控节点) 1. 目前仅支持w…

hashmap有关问题与计算

1.HashMap的存储方式是数组加链表,主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对;当不同的key经过hash计算得出的index值相同时,就需要在数组里添加一个链表来存储index相同的元素&…

前端学习(1991)vue之电商管理系统电商系统之实现商品的分页功能

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

java httpclient发送json 请求 ,go服务端接收

/***java客户端发送http请求*/package com.xx.httptest;/*** Created by yq on 16/6/27.*/import java.io.IOException; import java.net.URLEncoder; import org.apache.commons.httpclient.*; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.co…

linux 定时备份mysql数据库

首先要先搞清楚两个概念: ①、mysqldump,mysqldump是mysql的逻辑备份工具,它不是linux的命令,工作原理类似产生一些列sql语句,对数据库进行指定的逻辑备份。 最简洁的形式是:mysqldump -uuserName -ppassW…

前端学习(1992)vue之电商管理系统电商系统之实现搜索和清空

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

4123: 马走日,2797:最短前缀 Trie,2362:Square 能否拼接为正方形

4123: 马走日 深度优先搜索 回溯 ///马走日 int f[21][22],n,m,t; //f数组记录点有没有走过int x[10]{-2,-2,-1,1,2,2,1,-1},y[10]{-1,1,2,2,1,-1,-2,-2};//八个方向 int x1,y1,ans; void dfs(int a,int b) //搜索过程 {bool ptrue; //标记,判断马是否可以遍…

前端学习(1993)vue之电商管理系统电商系统之根据id删除数据

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

苹果开发者地址1

https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app转载于:https://www.cnblogs.com/zhangchengyuan/p/5623348.html

C++算法一些常用的stl函数

1.lower_bound( )和upper_bound( ) lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的 在从小到大的排序数组中, lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字&a…

LINUX 常用命令集合

su su命令是最基本的命令之一,常用于不同用户间切换。例如,如果登录为 user1,要切换为user2,只要用如下命令: $su user2 然后系统提示输入user2口令,输入正确的口令之后就可以切换到user2。完成之后就可…

前端学习(1994)vue之电商管理系统电商系统之通过编程导航跳转到商品导航界面

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

转载CSDN博客步骤

转载CSDN博客步骤: 1.CSDN博客页面右键,点击【检查】 点击检查后,页面右侧出现html代码,如下图 2.如果需要转载全文,则在html代码下侧点击选中article_content 即可,会在代码框中自动选中article_co…

android深度探索 HAL及驱动开发 第八章

第八章 读书笔记 本章主要讲蜂鸣器的驱动,蜂鸣器是S3cC6410开发板上带的一个硬件设备,本节将介绍蜂鸣器的实现原理,并实现一个完整的蜂鸣器驱动。本节讲介绍把linux驱动分成多个文件的方式。这些文件中的数据结构、函数的代码也可以被多个不同…

前端学习(1995)vue之电商管理系统电商系统之添加页面的基本结构

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

JDK1.7和JDK1.8中HashMap是线程不安全的,并发容器ConcurrentHashMap模型

一、HashMap是线程不安全的 前言 只要是对于集合有一定了解的一定都知道HashMap是线程不安全的,我们应该使用ConcurrentHashMap。但是为什么HashMap是线程不安全的呢,之前面试的时候也遇到到这样的问题,但是当时只停留在***知道是***的层面上…

前端学习(1996)vue之电商管理系统电商系统之美化步骤条

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

三种基本背包问题

一、0/1背包问题 问题描述:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大 。 特点:这是最简单的背包问题,特点是每个物品只有一件供你选择放还是不放。 ① 二维解法 设f[i][j]表示前 i 件物品…