Linux下C语言嵌入式笔记(五)

内存字节对齐
计算机中内存空间都是按照字节划分的,从理论上讲对任何类型的变量的访问可以从任何地址开始,但是在程序实际编译过程中,编译器会对数据类型在编译过程中进行优化对齐,编译器会将各种类型数据按照一定的规则在空间上排列,而不是顺序的排放,这就是内存字节对齐。

内存字节对齐原因

不同硬件平台对存储空间的处理是不同的。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如某些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据,这样数据读取效率就会很差。

内存字节对齐的规则
1、数据类型自然边界对齐

各种数据类型的自然边界对齐值如下:
在这里插入图片描述2、结构体、类的自身对齐
为结构体分配内存时,分配的内存大小至少是各个字段的长度和。通常,分配的结构体的长度会大于结构体各个字段的长度和,因为结构体需要对齐,即结构体各字段之间需要填充。
缺省情况下,编译器为结构体的每个成员按其自然边界对齐方式分配空间,按照每个成员被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。结构体整体的默认字节对齐值是结构体中的所有成员中对齐参数最大的一个的值,结构体长度的计算必须取所用过的所有对齐参数的整数倍。
结构体中成员的对齐方式取决于指定对齐方式对齐值和成员自身自然边界对齐值两者中较小的值。
结构体整体对齐方式取决于结构体中所有成员的自然边界对齐值的最大值和指定对齐值两者中最小的值。
3、编译器指定对齐
内存字节对齐是GCC编译器对C语言进行的扩展。在缺省情况下,C编译器为每一个变量或是数据单元按其自然边界对齐条件分配空间。同时GCC编译器规定了两种内存字节对齐的方法:
A、伪指令方式
#pragma pack(n) //n的取值可以为1、2、4、8,在编译过程中按照n个字节对齐
#pragma pack() //取消指定对齐,按照编译器的优化对齐方式对齐
GCC编译器不建议使用#pragma进行内存字节对齐,而且#pragma最多只能支持8字节的对齐。如果n大于8则编译器会报警告,编译器将会按8字节对齐。
warning: alignment must be a small power of two, not 9 [-Wpragmas]
B、属性设置方式
attribute ((packed)); //取消结构在编译过程中的优化对齐。
attribute ((aligned (n))); //让所作用的结构体、类的成员对齐在n字节自然边界上,如果结构中有成员的长度大于n,则按照机器字长来对齐。n=1,2,4,8,16…
GCC推荐使用方式
C、两种对齐方式的区别
#pragma pack(n) 对齐方式告诉编译器结构体或类内部的成员变量相对于第一个变量的地址的偏移量的对齐方式,缺省情况下,编译器按照自然边界对齐,当变量所需的自然对齐边界比n大时,按照n对齐,当变量所需的自然对齐边界比n小时,按照自然边界对齐。n最大只能为8。
attribute((aligned(m)))对齐方式告诉编译器一个结构体或者类或者联合或者类型的变量(对象)分配地址空间时的地址对齐方式。如果__attribute__((aligned(m)))作用于一个类型,那么该类型的变量在分配地址空间时,其存放的地址一定按照m字节对齐(m必须是2的幂次方);如果类型中的成员的自然边界对齐值大于m,则按照机器字长对齐。类型占用的空间,即大小,是m的整数倍,以保证在申请连续存储空间的时候,每一个元素的地址也是按照m字节对齐。
实例对比如下:
struct test{
char a;
int b;
short c;
char *p;
double d;
}attribute((aligned(4)));//实际有效指定对齐值为8字节(机器字长)
sizeof(struct test);//32字节
#pragma pack(4)
struct test{
char a;
int b;
short c;
char *p;
double d;
};//指定对齐值小于结构体中成员的自然边界对齐值8字节,有效对齐值为4字节
#pragma pack()
sizeof(struct test);//28字节

结构体定义
结构体定义一般有两种方法较为常用:
第一种方法:
struct person{
char *name;
unisgned int age;
};
第二种方法:
typedef struct person{
char *name;
unsigned int age;
}Person;
person实例声明如下:
Person person;//声明一个person对象
Person ptrPerson = (Person)malloc(sizeof(Person));//声明一个person对象,并分配内存
2、结构体的初始化
使用结构体声明的结构体对象的初始化:
Person person;
person.name = (char *)malloc(strlen(“socprio”) + 1);
strcpy(person.name, “scorpio”);
person.age = 30;
使用结构体指针声明的结构体的初始化:
Person *ptrPerson;
prtPerson = (Person *)malloc(sizeof(Person));
ptrPerson->name = (char *)malloc(strlen(“scorpio”) + 1); /->表示取出结构体中包含的数据项name
strcpy(ptrPerson->name, “scorpio”);
prtPerson->age = 30;
3、结构体释放的问题
定义结构体时会为结构体分配内存,但运行时系统不会自动为结构体内部的指针分配内存,结构体销毁时,运行时系统也不会自动释放结构体内部的指针指向的内存。结构体内部的指针指向内存一般为动态分配的内存,使用完成后需要释放,否则将会造成内存泄漏。
4、动态分配内存开销的避免
重复分配然后释放结构体会产生开销,可能会导致巨大的性能瓶颈。解决这个问题的移植方法是为分配的结构体单独维护一个表。当用户不需要某个结构体实例时,将其返回结构体池中。当需要某个结构体实例时,从结构体池中获取一个对象。如果结构体池中没有可用的结构体,就会自动动态分配一个结构体实例。

结构体宏
1、offsetof宏
offsetof根据结构体成员的类型和成员名来计算该成员距结构体首地址的偏移量。宏定义如下:
#define offsetof(type, member) ((size_t)(&((type *)0)->member))
( (TYPE *)0 ): 0地址强制 “转换” 为 TYPE结构类型的指针
((TYPE *)0)->MEMBER :访问TYPE结构中的MEMBER数据成员
&( ( (TYPE )0 )->MEMBER):取出TYPE结构中的数据成员MEMBER的地址
(size_t)(&(((TYPE
)0)->MEMBER)):结果转换为size_t类型
offsetof宏首先将0转换为结构体指针类型,然后引用成员变量并取其地址。由于结构体首地址为0,所以成员变量的地址即为成员距结构体首地址的偏移量。
现代编译器中常用
#define offsetof(type, member) __builtin_offsetof(type, member)
2、container_of宏
container_of根据成员地址、结构体类型和成员名来计算结构体的首地址,其定义如下:
#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
创建一个结构体,用offsetof和container_of获取结构体的地址和内部成员相对于结构体地址的偏移量。
代码实例:
#include <stdio.h>
#define offsetof(type, member) ((size_t)&((type *)0)->member)
#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})

struct test{
char a;
int b;
short c;
char *p;
double d;
}attribute((aligned(4)));

int main(int argc, char **argv)
{
struct test s;
printf(“offset a=%lu\n”,offsetof(struct test,a));
printf(“offset b=%lu\n”,offsetof(struct test,b));
printf(“offset c=%lu\n”,offsetof(struct test,c));
printf(“offset p=%lu\n”,offsetof(struct test,p));
printf(“offset d=%lu\n”,offsetof(struct test,d));
printf(“s=%p\n”,container_of(&s.a,struct test,a));
printf(“s=%p\n”,container_of(&s.p,struct test,p));
return 0;
}

运行结果:
offset a=0
offset b=4
offset c=8
offset p=16
offset d=24
s=0x7fffc8590cf0
s=0x7fffc8590cf0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值