iOS内存平移问题

首先定义一个类

@interface SLAnimal : NSObject
@property(strong,nonatomic)NSString * sl_name;
- (void)saySomething;
@end

@implementation SLAnimal
- (void)saySomething{
    NSLog(@"%s - %@",__func__,self.sl_name);
}
@end

在ViewController中 通过以下两种方式调用saySomething

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //下面这两种方式调用
    //方式一
    Class cls = [SLAnimal class];
    void  *sl = &cls;
    [(__bridge id)sl saySomething];
     
    //方式二:常规调用
    SLAnimal *a = [SLAnimal alloc];
     [a saySomething];
}

@end

输出结果如下

2023-02-21 09:22:09.591688+0800 aaa[2244:35170] -[SLAnimal saySomething] - <ViewController: 0x7fb0daf070e0>
2023-02-21 09:22:09.591778+0800 aaa[2244:35170] -[SLAnimal saySomething] - (null)

[a saySomething]的本质是对象发送消息,a的isa指向类SLAnimal,即a的首地址指向SLAnimal的首地址,可通过LGPerson的内存平移找到cache,在cache中查找方法

 [(__bridge id)sl saySomething]中的sl是来自于SLAnimal这个类,有一个指针sl,将其指向SLAnimal的首地址

 因此,a和sl都是是指向SLAnimal类的结构,都在SLAnimal中的methodList中查找方法

方式2:[a saySomething]

对于常规调用,self此时指向的是a的内存结构,获取sl_name是通过内存平移8字节获取

 方式1:[(__bridge id)sl saySomething] 

sl表示8字节指针,self.sl_name的获取,相当于sl首地址也需平移8字节找sl_name。sl是存在中的,栈是一个先进后出的结构,参数传入就是一个不断压栈的过程

  • 隐藏参数压入栈,每个方法搜有两个隐藏参数(id self,sel _cmd)
  • 隐藏参数压栈的过程,地址是递减的,栈是由高地址-->低地址分配的
  • super底层调用objc_msgSendSuper,其第一个参数是一个结构体__rw_objc_super(self,class_getSuperclass),以下定义结构体,判断内部成员压栈情况。

 20先加入,再加入10,所以栈中结构体内部的成员是反向压入栈,即低地址->高地址,是递增的。

检验完结构体内部成员的压栈情况后,回到最初的问题,此时栈中从高到低的顺序self --> _cmd --> (id)class_getSuperclass(objc_getClass("ViewController")) --> self - ->cls --> sl --> a

self_cmdviewDidLoad方法的两个隐藏参数(id self,sel _cmd),是高地址->低地址正向压栈class_getSuperClass 和 selfobjc_msgSendSuper2中的结构体成员,是从最后一个成员变量,即低地址->高地址反向压栈

可以通过代码检验

其中发现class_getSuperclassViewController, 由于objc_msgSendSuper2返回的是当前类,两个self,并不是同一个self,而是栈的指针不同,但是指向同一片内存空间

[(__bridge id)sl saySomething] 调用时,此时的sl是 SLAnimal: 0x7ff7befcab18,所以saySomething方法中传入的self 还是SLAnimal,但并不是我们通常认为的SLAnimal,使我们当前传入的消息接收者,即SLAnimal: 0x7ff7befcab18,是SLAnimal的实例对象,此时的操作与普通的SLAnimal是一致的,即SLAnimal的地址内存平移8字节

普通a流程:a-->sl_name-内存平移8字节

sl流程:0x7ff7befcab18 + 0x80 --> 0x7ff7befcab20, 即为self

​​​​​​​

所以sl查找sl_name时候“欺骗”了编译器, 在栈中内存平移8字节(0x7ff7befcab18 + 0x80 = 0x7ff7befcab20 = ViewController)

补充

哪些东西在栈里 哪些在堆里

  • alloc的对象 都在

  • 指针、对象 在中,例如person指向的空间中,person所在的空间在栈中

  • 临时变量

  • 属性值 在属性随对象是在

注意:

  • 是从小到大,即低地址->高地址

  • 是从大到小,即从高地址->低地址分配

    • 函数隐藏参数从前往后一直压,即 高地址->低地址 开始入栈

    • 结构体内部的成员是低地址->高地址

  • 一般情况下,内存地址有如下规则

    • 0x6 开头表示在 

    • 0x7 开头的地址表示在 

    • 0x1 开头的地址表示在全局区域

猜你喜欢

转载自blog.csdn.net/weixin_38016552/article/details/129135994
今日推荐