C语言字节对齐及__attribute__((aligned(n))) 与 #pragma(pack(n))的作用

本文详细解析了结构体成员及整体的对齐规则,并通过多个示例展示了如何使用__attribute__((aligned(n)))和#pragmapack(n)来控制对齐。此外,还介绍了字节对齐的重要性以及含位域结构体的sizeof大小计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:对齐规则

在没有__attribute__((aligned(n)))或#pragma pack修饰的声明下,字节对齐遵循下面三个原则:

1:结构体(struct)的数据成员,第一个数据成员存放的地址为结构体变量偏移量为0的地址处,结构体变量的首地址能够被其最宽基本类型成员的大小所整除。

数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。

2:其他结构体成员自身对齐时,存放的地址为min{自身对齐值, 指定对齐值} 的最小整数倍的地址处.
注:自身对齐值:结构体变量里每个成员的自身大小
注:指定对齐值:有宏 #pragma pack(N) 指定的值,这里面的 N一定是2的幂次方.如1,2,4,8,16等.如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8;__attribute__((aligned(n)))作用在结构体总体大小上,不影响结构体内部成员的排序。

备注:结构体A中嵌套结构体B时,结构体B的地址按照B内成员最大宽度来对齐。计算大小时结构体B在结构体A中展开。结构体作为成员,如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)

3:结构体的总大小,也就是sizeof的结果,总体对齐时,字节大小是min{所有成员中自身对齐值最大的, 指定对齐值} 的整数倍.不足的要补齐。__attribute__((aligned(n)))指定是最小对齐值,n < min{所有成员中自身对齐值最大的, 指定对齐值}时忽略,n > min{所有成员中自身对齐值最大的, 指定对齐值}时,总大小与n对齐。

二:测试样例

下面以32位处理器来看几个例子:

测试1:

struct test
{
  char a;  //1
  char b;  //1
  char c;  //1
};

32位系统下,自然对齐,sizeof大小是3字节。
测试2:

struct test
{
  char a;  //1
  short b;  //2
  char c;  //1
};

32位系统下,自然对齐,sizeof大小是6字节。

来结合对齐规则来看一下,

1、第一个成员首地址为0(准确说是偏移量),这个没什么好说,

2、每个成员的首地址是自身大小的整数倍,因为b是short类型的,占用两个字节,所以,必须以2字节对齐,也就是说你可以把b放在0啊,2啊,4啊这些地址,但是你不能放在1,3,5这样的地址。a的地址是0,下一个地址是1,不能放,只能空掉,放在2位置处。这样,a和b就占了4个字节了,接下来c占一个字节。

3,看第三条规则,结构体的总大小,为其成员中所含最大类型的整数倍。所以,在这个例子中,结构体总大小应该要为2的整数倍,所以是6,

测试3:

struct test
{
  char a;   //1+3
  int  b;    //4
  short c;    //2+2
};

32位系统下,自然对齐,sizeof大小是12字节。
测试4:

struct test
{
  char d[7]; 
  double a;
  short  b;
  char* c;
};

32位系统下,自然对齐,sizeof大小是24字节。

指针在32位系统中都是占4个字节


typedef struct bb
{
    int id;             //[0]....[3]
    double weight;      //[8].....[15]      原则1
    float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3
}BB;

typedef struct aa
{
    char name[2];     //[0],[1]
    int  id;         //[4]...[7]          原则1
    double score;     //[8]....[15]    
    short grade;    //[16],[17]        
    BB b;             //[24]......[47]          原则2
}AA;


int main(void)
{
	AA a;
	
	printf("a size %d\n",sizeof(a));

	return 0;
}

输出结果:a size 48

32位系统下,自然对齐,sizeof大小是48字节。

三、为什么要字节对齐


        需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误。
  
        对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
  
        数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。 
        联合 :按其包含的长度最大的数据类型对齐。 
        结构体: 结构体中每个数据类型都要对齐。

        在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐。即使看起来本来就自然对齐的也要使其对齐,以免不同的编译器生成的代码不一样.

四:__attribute__选项

1. __attribute__ 是什么


