C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用

在不使用#pragma pack__attribute__((packed) 等选项来自定义字节对齐大小的情况下,关于正常字节对齐的描述,可参考博文:

C/C++计算类/结构体和联合体(union)所占内存大小(内存对齐问题)_联合体占用的内存空间_SOC罗三炮的博客-CSDN博客同学可以尝试将char f 注释,最后将得到24,也可以从侧面说明再加上一个char f,其大小肯定大于等于24 byte。Test3中的最大数据成员大小比成员结构体Test内部最大成员大小要小,这时规则3是按照。sizeof的大小是24,即满足容下a[20],同样24是b、c和d的倍数,规则3。sizeof的大小是20,即a[20]的大小,同样20是b和c的倍数,规则3。s中char ch1;占用 1Byte(存储位置8)的,sizeof的结果是40。最大元素大小的整数倍地址。_联合体占用的内存空间https://blog.csdn.net/luolaihua2018/article/details/115372273?ops_request_misc=&request_id=fd0dff42f0ee4596a85be00069ed9893&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~koosearch~default-2-115372273-null-null.268%5Ev1%5Econtrol&utm_term=%E5%AD%97%E8%8A%82%E5%AF%B9%E9%BD%90&spm=1018.2226.3001.4450本文主要讨论如何使用#pragma pack和__attribute__((packed)等选项来自定义字节对齐大小。

目录

1,对整个结构体进行打包压缩

 #pragma pack(n) 实验

__attribute__((packed)) 实验

 2,编译器内存对齐操作

test1,正常情况下的对齐操作,12 bytes

 test 2,使用__attribute__((packed)),相当于#pragma pack(1),8 bytes (最小)

 test3, 使用#pragma pack(2),10 bytes

  test4, 使用#pragma pack(4),12 bytes

  test5, 使用#pragma pack(5) ,12bytes

 test6, 使用#pragma pack(8) ,12bytes 

test7,使用__attribute__((packed))对结构体中的成员进行修饰,10 bytes

3,使用armlink 选项 --info=sizes 查看 结构体大小


使用pack功能将数据结构进行打包,以减小应用程序对内存的占用,这一点在嵌入式系统中,尤其是需要存储和访问大量内存的时候,显得尤其重要。

如果没有使用#pragma pack和__attribute__((packed)等选项,将数据结构进行打包。编译器为了提高对数据成员的访问速度,通常会在不同大小的数据成员之间插入空白内存(padding),进行补齐操作,即内存对齐。默认情况下的内存对齐,需要遵守一下三条规则:

  1. 数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从4的整数倍地址开始存储)
  2. 结构体作为成员,如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
  3. 结构体的总大小,即sizeof的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐
     

