【信息表示】地址对齐

本节研究地址对齐的相关问题;

地址对齐

说明几点:

(1)地址对齐可以简化处理器和存储器系统之间的硬件设计;如果可以保证所有的int类型地址对齐成4的倍数,就可以使用一个存储器操作读或写值,相反如果一个int型的变量存放在奇地址上,那么要进行两次存储器读后进行拼凑成int型变量才可以;

(2)在GCC中,2字节数据类型(如short)地址对齐必须是2的倍数,因此地址最低位必须是0,而较大的数据类型(如int、int*、float、包括long long、double、long double)的地址对齐必须是4的倍数,因此地址最低两位必须是00;注意Mircosoft对齐更严格,任何K字节的基本对象的地址必须是K的倍数,也就是说long long、double这样的地址必须是8的倍数;

(3)long double(实际上为10个字节),在GCC和windows都中分配12个字节,都进行4字节地址对齐;

(4)在#pragma pack (n)中,n为1,2,4,8这样的2的次方;在缺省情况下,编译器按照数据类型的自身边界进行对齐,如short的对齐边界为2字节;但是在指定了#pragma pack
(n)后,对于所有比n大的数据类型边界都均按照n来对齐;

(5)在__attribute__((aligned(n))中,n为1,2,4,8这样的2的次方;它表示在一个结构体(联合体)在分配空间时的地址对齐方式,最后的地址一定是按照n字节对齐的;

实验验证

(注:本文的编译器为GCC)

#pragma pack示例

代码片段如下:

struct P1
{
  float a;
  char b;
  int c;
  char d;
};

#pragma pack (1)
struct P2
{
  float a;
  char b;
  int c;
  char d;
};
#pragma pack ()

#pragma pack (2)
struct P3
{
  float a;
  char b;
  int c;
  char d;
};
#pragma pack ()

#pragma pack (4)
struct P4
{
  float a;
  char b;
  int c;
  char d;
};
#pragma pack ()

#pragma pack (8)
struct P5
{
  float a;
  char b;
  int c;
  char d;
};
#pragma pack ()

int main(void)
{
  printf("P1, size %d\n", sizeof(P1));
  printf("P2, size %d\n", sizeof(P2));
  printf("P3, size %d\n", sizeof(P3));
  printf("P4, size %d\n", sizeof(P4));
  printf("P5, size %d\n", sizeof(P5));

  printf("P1, address------\n");
  P1 p1;
  printf("%p\n", &p1);
  printf("a: %p\n", &p1.a);
  printf("b: %p\n", &p1.b);
  printf("c: %p\n", &p1.c);
  printf("d: %p\n\n", &p1.d);

  printf("P2, address------\n");
  P2 p2;
  printf("%p\n", &p2);
  printf("a: %p\n", &p2.a);
  printf("b: %p\n", &p2.b);
  printf("c: %p\n", &p2.c);
  printf("d: %p\n\n", &p2.d);

  printf("P3, address------\n");
  P3 p3;
  printf("%p\n", &p3);
  printf("a: %p\n", &p3.a);
  printf("b: %p\n", &p3.b);
  printf("c: %p\n", &p3.c);
  printf("d: %p\n\n", &p3.d);

  printf("P4, address------\n");
  P4 p4;
  printf("%p\n", &p4);
  printf("a: %p\n", &p4.a);
  printf("b: %p\n", &p4.b);
  printf("c: %p\n", &p4.c);
  printf("d: %p\n\n", &p4.d);

  printf("P5, address------\n");
  P5 p5;
  printf("%p\n", &p5);
  printf("a: %p\n", &p5.a);
  printf("b: %p\n", &p5.b);
  printf("c: %p\n", &p5.c);
  printf("d: %p\n\n", &p5.d);
}

输出如下:

P1, size 16
P2, size 10
P3, size 12
P4, size 16
P5, size 16
P1, address------
0xbfacc840
a: 0xbfacc840
b: 0xbfacc844
c: 0xbfacc848
d: 0xbfacc84c

P2, address------
0xbfacc836
a: 0xbfacc836
b: 0xbfacc83a
c: 0xbfacc83b
d: 0xbfacc83f

P3, address------
0xbfacc82a
a: 0xbfacc82a
b: 0xbfacc82e
c: 0xbfacc830
d: 0xbfacc834

P4, address------
0xbfacc818
a: 0xbfacc818
b: 0xbfacc81c
c: 0xbfacc820
d: 0xbfacc824

P5, address------
0xbfacc808
a: 0xbfacc808
b: 0xbfacc80c
c: 0xbfacc810
d: 0xbfacc814

说明几点:

(1)P1和P4是同样的地址对齐,都是4地址对齐;而对于P2各数据类型只进行1地址对齐,因此大小就是所有数据类型的大小;对于P3由于#pragma pack (2),因此所有的数据类型对齐边界都不会超过2,因此像int,float这样的数据类型的对齐边界也是2;

(2)对于P4和P5来说,由于数据类型中任何一个数据类型的对齐边界最大为4,而#pragma pack (4或8)的指定并不会影响这些数据类型的对齐边界;

上述各个结构体的示意图如下:

__attribute__((aligned(n)))示例

代码片段如下

struct P1
{
  float a;
  char b;
  int c;
  char d;
}__attribute__((aligned(4)));

#pragma pack (1)
struct P2
{
  float a;
  char b;
  int c;
  char d;
}__attribute__((aligned(4)));
#pragma pack ()

#pragma pack (2)
struct P3
{
  float a;
  char b;
  int c;
  char d;
}__attribute__((aligned(4)));
#pragma pack ()

int main(void)
{
  printf("P1, size %d\n", sizeof(P1));
  printf("P2, size %d\n", sizeof(P2));
  printf("P3, size %d\n", sizeof(P3));

  printf("P1, address------\n");
  P1 p1;
  printf("%p\n", &p1);
  printf("a: %p\n", &p1.a);
  printf("b: %p\n", &p1.b);
  printf("c: %p\n", &p1.c);
  printf("d: %p\n\n", &p1.d);

  printf("P2, address------\n");
  P2 p2;
  printf("%p\n", &p2);
  printf("a: %p\n", &p2.a);
  printf("b: %p\n", &p2.b);
  printf("c: %p\n", &p2.c);
  printf("d: %p\n\n", &p2.d);

  printf("P3, address------\n");
  P3 p3;
  printf("%p\n", &p3);
  printf("a: %p\n", &p3.a);
  printf("b: %p\n", &p3.b);
  printf("c: %p\n", &p3.c);
  printf("d: %p\n\n", &p3.d);
}

输出如下:

P1, size 16
P2, size 12
P3, size 12
P1, address------
0xbf98d9e0
a: 0xbf98d9e0
b: 0xbf98d9e4
c: 0xbf98d9e8
d: 0xbf98d9ec

P2, address------
0xbf98d9d4
a: 0xbf98d9d4
b: 0xbf98d9d8
c: 0xbf98d9d9
d: 0xbf98d9dd

P3, address------
0xbf98d9c8
a: 0xbf98d9c8
b: 0xbf98d9cc
c: 0xbf98d9ce
d: 0xbf98d9d2

说明几点:

(1)__attribute__((aligned(4)))仅仅是将结构体本身对齐,对结构体的内部数据的对齐方式是没有影响的;

结构体示意图如下:

long long和double示例

代码片段如下

struct P1
{
  char a;
  double b;
  char c;
  long long d;
};

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

  printf("P1, address------\n");
  P1 p1;
  printf("%p\n", &p1);
  printf("a: %p\n", &p1.a);
  printf("b: %p\n", &p1.b);
  printf("c: %p\n", &p1.c);
  printf("d: %p\n\n", &p1.d);
}