__attribute__是GCC里的编译参数,用法有很多种,感兴趣可以阅读一下gcc的相关文档。这里说一下__attribute__对变量和结构体对齐的影响。这里的影响大概分为两个方面,对齐和本身占用的字节数的大小,即sizeof(变量)的值。

2,__attribute__在字节对齐上的使用

__attribute__((packed)):取消对其原则,按照紧密对齐(也就是1字节对齐)

__attribute__((__aligned__(n))):n可以为1/2/4/8等,按照n字节对齐。

正确理解__attribute__((aligned(x))):

__attribute__((__aligned__(n)))定义的是最小对齐边界,它可以用来修饰structure也可以用来修饰变量或者structure成员变量,它只能增加 structure、变量或者structure成员变量的对齐值(GCC手册说在修饰 typedef 类型时可以增加或减少对齐值)。

3. __attribute__((__aligned__(n)))修饰变量

使用的语法如下:
int a __attribute__((__aligned__(8))) = 10;
这个修饰的影响主要是对齐,所谓对齐是存储为值的起始地址。变量a的地址&a,本来是4字节对齐,变成了8字节对齐(有的环境对最大对齐数值有限制)。8字节对齐就是&a的地址是8的整数倍。
sizeof(a) = 4; //a 占用的字节数还是4个字节

下面看看在使用了typedef之后对对齐和变量大小的影响:
typedef int int32_t __attribute__((__aligned__(8))) ;
sizeof(int32_t ) = 4; //占用的字节数还是4个字节
这样说明int32_t 声明的变量按照8字节对齐,大小是4字节,这样就会有一个问题,这个变量不能定义数组:
int32_t array[2]; //这样定义编译器会报err

报错的原因是数组的存储在内存中是连续的,而int32_t 只有4字节确要8字节对齐,这样对齐和连续就不能同时保证,就会报错。

4. __attribute__((__aligned__(n)))修饰结构体

定义一个结构体:

typedef struct flag {
	int a;
	char b;
} FLG;
FLG test;

在32位系统下,自然对齐的情况下:sizeof(test) = 8;

 注意__attribute__((__aligned__(n)))的位置。

typedef struct flag {
	int a;
	char b;
}__attribute__((__aligned__(32))) FLG;
FLG test;

sizeof(test) = 32; 
__attribute__((__aligned__(32)))修饰struct flag,结构体按照32字节对齐



typedef struct flag {
	int a;
	char b;
}__attribute__((__aligned__(1))) FLG;
FLG test;

sizeof(test) = 8; 
__attribute__((__aligned__(1)))修饰struct flag,结构体最小对齐值为1,系统默认4字节对齐了结构体成员,1 < 4,忽略1字节对齐。



typedef struct flag {
	int a;
	char b;
}FLG __attribute__((__aligned__(32)));
FLG test;

sizeof(test) = 8; 
__attribute__((__aligned__(32)))修饰FLG变量,只影响对齐,不影响结构的大小,FLG的地址是32字节对齐。

五:#pragma pack(n)选项

正确理解 #pragma(pack(n))
#pragma(pack(n))定义的是最大对齐边界,它可以用来修饰structure也可以用来修饰structure成员变量,它可以增加/减少 structure或者structure成员变量的对齐值。

1,#pragma pack(n)的使用

#pragma pack需要成对使用,

#pragma pack(1)按照1字节对齐。

#pragma pack()取消定义的对齐规则恢复系统默认。

#pragma pack(1)
struct test
{
  char a;
  int  b;
};
#pragma pack

根据我们之前的分析,这个结构体应该要占用8个字节,但是因为加了#pragma pack(1)导致整个结构体按照1字节来对齐,所以sizeof结果是5.

#pragma pack(2)
struct test
{
  char a;
  int  b;
};
#pragma pack

使用#pragma pack(2)整个结构体按照2字节来对齐,所以sizeof结果是6.

六:#pragma pack(n)与__attribute__((aligned(x)))的区别


#pragma pack(n) 的作用范围是整个文件,也就是说结构体本身和结构体成员都需要遵守这个对齐规则;

__attribute__((aligned(x)))的作用范围如果是用在结构体变量上,则对齐规则只限于结构体地址本身,结构体大小不受影响,其内部的成员变量不遵循此规则。

