引言

在我们学习嵌入式的时候,可能会接触到各种平台,那么我们的代码也有可能在不同的平台上运行。那么我们如果没有约定的一套规则的话,我们定义的一些类型去到其他的平台可能就会出现问题(比如操作系统Linux、Bootloader等系统型的软件,它们的使命就是应该要在不同平台中运行)。那么,这个时候我们就应该要了解跨平台的嵌入式程序,需要注意什么地方:

那么,有接触过项目开发的朋友,肯定也会看到过例程,或者别人的代码中的头文件都会这样数据类型的封装:

嵌入式 linux hiredis_数据类型封装

一、确保变量的大小固定(字节数大小)

以下面自己编写的程序、以及A平台、B平台为例子:

嵌入式 linux hiredis_数据类型封装_02

  • A平台、B平台不同的类型所定义的字节数并不相同。如果我们自己编写的代码继续使用int a 来定义的话,是不清楚a所被定义的字节数的,那么一旦程序在A平台上运行,就会导致崩溃。因此,这就是为什么很多程序都是使用int32_t 这种方法来定义变量。
  • 当程序在A平台上面运行时,A平台使用了typedef的方法定义了long int32_t 来表示,在A平台中,long类型定义的变量是占用4个字节的,那么传输过去A平台的a,将会变成long a
  • 当程序在B平台上运行,同理可得:int a

二、确保变量的相对位置固定(地址对齐)

什么是地址对齐?

首先补一下知识点:一个32位的系统的运作原理:

32位系统:指处理器拥有每次存储32位(4bytes)的数据。

假设,有一个32位的系统,现在要处理一个int a 为4个字节的数据,如果没有规则去限制这个a,那么这个a有可能是这样存入内存中:

嵌入式 linux hiredis_嵌入式 linux hiredis_03

  • a在内存中的地址是连续的,可是CPU是32位,也就是每次处理4个字节的数据(一个红圈),那么针对以上情况,CPU的反应有两种:
    1.不工作
    2.降低效率,两次存储处理该变量。
    以上的这种情况就是:地址不对齐情况
    因此,我们要规定一个规则(地址对齐),告诉计算机要让a以下面这种方式去放入内存,才能让CPU高效率工作:

    此时,a的地址满足条件:&a = 4*N 即可!
问题深入:

我们刚刚看完上面一个简单的例子,下面就应该对地址对齐的问题进行深入探讨。我们以上面的例子:&a=4*N 我们将这个4看作一个m值r然后来探讨下面几个类型的m值:

struct	node
{
	char	a;		字节数为1
	int		b;		字节数为4
	short	c;		字节数为2
	double	d;		字节数为8
}

我们借助刚刚上面的例子可以推敲出这四个类型的m值:

嵌入式 linux hiredis_嵌入式 linux hiredis_04


这里以比较难理解的double类型为解释:

&d = 4 * N

N=0: &d=0

N=1: &d=4

N=2: &d =8

嵌入式 linux hiredis_数据类型封装_05


虽然说m值与byte有关,但是这里的m只需要4就可以了,如果为8的话就不合理,可以自己去画画看。

因此,控制m值成为了地址对齐的条件


第二个需要掌握的是系统计算这个包含abcd类型的结构体的大小:

我们通过画图,可以看到占用的是20

嵌入式 linux hiredis_地址对齐_06


那么我们也可以通过代码来输出验证一下:

嵌入式 linux hiredis_嵌入式 linux hiredis_07


嵌入式 linux hiredis_数据类型封装_08

  • 通过上面的图示,也看到了系统内存在存储char类型,short类型的时候,都在后面补零,而不是使用继续填充下一个类型的方法。那么这就是隐藏在系统中的地址对齐规则。
  • 隐藏在里面的地址对齐还有对整个结构体的对齐规则:
    我们可以看到系统首先存储的是char类型的a,那么a占用的字节是1,m值也是等于1。那么按照道理来说,a可以存放在内存的任何地方,但是为什么就是按照图示的位置存储呢?
    这里就衍生出了,结构体Node的m值。结构体Node也是变量,其他,系统也对它要求地址对齐,因此它也有一个m值:m=4(取结构体变量中m值最大的)。
    因此,a的地址位置就受到了结构体m值的约束。
  • m值的作用:
    1.规定起始地址为m的整数倍。例如:如果结构体的大小为4,那么该结构体的大小一定为4的整数倍,若不满,则补零填满。
    2.变量的大小也为m的整数倍。

使用attribute固定m值

在定义变量时,使用__attribute__((aligned(m值)))来固定m值:

嵌入式 linux hiredis_嵌入式_09


那么,如果将代码更改为上图,我们的结构体占用的大小为多少呢?

嵌入式 linux hiredis_嵌入式 linux hiredis_10

  • 存a:黑色阴影部分。
  • 存c:蓝色阴影部分,补一个零。
  • 存b:b的m值被更改为64,因此,存完4个字节后,在后面补60个零为64字节。
  • 存d:存入8个字节。
  • 52个零的由来:结构体node的m值为:64,可是现在所有加起来只占76字节。那么我们的结构体的m值为64,那么整个结构体的大小一定为64的倍数,因此,我们要往后面补零补足128个字节。
  • 因此得到:8+60+8+52=128

我们看看程序的输出:

嵌入式 linux hiredis_字节数_11


当你成功理解,并且算出这个128的大小后,恭喜你,你已经掌握了地址对齐了。在课堂中,可能老师都不会深入到这一点,但是当我们接触最底层,或者接触工作项目的时候就会遇到这一部分。这个时候,我们就能柔韧而解地完成项目。

最后再总结一次:编写跨平台的嵌入式程序要注意的地方

1.确保变量在不同平台的大小变化(字节数大小)
2.确保变量在不同平台的地址变化(使用m值确定地址位置,地址对齐)