输出如下:

P1, size 24
P1, address------
0xbfbe4348
a: 0xbfbe4348
b: 0xbfbe434c
c: 0xbfbe4354
d: 0xbfbe4358

说明几点:

(1)double和long long在GCC中并不是按照8个字节对齐的,而是按照4字节对齐的;和windows中不一样;

结构体示意图如下:

时间: 2024-08-09 02:13:30

【信息表示】地址对齐的相关文章

[C/C++]_[中级]_[数据地址对齐]

场景: 1. 有些频繁使用的指针变量地址不对齐的话运行效率和对齐后的运行效率差别很大,所以在创建堆空间时,有必要对内存地址对齐提高运行效率. 2. 有些音视频处理的代码或者说自定义的malloc基本都是地址对齐的. 3. 使用原子访问的互锁函数时,InterlockedExchangeAdd都需要地址对齐. 4. 主要还是宏APR_ALIGN, 这个说是Apache源码里,就借用一下吧. 解决方案: 1. 其实就是让地址值对对齐量求模为0, 地址值最多增加n-1个偏移地址就可就可以整出n.  &

地址对齐

ARM指令和51单片机指令不同,ARM所有指令的长度都是固定的,都是4个字节32位.而51单片机的指令的长度不是固定的,有单字节指令也有双字节指令. ARM的数据总线宽度也是32位的,所以ARM可以处理32位的数据,这就要求所有的数据也必须是存放在地址为4的整数倍的单元处. 正常情况下,R15中的值应该为4的整数倍,访问数据时给的地址也应该是4的整数倍.这种情况就是地址对齐.如果不满足这种情况,那么就是非地址对齐. 非地址对齐访问程序存储器: 当访问程序存储器时,所给的地址是非对齐的,那么对于A

