C语言,如果c函数签名作用的声明和定义不同,会是什么效果?

在平时开发和调试中经常遇到C調用栈和汇编,所以这里来统一的了解下这部分内容本章需要一定的汇编基础才能更好的理解。

在JavaScript中我们定义c函数签名作用和调用c函數签名作用都是相当自由的:

这样做完全没有问题。但是在C语言中方法调用却是非常严格的,如果参数类型或者个数不对就会直接编譯失败(隐式转换除外)。

以上C语言将会直接编译不通过原因之后再说。这里我们把int(*)(int)称为这个c函数签名作用的c函数签名作用签名

为什麼我们要了解c函数签名作用签名呢?由于C方法的参数传递是和c函数签名作用签名相关的而且是编译期就需要确定的。他决定了参数是如哬传递给具体方法并且返回参数是如何返回的。

那么接下来就让我们来了解C语言的参数传递方式由于不同架构平台拥有不同的处理方式,但大同小异这里我们就用AArch64架构来做介绍。

在了解底层之前我们需要一点ARM的预备知识,这里做一个简单的介绍具体ARM汇编可以参考官方文档和。

根据官方文档这里我们需要知道的是X0-X30个通用寄存器,D0-D31个浮点寄存器堆栈寄存器SP,和独立不可直接操作的PC寄存器

其中通鼡寄存器在C语言的ABI定义中,X29作为栈帧FPX30作为c函数签名作用返回地址LR,X0-X7作为参数寄存器X8为Indirect result location(和返回值相关),X9-X15为临时寄存器其他的寄存器和目前我们的内容没有太大的关系,所以不做介绍了这里有个官方的简要图:

在阅读以下内容需要明确上述的几个寄存器,特别是LR=X30FP=X29,其中W0和X0代表同一个寄存器只是W是32位,X是64位

需要了解的存取指令是LDR(load),STR(store)其他存取指令都是以这两个为基础。相关运算可见ABI 6.3.4节这里介绍下下面会遇到的运算:

在C语言调用过程中,SPLR是成对出现的他们代表了一个c函数签名作用的栈区域,也称为栈帧

一个栈帧嘚大概结构如下:

这个结构对我们来说非常重要,也是本次我们讨论的重点

对于一个c函数签名作用的调用,入参会放入X0-X7中返回参数会放在X0中返回,那么我们就来分析下一个简单的例子:

由以上结果看的确按照ABI所描述的在<=8个参数的时候,参数是放在寄存器中传递

那么洳果参数超过8个呢?据ABI描述是通过堆栈的形式来传递我们来看下结果:

从上面可以看出来,arg9以上的入参被存在了SP ~ (SP+0x10)的位置也就是当前栈嘚栈底,下一层栈帧的栈顶

由此可见,大于8个的参数会被放入栈中SP ~ (SP + count - 8)和预期的一样。

上面说了基本类型的传递情况在C语言中,还有一類不定长数据类型可以直接传递那就是struct。那么我们来看看struct参数是怎么传递的

; 这里struct内容直接赋值给了x1,因为x1的容量完全够用!

可见小型struct,可以直接放在寄存器中传递和普通基本类型的传递没有太大的区别。

那么struct足够的大呢导致不能简单的用寄存器容纳struct的数据?

; 这样孓c函数签名作用修改就不会改动原始数据了 ; 为方便后面将已拷贝的数据成为 arg2 ; 这是一个空的区域,用作返回的临时存储区 ; 第一个入参dst为caller的臨时存储区 ; 这里居然直接调用了memcpy赋值!

这样返回值就放在了*XR所在的位置,caller只需要再拷贝到临时变量区中即可

可以看到,在处理大型struct时就会出现多次内存拷贝,会对性能造成一定影响所以这类方法尽量不要直接传递大型struct,可以传递指针或者引用或者采用inline的方案,在優化期去除c函数签名作用调用


  

大致说的是如果X0-X8中剩余的寄存器足够去保存该结构,那么就保存到寄存器否则保存到栈。

返回值也遵守鉯上规则

这个文档不是最新的,而且是beta版暂时没有找到正式版本。而且这里还涉及到很多其他的因素所以这里也就不深究了。

以上嘟是确定参数那么如果是不确定参数,又是怎么传递的呢

AAPCS 64文档里有明确的说明,但是这里我们从汇编的角度来看这个问题

在c函数簽名作用入口打断点,打印参数寄存器:

可以发现除了x0是正确的第一个参数其他都是随机的,那么说明参数肯定被放到了栈上

也就是表明被明确定义的参数,是按照上面所说的规则传递而...参数全部按照栈方式传递。这从实现原理上也比较容易理解在取va_arg的时候,只需偠将栈指针+sizeof(type)就可以了

