指针与内存对齐到底是什么鬼?( 二 )

  • t.b = 2:语义是给符号b的值,赋值2 。符号b的地址是t的地址往高处偏移a的长度;同时符号b的值的长度是2个字节,=就是获取b的内存值,最后将short 2填充到这2个字节的内存中,完成赋值 。
  • 再看看复杂点的Test* t_ptr = &t;:t_ptr是个符号,它有地址与值两个属性 。Test*修饰部分用来描述t_ptr的长度,这里的Test*说明 t_ptr的值是一块内存的起始地址,长度为8字节(x64平台) 。这就是按照编译器的角度解释指针:不管*号前面是什么类型(task_struct* ptr还是int* ptr还是char* ptr)被修饰的符号长度永远就是8个字节的地址 。而t_ptr = &t就是将符号t_ptr的值赋值成符号t的地址——就是t_ptr这个符号的地址开始连续8个byte的内存填入符号t的地址值 。
  • 看了指针的本质,我们来看看对指针的操作指令:t_ptr->a = 3 。=的本质是获取符号a的值进行赋值;而找到a的地址比较复杂,先要拿到t_ptr符号的值,也就是&t,然后在t的地址基础根据a的偏移找到a的地址,这里偏移是0,a的地址等于t的地址 。然后根据a的类型int将int 3赋值到这4个字节上就行了 。
  • 到这里我们可以推敲一下指针的本质了 
    接着上面的例子,我们已经分析了t_ptr的内存布局,它的值是一个地址 。问题就来了,你想过没有,如果一个符号,它的值保存了一个地址,我对他能做什么操作?我们知道,如果t_ptr的值是int、long,我就能用CPU的算术模块对它们进行“加减乘除”,这样是有意义的,因为我在做代数运算 。那么对一个地址,显然,做加减乘除运算是没有意义的 。我们唯一能对地址做的有意义的操作就是找到这块地址,对这个地址对应的内存进行操作,这才是地址类型数据的意义 。
    因为对地址进行普通意义上的四则运算是没有代数意义的,所以,C语言为地址数据类型(指针)增加了两个操作符*与-> 。
     
    • *就是切换符号的含义,如*ptr = 3那么=获取的内存值,并不是ptr这个符号本身的值,而是ptr的值所对应的内存地址的内存值 。相当于将符号ptr的含义进行了切换,切换到了新的目标内存地址;
    • ->也是切换符号含义,但是不是切换到ptr指向的大内存块,而是里面的小内存块,可以理解成切换成对成员变量的符号的内存访问 。
    看看linux中一些指针操作——二重指针 
    看个Linux内核中的例子,这是mcs spinlock的加锁操作
    static inlinevoid mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)struct mcs_spinlock *prev;/* Init node */node->locked = 0;node->next = NULL;prev = xchg(lock, node); //相当于把mcslock.next = node;同时返回*lock修改之前的值 。if (likely(prev == NULL)) { //原来*lock指向NULL 。也就是现在链表还没形成,没有竞争 。return;} // 如果有值说明有竞争,要排队 。所以直接插入最后就行了 。prev就是最后一个元素 。WRITE_ONCE(prev->next, node);/*这里是个spin loop 。在percpu自身的lock上面自旋,等待变成1,获取锁 。/* Wait until the lock holder passes the lock down. */arch_mcs_spin_lock_contended(&node->locked);
    • struct mcs_spinlock **lock是什么呢?书上会说指向指针的指针,这么说没错,但是对很多刚刚接触到C语言的人来说其实很难理解,很难对应到实际内存中的样子 。不如,一步步拆解这条语句的含义 。
      • 首先,我们要先找到符号,符号才是内存,才有意义 。显然这里描述的符号是lock;
      • 符号就是一块内存,是内存就有地址与值,那么lock的值的信息可以从它的类型来推断出来,struct mcs_spinlock **就是类型信息,它的主要作用是描述lock的值有多长 。那么有多长呢?看到*就不用看struct mcs_spinlock了,就是一个地址,另一个符号的地址,8个字节长,保存在了lock符号的值里面 。
      • struct mcs_spinlock **的含义是什么呢?这是一个递归定义 。根据前面对*运算符的解释,struct mcs_spinlock **可以展开成这种形式(struct mcs_spinlock *)(*lock) 。*lock的含义是切换符号,假设切换成了(struct mcs_spinlock *)_lock符号,_lock符号的地址是lock的值,而_lock的值又是一个地址,一个struct mcs_spinlock *类型的地址 。如下图:
     
    指针与内存对齐到底是什么鬼?

    文章插图
    指针的指针到底是什么?


    推荐阅读