[008] [RT-Thread学习笔记] 求结构体首地址rt_list_entry函数与字节对齐RT_ALIGN宏

RT-Thread
学习笔记
rt_list_entry函数
源码分析
应用示例
RT_ALIGN宏
源码分析
应用示例

RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)

1 rt_list_entry函数

rt_list_entry函数的作用是根据已知成员的地址,算出其结构体的首地址。函数定义如下(在rtservice.h中):

1.1 源码分析

#define rt_container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))

/**
 * @brief 获取结构体入口地址
 * @param node      入口节点地址
 * @param type      节点所在结构体的类型  
 * @param member    节点在该结构体中的成员名称
 */
#define rt_list_entry(node, type, member) \
    rt_container_of(node, type, member)
  • (unsigned long)(&((type *)0)->member)):其中(type *)0表示将数值0强制转换为type 类型(一般为结构体类型)的指针变量,然后让该指针访问结构体中名称为member的成员。于是&((type *)0)->member)就表示该成员在内存中的地址,由于结构体是按照定义顺序将每个成员放入到内存中的,因此该成员地址等价于结构体的基地址+该成员所在结构体基地址的偏移字节数,结构体基地址在这里就是指针变量(type *)0本身,即为0,所以&((type *)0)->member)就是名为member的成员相对于type类型的结构体首地址偏移的字节数。最后将其转换为无符号长整型。
  • (char *)(ptr)ptr就是实际定义的结构体变量的某成员地址,然后将其转换为char *类型的指针变量。

问:为什么一定要将ptr转换成char*类型呢?
答:因为指针变量加减某值后偏移的字节数取决于它指向的变量类型,如int*类型的指针加1后,是相对于指针变量当前地址向后偏移4个字节。在这里需要将ptr指向的成员,减去该成员相对结构体首地址的偏移字节数(unsigned long)(&((type *)0)->member)),以得到该成员所在结构体变量的地址。所以只能转换成char*的指针去减偏移的字节数,如果用int*类型的指针,那么就会偏移四倍于它的字节,得到一个错误的地址。

1.2 应用示例

如释放互斥量函数rt_mutex_release中有这样一段代码:

/* 获取互斥量等待队列第一个线程的结构体地址 */
thread = rt_list_entry(mutex->parent.suspend_thread.next,
                       struct rt_thread,
                       tlist);

其中 rt_thread线程结构体定义为(部分省略):

struct rt_thread
{
    /* rt object */
    char        name[RT_NAME_MAX];         /* 线程名称 */
    rt_uint8_t  type;                      /* 对象类型  */
    rt_uint8_t  flags;                     /* 线程标志位 */
    ...
    rt_list_t   list;                      /* 对象列表 */
    rt_list_t   tlist;                     /* 线程列表 */
    ...
};

mutex->parent.suspend_thread.next表示该互斥量线程挂起链表中第一个节点,为rt_list_t类型,为rt_thread线程结构体中的tlist成员,通过rt_list_entry函数获取该线程的地址,然后让该线程持有互斥量。

2 字节对齐宏

字节对齐的作用不仅是便于 CPU 快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

2.1 源码分析

  • RT_ALIGN

直接分析源码,该宏在rtdef.h中定义:

#define RT_ALIGN(size, align)	(((size) + (align) - 1) & ~((align) - 1))

作用为将字节数size向上提升为align的整数倍,需要注意的是align要为2的幂次方。

  • (size) + (align) - 1align-1size除以align的最大余数,用size去加它是为了便于向上取align的整数倍数值。
  • ~((align) - 1):为余数的掩码,将其&上(size) + (align) - 1即可清除其余数,得到align的整数倍数值。

  • RT_ALIGN_DOWN

将字节数size向下提升到align的整数倍的:

#define RT_ALIGN_DOWN(size, align)      ((size) & ~((align) - 1))

  • ALIGN
#define ALIGN(n)                    __attribute__((aligned(n)))

该宏作用是在给某对象分配地址空间时,将其存放的地址按照 n 字节对齐,这里 n 依然只能取 2 的幂次方。

关键字__attribute__允许你在定义struct、union、变量等类型时指定特殊属性。此关键字后面是跟着双括号括起来的属性说明。__attribute__不属于标准C语言,它是GCC对C语言的一个扩展用法。
__attribute__((aligned(n))):此属性指定了指定类型的变量的最小对齐(以字节为单位)。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

注意:对齐属性的有效性会受到链接器(linker)固有限制的限制,即如果你的链接器仅仅支持8字节对齐,即使你指定16字节对齐,那么它也仅仅提供8字节对齐。

2.2 应用示例

  • RT_ALIGN(17,4)= 20
(size) + (align) - 1 = 17 + 3 = 0001 0100
~((align) - 1) = ~(3) = ~(0000 0011)
0001 0100 & ~(0000 0011) = 0001 0100 = 20	// 将后两位清0,后两位即为最大余数3

上面说过align必须为2的n次幂,下面测试其不为2的n次幂时的错误用法:

  • RT_ALIGN(17,3)
    正确结果:18
    实际运算结果:17
(size) + (align) - 1 = 17 + 2 = 0001 0011
~((align) - 1) = ~(2) = ~(0000 0010)
0001 0011 & ~(0000 0010) = 0001 0001 = 17	

参考文章:Linux下__attribute__((aligned(n)))的使用

END

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯西的彷徨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值