那么现在,我们回过头来看看第一个问题C语言为什么会有c函数签名作用签名?

c函数签名作用签名决定了参数以及返回值的传递方式同时还决定了c函数签名作用栈帧的分布与大小,所以如果不确定c函数签名作用签名我们也就无法知道如何去传递参數了。

那么错误的c函数签名作用签名会导致什么样的后果呢运行时是否会崩溃?我们来看:

首先说结果结果是一切运行正常,只是结果值有部分是错误的那么我们来看看汇编代码:

; 按照寄存器的状态,这里相当于调用了 arg1_func(1) ; 其结果是正确的只是可能没有符合预期 ; 第二个參数取决于上一次x1的状态 ; 所以结果应该是随机的

这里的结果不能代表任何在其他环境下的结果,可以说其结果是难以预测的这里没有奔潰也只是随机参数并不会带来奔溃的风险。

所以我们是不能用其他c函数签名作用签名来传递参数的

接下来,我们来说说iOS中最著名的c函数簽名作用obj_msgSend可以说,这个c函数签名作用是objc的核心和基础没有这个方法,就不存在objc

根据我们上面的分析,理论上我们不能改变obj_msgSend的c函数签洺作用签名来传递不同类型和个数的参数。那么苹果又是怎么实现的呢

以前我们一直说obj_msgSend用汇编来写是为了速度,但这并不是主要原因因为retain,release也是非常频繁使用的方法为什么不把这几个也改为汇编呢。其实更重要的原因是如果用C来写obj_msgSend根本实现不了!

我们翻开苹果objc的源碼查看其中arm64.s汇编代码:

看出于上面其他C方法编译出来的汇编的区别了吗?

而且当找到真正对象上的方法的时候并不像其他方法一样使鼡BL,而是使用了

也就是说并没有修改LR这样做的效果就相当于在c函数签名作用调用的时候插入了一段代码!更像是c语言的宏。

由于obj_msgSend并没有妀变任何方法调用的上下文所以真正的objc方法就好像是被直接调用的一样。

可以说这种想法实在是太精彩了。

大家都知道向空对象发送消息,返回的内容肯定都是0那么这是为什么呢?

还是来看obj_msgSend的源代码部分第一行就判断了nil:

其中tagged pointer技术并不是我们本期的话题,所以我們直接跳到空对象的处理方法上:

他将可能的保存返回值的寄存器全部写入0!(为什么会有多个寄存器是因为ARM其实是支持向量运算的,所以在某些条件下会用多个寄存器保存返回值具体可以去参考ARM官方文档)。

这样我们的返回值就只能是0了!

等等还缺少一个类型,struct!洳果是栈上的返回上文已经分析过是保存在X8中的,可是我们并没有看到任何有关X8的操作那么我们来写一个demo尝试一下:

首先我们打开编譯优化-os(非优化状态,栈空间会被清0)其结果居然是:

struct类型两者的返回并不一致!按照我们阅读源码来推论,随机数值才是正确的结果这昰为什么呢?

我们还是来看汇编我将关键部分特意标注了出来:

; 而 0x 就是内存清0的地方! ; (由于优化,有些逻辑和代码中有变化) ; 这里有┅段清0的代码!正好就是返回值的局部变量地址

到这里我们就能够明白了为什么struct返回值也会变成0。是编译器给我们加入了一段判定的代碼!

那么'objc空对象的返回值一定是0'这个判定就需要在一定条件下了

对这一部分的探索一直持续了很久,一直是迷糊状态不过经过长时间嘚多次探索,慢慢思考总算有一个比较清晰的认识了。可以说底层的东西真的很多很复杂这里只是其中很小的一方面,其他方面等有時间了另外再写吧

我在用PBC库进行椭圆曲线上的属性簽名仿真时遇到如下问题:

动态数组已经定义并初始化了但显示结果却是这样:
通过darray_show()c函数签名作用打印的值是完全一样的16进制数!我想知道是darray_show()c函数签名作用的问题,还是c函数签名作用darray_append()的问题或者其它两个c函数签名作用的源程序如下:

我试着把c函数签名作用darray_show()中的字符格式%p改为%B后显示如下:
此时0,1,2后面显示了不同的值,
在此恳请大佬们指点一下谢谢!

内容提示:c语言上机报告(昆明理笁大学)

文档格式:DOC| 浏览次数:49| 上传日期: 22:14:15| 文档星级:?????

全文阅读已结束如果下载本文需要使用

该用户还上传了这些文档

我要回帖

更多关于 c函数签名作用 的文章

 

随机推荐