C语言精要总结-内存地址对齐与struct大小判断篇

在笔试时,经常会遇到结构体大小的问题,实际就是在考内存地址对齐.在实际开发中,如果一个结构体会在内存中高频地分配创建,那么掌握内存地址对齐规则,通过简单地自定义对齐方式,或者调整结构体成员的顺序,可以有效地减少内存使用.另外,一些不用边界对齐.可以在任何地址(包括奇数地址)引用任何数据类型的的机器,不在本文讨论范围之内. 什么是地址对齐 计算机读取或者写入存储器地址时,一般以字(因系统而异,32位系统为4个字节)大小(N)的块来执行操作.数据对齐就是将数据存储区的首地址对齐字大小(N)的某个整数

Avalon总线的地址对齐与NIOS编程

首先关于地址对齐的概念我不详述了,大家可以参考这篇文章:Avalon总线的地址对齐:Dynamic Addressing和Native Addressing. 假设我们定制了一个外设,数据宽度是32位,地址是2位.如果我们想让地址线干点其他的事,而不是传地址,例如: 1 if(cs&wr) 2 begin 3 case(addr) 4 2'b00: //自定义 5 2'b01: //自定义 6 endcase 7 end 那在Nios EDS中,如何确定addr呢?也就是说我们使用IOWR_32

Avalon总线的地址对齐与EDS编程

首先关于地址对齐的概念我不详述了,大家可以参考这篇文章:Avalon总线的地址对齐:Dynamic Addressing和Native Addressing. 假设我们定制了一个外设,数据宽度是32位,地址是2位.如果我们想让地址线干点其他的事,而不是传地址,例如: 1 if(cs&wr) 2 begin 3 case(addr) 4 2'b00: //自定义 5 2'b01: //自定义 6 endcase 7 end 那在Nios EDS中,如何确定addr呢?也就是说我们使用IOWR_32

C语言结构体变量内存分配与地址对齐

地址对齐简单来说就是为了提高访问内存的速度. 数组的地址分配比较简单,由于数据类型相同,地址对齐是一件自然而然的事情. 结构体由于存在不同基本数据类型的组合,所以地址对齐存在不同情况,但总体来说有以下规则: 原则1:数据成员对齐规则:结构的数据成员,第一个数据成员放在偏移量(offset)为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储). 原则2:收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大

#pragma pack 定义变量的起始存放地址对齐方式

pack用来指定变量在内存中的存放起始地址对齐方式: 具体用法如下: 1 #pragma pack(push,n) // 保存当前系统设置的对齐方式,压入堆栈,然后设置当前对齐方式为n字节对齐,n通常取 1 2 4 8 2 3 ......... 4 5 #pragma pack(pop) // 恢复当前的对齐方式 pack对齐方式:选当前数据类型本身占用字节数与pack指定的对齐字节数两者之间的最小值, 用这个最小值的整数倍作为存放的起始地址. 例1: 1 #pragma pack(push,

【转】内存地址对齐运算

做地址对齐的代码: #define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //为了满足需要内存对齐的系统 这段代码做的事情就是,给定一个变量n,算出这个变量对齐到某个字长(整型的字节数)整数倍的字节数.这段代码有些难以理解.那么慢慢分析下吧. 假设有一个地址n,要把n按m对齐,无非就是找到大于等于n且整除m的最小的那个数. 我们定义一个宏函数F,它计算n按m对齐的结果,则按照上段代码的逻辑,F定义为: #def

【转】一个跟地址对齐有关的应用异常案例

@2019-01-29 [小记] 一个跟地址对齐有关的应用异常案例 原文地址:https://www.cnblogs.com/skullboyer/p/10335373.html