__attribute__((aligned(x)))的作用范围如果是用在结构体上,则对齐规则只限于结构体本身,其内部的成员变量不遵循此规则。

__attribute__((aligned(x)))如果是修饰结构体中的成员变量,则此成员变量需要遵守对齐规则,最后结构体进行对齐时需要和成员变量的最大对齐值成倍数即可(不一定受此规则限制)。

测试样例1:

#pragma pack(push,2)
typedef struct test{
    char a;
    double b;
    char c;
    int d;
}  __attribute__ ((aligned (32))) test ;
#pragma pack(pop)


① #pragma pack(push,2) 将编译器的最大对齐边界设置为 2
② 以 2 为最大对齐边界后,test 的内存对齐情况(先不考虑 aligned 属性):
typedef struct test{
    char a;   // 1B
    double b; //因为最大对齐是2,所以只需要满足2的倍数,不需要满足8的倍数
    		  //填充 1B ,b 本身占据 8B	
    char c;   //对齐时我们要选择最小的对齐值,对于 c最小的对齐值是1(不是2)
              //无填充,自身占据 1B
    int d;    //对齐值为2,故填充 1B,自身占据 4B
} test ;      //整个结构体对齐值为 2(不是8,因为已经设置了最大对齐值是2)
			  //(1B)+(1B+8B)+(1B)+(1B+4B)+0B(已经满足2的倍数,无需对结构体进行填充)
也就是说当前(不考虑 aligned 属性)时,test 类型占据 16B,最大对齐值 2 。
③ 最后来看 aligned 属性,我们前面说了,aligned 属性设置的是最小对齐值,也就是说当前类型或变量的对齐值要大于或等于aligned 属性设置的对齐值,所以编译器重新设置 test类型的对齐值为32,则test struct需要填充16B。

✔结果就是32B 。
测试样例2:

#pragma pack(push,4)
typedef struct  test{
    char a;
    double b;
    char c;
    int d;
}  __attribute__ ((aligned (2))) test ;
#pragma pack(pop)

对应于 test类型的对齐值,大于 aligned 属性设置的最小对齐值的情况,这种情况下可以忽略aligned 属性。

✔结果就是20B 。
测试样例3:

typedef struct  subtest{
    char a;
    double b;
    char c;
    int d;
}subtest;

#pragma pack(push,2)
typedef struct  test{
    char a;
    subtest b;
}  __attribute__ ((aligned (4))) test ;
#pragma pack(pop)

① test 的内存对齐情况(先不考虑 aligned 属性)
typedef struct  subtest{
    char a;
    double b;
    char c;
    int d;
}subtest;

#pragma pack(push,2)
typedef struct  test{
    char a;    // 1B
    subtest b; //对于结构体subtest,原本以 8B做为对齐值
               //但这里设置了最大对齐值2,所以 填充1B,自身占据 24B
} test ;       // (1B) + (1B+24B) + 0B(已经是2的倍数,不需要再填充)
#pragma pack(pop)
② 最后来看 aligned 属性,设置的最小对齐值4 > 2,所以 test 结构体需要填充 2B,以构成4的倍数。

✔结果就是28B 。

七:含位域结构体的sizeof大小

前面已经说过,位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof,只是考虑到其特殊性而将其专门列了出来。C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。 使用位域的主要目的是压缩存储,

其大致规则为: 

1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字 段将紧邻前一个字段存储,直到不能容纳为止; 

2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字 段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 

3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方 式,Dev-C++采取压缩方式;

4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 

5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

 所以使用位域时不同编译器平台下占用大小不同,尽量还是自己实际测试一下。

测试:

struct test
{
    char a:1;
    char b:2;
    int c:3;
    char d:2;
};
test t1;

int len=sizeof(t1);   //len=12