ARM的嵌入式编译器提供了编译选项和属性(#pragma pack和__attribute__((packed)),可以让程序员自定义内存对齐的大小,对数据结构(结构体或联合体)进行打包,必要情况下甚至可以不需要内存留白,即不对齐。

#pragma pack () 对于结构体中的每个成员,如果  小于该成员的默认对齐大小(关于默认对齐大小,可参考上文中提到的博文),则使用  bytes 作为该成员的对齐大小。如果 大于默认对齐大小,则使用默认对齐大小。简言之,使用 和默认对齐大小中较小的那个,作为该数据成员的对齐大小。 详情见: __alignof__.
__attribute__((packed)) 等效于#pragma pack(1),最小对齐大小,即不进行内存对齐,可以对结构体中的单个数据成员使用。

1,对整个结构体进行打包压缩

在声明结构体或联合体时,可以使用__attribute__((packed)) 或 #pragma pack(n) 对整个结构体进行声明。这样,结构体或联合体里面的每个数据成员都会按照新的对齐大小进行对齐。

__attribute__((packed)) 和  #pragma pack(n)的作用范围不同,使用__attribute__((packed))可以精准地对其声明的结构体或者联合体进行打包压缩,而不会影响其他及结构体,甚至是其子结构体(其数据成员也为结构体)的正常内存对齐。而 #pragma pack(n) 的作用范围更大,在使用#pragma pack(n)之后的所有结构体联合体(不包括子结构体)的内存对齐都会受到其影响。

struct __attribute__((packed)) stc
{
    char one;
    short two;
    char three;
    int four;
} c,d;

#pragma pack (1)
struct stc
{
    char one;
    short two;
    char three;
    int four;
} c,d;

 #pragma pack(n) 实验

/* ================= test 1 ==================*/
struct stc
{
    char one;
    short two;
    char three;
    int four;
} cc; 

#pragma pack (1)
struct stcd
{
    char one;
    short two;
    char three;
    int four;
} dd;

sizeof(cc) = 12
sizeof(dd) = 8

/* ================= test 2 ==================*/
#pragma pack (1)
struct stc
{
    char one;
    short two;
    char three;
    int four;
} cc; 

struct stcd
{
    char one;
    short two;
    char three;
    int four;
} dd;

sizeof(cc) = 8
sizeof(dd) = 8

/* ================= test3 ==================*/

typedef struct 
{
    char one;
    short two;
    char three;
    int four;
} stc;
stc cc;

struct stcd
{
    char one;
    short two;
    char three;
    int four;
    stc C;
} dd;

sizeof(cc) = 12
sizeof(dd) = 24

/* ================= test4 ==================*/

#pragma pack (1)
typedef struct 
{
    char one;
    short two;
    char three;
    int four;
} stc;

stc cc;
struct stcd
{
    char one;
    short two;
    char three;
    int four;
    stc C;
} dd;

sizeof(cc) = 8
sizeof(dd) = 16

/* ================= test5 ==================*/

typedef struct 
{
    char one;
    short two;
    char three;
    int four;
} stc;
stc cc;
#pragma pack (1)
struct stcd
{
    char one;
    short two;
    char three;
    int four;
//    #pragma pack (1)
    stc C;
} dd;

sizeof(cc) = 12
sizeof(dd) = 20

/* ================= test6 ==================*/

typedef struct 
{
    char one;
    short two;
    char three;
    int four;
} stc;
stc cc;

struct stcd
{
    char one;
    short two;
    char three;
    int four;
    #pragma pack (1)
    stc C;
} dd;

sizeof(cc) = 12
sizeof(dd) = 20

从test4,test5和test6可以看到,虽然 在#pragma pack(n)之后,都会受到其对齐影响,但是结构体dd中的C结构体仍保持自然的内存对齐,并未受影响。此外,在结构体dd内使用#pragma pack(n),仍相当于对整个结构体dd起作用。

__attribute__((packed)) 实验

/* ================= test1 ==================*/

typedef struct __attribute__((packed))
{
    char one;
    short two;
    char three;
    int four;
} stc;

stc cc;

struct stcd
{
    char one;
    short two;
    char three;
    int four;
    stc C;
} dd;

sizeof(cc) = 8
sizeof(dd) = 20

/* ================= test2 ==================*/
typedef struct __attribute__((packed))
{
    char one;
    short two;
    char three;
    int four;
} stc;

stc cc;

struct __attribute__((packed)) stcd
{
    char one;
    short two;
    char three;
    int four;
    stc C;
} dd;

sizeof(cc) = 8
sizeof(dd) = 16

/* ================= test3 ==================*/
typedef struct 
{
    char one;
    short two;
    char three;
    int four;
} stc;

stc cc;

struct __attribute__((packed)) stcd
{
    char one;
    short two;
    char three;
    int four;
    stc C;
} dd;

sizeof(cc) = 12
sizeof(dd) = 20

使用  __attribute__((packed))可以精准地对想要打包压缩的结构体进行操作,而不像#pragma pack(n)的全局生效。同时也是对整个结构体生效,如果结构体内的数据成员仍为结构体,则不对子结构体生效。 __attribute__((packed))相当于#pragma pack(1),即尽可能地压缩结构体空间,不像#pragma pack(n)那样可以选择不同大小的对齐尺寸。

使用__attribute__((packed))声明变量时,要注意其摆放位置,比如test4的声明是错误的,虽然可以编译通过,但是__attribute__((packed))并不会生效,还会报一个warning:

[Warning] 'packed' attribute ignored [-Wattributes]

test5中的声明方式是正确的: 

/* ================ test 4 ==============*/
typedef struct
{
    char one;
    short two;
    char three;
    int four;
} stc;

stc __attribute__((packed)) cc;

sizeof(cc) = 12  

/* ================ test 5 ==============*/

typedef struct __attribute__((packed))
{
    char one;
    short two;
    char three;
    int four;
} stc;
stc cc;

sizeof(cc) = 8

 2,编译器内存对齐操作

读者可以先参考博文,了解正常情况下的结构体对齐操作:
C/C++计算类/结构体和联合体(union)所占内存大小(内存对齐问题)_联合体占用的内存空间_SOC罗三炮的博客-CSDN博客同学可以尝试将char f 注释,最后将得到24,也可以从侧面说明再加上一个char f,其大小肯定大于等于24 byte。Test3中的最大数据成员大小比成员结构体Test内部最大成员大小要小,这时规则3是按照。sizeof的大小是24,即满足容下a[20],同样24是b、c和d的倍数,规则3。sizeof的大小是20,即a[20]的大小,同样20是b和c的倍数,规则3。s中char ch1;占用 1Byte(存储位置8)的,sizeof的结果是40。最大元素大小的整数倍地址。_联合体占用的内存空间https://blog.csdn.net/luolaihua2018/article/details/115372273?ops_request_misc=&request_id=6d04e4a92dcd4c6585378e324b6f7c4a&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~koosearch~default-2-115372273-null-null.268%5Ev1%5Econtrol&utm_term=%E5%86%85%E5%AD%98%E5%AF%B9%E9%BD%90&spm=1018.2226.3001.4450

 下面将通过一些示例,详情介绍使用__attribute__((packed))和#pragma pack(n)对内存对齐产生的影响:

test1,正常情况下的对齐操作,12 bytes

结构体中,各元素按照顺序摆放,同时需要针对不同大小的成员数据进行不同大小的补齐操作

C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用_第1张图片

 test 2,使用__attribute__((packed)),相当于#pragma pack(1),8 bytes (最小)

 #pragma pack(1) 不需要任何内存补齐,没有任何pandding,如下图所示,数据按照成员顺序摆放即可:C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用_第2张图片

C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用_第3张图片

 test3, 使用#pragma pack(2),10 bytes

强制使用2 bytes作为结构体中的最大对齐尺寸

 C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用_第4张图片

  test4, 使用#pragma pack(4),12 bytes

C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用_第5张图片

  test5, 使用#pragma pack(5) ,12bytes

如果n不是2的次方,即不是1,2,4,8...,则不会生效,并且会报一个warning:

9[Warning] alignment must be a small power of two, not 5 [-Wpragmas]

 test6, 使用#pragma pack(8) ,12bytes 

 正常情况下,结构体c和d中的最大自然对齐尺寸为4,所以编译器会在8和4中选一个最小的对齐尺寸,即为4。

test7,使用__attribute__((packed))对结构体中的成员进行修饰,10 bytes

使用__attribute__((packed))可以指定某个数据成员不需要进行内存对齐,虽然指定了 int类型的four不需要内存对齐,但是结构体中的其他成员仍需遵守内存对齐规则,除了int外,最大的是short类型的two,所以该结构体的最小对齐尺寸为short的大小,即2 bytes,这就是为什么最后的结果为10 bytes,而不是9 bytes.

C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用_第6张图片

3,使用armlink 选项 --info=sizes 查看 结构体大小

将示例代码写入str.c,

struct stc
{
    char one;
    short two;
    char three;
    int four;
} c,d;


int main (void)
{
    c.one=1;
    return 0;
}

 并按以下命令编译,生成.o文件:

armclang --target=arm-arm-none-eabi -march=armv8-a -c str.c -o str.o

使用armlink的 -info=sizes选项将.o文件的各个属性的数据的大小列出:

armlink file.o --info=sizes

 其中c和d为全局变量,并未对齐初始化,所以编译器会将其初始化为0,即属于 ZI (zero initialize)数据,由下表可知,其大小为12+12=24 bytes:

Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name

  36          0          0          0         24          0   str.o
---------------------------------------------------------------------------
  36          0         16          0         24          0   Object Totals

参考文章:

Packing data structures

C/C++计算类/结构体和联合体(union)所占内存大小(内存对齐问题)_联合体占用的内存空间

你可能感兴趣的:(ARM,Compiler,Linux_C,c语言,编译优化,嵌入式,arm开发,内存对齐)