<think>我们正在讨论C语言中的__attribute__关键字。根据提供的引用,我们可以总结如下: 引用[1]提到:__attribute__是GNU C提供的一个编译器扩展特性,用于指定变量、函数、类型等的特殊属性。它可以用于优化内存对齐、打包结构体、指定函数行为等。 引用[2]具体说明了alignedpacked属性的使用:aligned属性用于指定变量或类型的对齐方式,packed属性用于取消对齐(即按实际字节紧密排列)。 引用[3]给出了一个使用aligned属性的例子,展示了如何通过aligned属性改变结构体的对齐方式,从而影响结构体的大小。 现在,我将详细解释__attribute__关键字的功能和使用方式。 ### 1. __attribute__的功能 `__attribute__`是GNU C的一个扩展,它允许开发者向编译器提供关于变量、函数、类型等的额外信息,以便编译器进行优化或生成特定代码。常见的功能包括: - 控制结构体的对齐方式(aligned)和打包(packed) - 指定函数的特殊行为(如:noreturn, format, section等) - 变量或函数的可见性(visibility) - 等等 ### 2. 基本语法 `__attribute__`的语法格式如下: ```c __attribute__((attribute_name)) ``` 或者带有参数: ```c __attribute__((attribute_name(params))) ``` 它可以放在函数、变量或类型的声明之后。 ### 3. 常用属性详解 #### 3.1 aligned 属性 `aligned`属性用于指定变量或类型的对齐方式。对齐值必须是2的幂次方。 示例1:指定变量的对齐方式 ```c int my_var __attribute__((aligned(16))); ``` 这表示`my_var`变量将按照16字节对齐。 示例2:指定结构体的对齐方式 ```c struct my_struct { char a; int b; } __attribute__((aligned(8))); ``` 这样,结构体`my_struct`的实例将按照8字节对齐。 #### 3.2 packed 属性 `packed`属性用于取消结构体或联合体的填充,使其成员紧密排列,不进行对齐。这在需要硬件或网络协议交互时非常有用。 示例: ```c struct __attribute__((packed)) my_packed_struct { char a; int b; }; ``` 这样,结构体`my_packed_struct`的大小就是`sizeof(char)+sizeof(int)`(即1+4=5字节),而不是按照默认对齐方式(可能是8字节)。 #### 3.3 section 属性 `section`属性用于将函数或变量放入指定的段(section)中,这在嵌入式开发中常用于将代码或数据放入特定的内存区域。 示例: ```c int my_data __attribute__((section(".my_section"))); ``` 将变量`my_data`放入名为`.my_section`的段中。 #### 3.4 noreturn 属性 `noreturn`属性用于告诉编译器该函数不会返回(例如,函数中调用了`exit()`或无限循环)。这有助于编译器优化并避免生成不必要的代码。 示例: ```c void __attribute__((noreturn)) my_exit() { exit(0); } ``` #### 3.5 format 属性 `format`属性用于指定函数使用类似`printf`或`scanf`的格式字符串,这样编译器可以检查格式字符串和参数是否匹配。 示例: ```c void my_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); ``` 这表示`my_printf`函数的第一个参数是格式字符串,第二个及以后的参数是可变参数,类似于`printf`。 ### 4. 高级用法:组合属性 可以在同一个声明中使用多个属性,例如: ```c struct my_struct { char a; int b; } __attribute__((packed, aligned(4))); ``` 这表示结构体被紧密打包,但整个结构体按照4字节对齐。 ### 5. 注意事项 - `__attribute__`是GNU C扩展,不是标准C语言的一部分,因此可能在其他编译器(如MSVC)中不可用。 - 在使用时,为了可移植性,可以使用宏定义来包装,例如: ```c #ifdef __GNUC__ #define PACKED __attribute__((packed)) #else #define PACKED #endif struct PACKED my_struct { // ... }; ``` ### 6. 示例分析(引用[3]) 引用[3]中的例子: ```c struct p { int a; char b; char c; } __attribute__((aligned(4))) pp; struct q { int a; char b; struct n qn; // 注意:这里原例子中struct n未定义,我们假设struct n是已知的 char c; } __attribute__((aligned(8))) qq; ``` 输出: ``` sizeof(int)=4,sizeof(short)=2.sizeof(char)=1 pp=8,qq=24 ``` 分析: - 结构体`p`:成员`int a`(4字节),`char b`(1字节),`char c`(1字节),默认情况下,结构体大小可能是4+1+1=6,但因为有`aligned(4)`,所以整个结构体按照4字节对齐,大小变为8(因为6不是4的倍数,需要填充到8)。 - 结构体`q`:我们假设`struct n`的大小为8(比如,它可能是两个int,或者一个double),那么结构体`q`的成员:`int a`(4字节),`char b`(1字节),然后有3字节填充(为了对齐`struct n`到8字节),接着`struct n qn`(8字节),最后`char c`(1字节),然后需要填充到8的倍数,所以总大小=4+1+3(填充)+8+1=17,然后填充到24(因为24是8的倍数,17向上取整到24?)。但是,这里我们缺少`struct n`的具体定义,所以实际计算可能不同。但根据输出24,我们可以推断`struct n`的大小可能是8,加上其他成员和填充,最终得到24。 ### 7. 总结 `__attribute__`是GNU C中一个强大的扩展,它允许开发者更精细地控制数据布局、函数行为等。合理使用可以优化程序性能,但需要注意可移植性问题。 §§相关问题§§ 1. __attribute__((aligned))和__attribute__((packed))在结构体中的使用有什么区别? 2. 如何用__attribute__((section))将函数放入自定义段? 3. __attribute__((format))如何使用,它有什么好处? 4. 在非GCC编译器(如MSVC)中如何实现类似__attribute__的功能?</think>### C语言 `__attribute__` 关键字详解 `__attribute__` 是 GCC 编译器提供的扩展语法,用于向编译器传递变量、函数或类型的额外属性信息,以实现底层优化或特殊控制。以下是其核心功能和使用方法: --- #### 一、核心功能 1. **内存对齐控制** - `aligned`:指定变量或结构体的对齐方式 ```c struct S { int a; char b; } __attribute__((aligned(8))); // 按8字节对齐 ``` - `packed`:取消结构体填充(紧密排列成员) ```c struct P { int a; char b; } __attribute__((packed)); // 无填充字节 ``` 2. **函数行为控制** - `noreturn`:标记函数永不返回(如 `exit()`) ```c void fatal() __attribute__((noreturn)); ``` - `format`:校验格式化字符串(类似 `printf`) ```c void log(const char *fmt, ...) __attribute__((format(printf, 1, 2))); ``` 3. **变量/函数位置控制** - `section`:将对象放入指定内存段 ```c int var __attribute__((section(".my_section"))); ``` 4. **优化提示** - `always_inline`:强制内联函数 ```c inline void foo() __attribute__((always_inline)); ``` --- #### 二、详细使用示例 1. **结构体对齐控制(引用[3]分析)** ```c struct p { int a; // 4字节 char b; // 1字节 char c; // 1字节 } __attribute__((aligned(4))) pp; // 总大小=8(4+1+1+2填充) struct q { int a; // 4字节 char b; // 1字节 struct n qn;// 假设sizeof(struct n)=8 char c; // 1字节 } __attribute__((aligned(8))) qq; // 总大小=24(4+1+3填充+8+1+7填充) ``` **输出分析**: - `pp=8`:按4字节对齐后,结构体大小扩充至8字节 - `qq=24`:按8字节对齐后,填充字节使总大小为8的倍数[^3] 2. **跨平台宏定义(引用[2])** ```c #ifdef __GNUC__ #define PACKED __attribute__((packed)) #else #define PACKED #endif struct packet { int id; char data[3]; } PACKED; // 紧密排列 ``` 3. **函数属性应用** ```c // 函数放入.init段,且不返回 void __attribute__((section(".init"), noreturn)) boot_init() { while(1); // 永不返回 } ``` --- #### 三、使用注意事项 1. **编译器兼容性** - 仅适用于 GCC 和 Clang,非标准 C 语法 - MSVC 需用 `#pragma pack` 等替代方案 2. **对齐值限制** - `aligned(N)` 的 $$N$$ 必须是 2 的幂次(如 1, 2, 4, 8...) 3. **性能权衡** - `packed` 节省内存但降低访问速度 - `aligned` 提升访问效率但增加内存占用 --- #### 四、典型应用场景 1. **硬件寄存器映射** 用 `packed` 精确控制结构体布局匹配寄存器 2. **嵌入式内存管理** 用 `section` 将关键函数放入特定内存区域 3. **高性能计算** 用 `aligned` 确保 SIMD 指令的数据对齐要求 > 提示:详细技术原理可参考《C语言\_\_attribute\_\_技术详解》[^1] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值