对于不同的数据类型,内存对齐是否不同

C中的不同数据类型(如charshortintlongfloatdouble是否具有不同的内存对齐边界? 在32位字对齐的字节可寻址操作系统中,如何访问charshort来访问intfloat ? 在这两种情况下,CPU是否读取完整的32位字? 当int不在边界时会发生什么? 如何在任何内存地址读取char

正如其他人所指出的那样,简短的答案是编译器将为它编译的架构做最好的事情。 它可以将它们与原始单词大小对齐。 它可能不会。 这是一个展示这一点的示例程序:

 #include  int main() { using namespace std; char c; short s; int i; cout << "sizeof(char): " << sizeof(char) << endl; cout << "sizeof(short): " << sizeof(short) << endl; cout << "sizeof(int): " << sizeof(int) << endl; cout << "short is " << (int)&s - (int)&c << " bytes away from a char" << endl; cout << "int is " << (int)&i - (int)&s << " bytes away from a short" << endl; } 

输出:

 sizeof(char): 1 sizeof(short): 2 sizeof(int): 4 short is 1 bytes away from a char int is 4 bytes away from a short 

如您所见,它在int和short之间添加了一些填充。 它并没有打扰短片。 在其他情况下,反之亦然。 优化规则很复杂。

并且,警告:编译器比你聪明。 除非你有一个非常非常好的理由,否则不要使用填充和对齐。 只要相信编译器正在做的事情是正确的。

这取决于编译器和您定义变量的方式。 大多数编译器的默认行为是将变量对齐,以便在给定平台上产生最快的访问。 对齐变量为您提供最佳性能。

但是,像gcc这样的编译器提供了编译器特定的指令 ,可以用来“打包”不同类型的相邻变量(以及因此大小),以牺牲性能为代价来节省内存(但这是你可以决定的,通过使用包装指令。)看到这个问题。

在读取char / short时,CPU可以读取完整的32位字(可能更多以获得整个缓存行)。

很多问题……

C中的不同数据类型(如char,short,int,long,float,double)是否具有不同的内存对齐边界?

是。 确切的对齐边界是特定于编译器的,有些可以让您更改它们打包struct的方式。 (最好插入填充字段,以免它成为一个问题。)

在32位字对齐的字节可寻址操作系统中,如何访问char或short来访问int或float?

实际上,这取决于架构。 我已经看到一些在总线上有字节启用线路,并将使用它们来访问他们想要的内存部分。 在其他情况下,非I / O存储器访问导致读取或写入整个高速缓存行。

在这两种情况下,CPU是否读取完整的32位字?

不必要。 使用字节启用,您不必读取完整的32位字。 Byte Enables还允许您在> 8位架构上写入单个字节,而无需执行读取 – 修改 – 写入。

当int不在边界时会发生什么?

某些体系结构(例如x86,IIRC)将执行多次访问并为您加入部件。 其他(例如PowerPC)将生成总线错误或类似的exception。

如何在任何内存地址读取字符?

因为地址是通过体系结构上的字节量化的。 并非所有体系结构都适用。 DSP以字对齐指针而闻名,即指针是字地址,而不是字节地址。 (我不得不为其中一个写一个串口驱动程序sizeof(char) == sizeof(short) == 1 == 16位。所以你必须在简单的代码之间做出选择,这会浪费一半的RAM,而且需要大量的字节打包/解包代码。)

简短回答:这取决于您的编译器和架构。 大多数编译器都有某种命令行选项或#pragma ,您可以使用它们来手动指定或更改变量的对齐方式。

我曾经使用过这样的东西来研究各种类型的数据对齐:

 union { struct { char one; char two; char three; char four; } chars; struct { short one; short two; short three; short four; } shorts; struct { int one; int two; int three; int four; } ints; struct { double one; double two; double three; double four; } doubles; /* etc, etc */ } many_types; 

通过查看每个struct成员的地址与sizeof()该成员的地址,您可以了解编译器如何对齐不同的数据类型。

你可能会研究这个程序的输出 – 在运行MacOS X 10.6.2的Intel Mac上为32位和64位编译。

 /* @(#)File: $RCSfile: typesize.c,v $ @(#)Version: $Revision: 1.7 $ @(#)Last changed: $Date: 2008/12/21 18:25:17 $ @(#)Purpose: Structure sizes/alignments @(#)Author: J Leffler @(#)Copyright: (C) JLSS 1990,1997,2004,2007-08 @(#)Product: :PRODUCT: */ #include  #include  #include  #if __STDC_VERSION__ >= 199901L #include  #endif /* __STDC_VERSION__ */ #define SPRINT(x) printf("%2u = sizeof(" #x ")\n", (unsigned int)sizeof(x)) int main(void) { /* Basic Types */ SPRINT(char); SPRINT(unsigned char); SPRINT(short); SPRINT(unsigned short); SPRINT(int); SPRINT(unsigned int); SPRINT(long); SPRINT(unsigned long); #if __STDC_VERSION__ >= 199901L SPRINT(long long); SPRINT(unsigned long long); SPRINT(uintmax_t); #endif /* __STDC_VERSION__ */ SPRINT(float); SPRINT(double); SPRINT(long double); SPRINT(size_t); SPRINT(ptrdiff_t); SPRINT(time_t); /* Pointers */ SPRINT(void *); SPRINT(char *); SPRINT(short *); SPRINT(int *); SPRINT(long *); SPRINT(float *); SPRINT(double *); /* Pointers to functions */ SPRINT(int (*)(void)); SPRINT(double (*)(void)); SPRINT(char *(*)(void)); /* Structures */ SPRINT(struct { char a; }); SPRINT(struct { short a; }); SPRINT(struct { int a; }); SPRINT(struct { long a; }); SPRINT(struct { float a; }); SPRINT(struct { double a; }); SPRINT(struct { char a; double b; }); SPRINT(struct { short a; double b; }); SPRINT(struct { long a; double b; }); SPRINT(struct { char a; char b; short c; }); SPRINT(struct { char a; char b; long c; }); SPRINT(struct { short a; short b; }); SPRINT(struct { char a[3]; char b[3]; }); SPRINT(struct { char a[3]; char b[3]; short c; }); SPRINT(struct { long double a; }); SPRINT(struct { char a; long double b; }); #if __STDC_VERSION__ >= 199901L SPRINT(struct { char a; long long b; }); #endif /* __STDC_VERSION__ */ return(0); } 

64位编译的输出:

  1 = sizeof(char) 1 = sizeof(unsigned char) 2 = sizeof(short) 2 = sizeof(unsigned short) 4 = sizeof(int) 4 = sizeof(unsigned int) 8 = sizeof(long) 8 = sizeof(unsigned long) 8 = sizeof(long long) 8 = sizeof(unsigned long long) 8 = sizeof(uintmax_t) 4 = sizeof(float) 8 = sizeof(double) 16 = sizeof(long double) 8 = sizeof(size_t) 8 = sizeof(ptrdiff_t) 8 = sizeof(time_t) 8 = sizeof(void *) 8 = sizeof(char *) 8 = sizeof(short *) 8 = sizeof(int *) 8 = sizeof(long *) 8 = sizeof(float *) 8 = sizeof(double *) 8 = sizeof(int (*)(void)) 8 = sizeof(double (*)(void)) 8 = sizeof(char *(*)(void)) 1 = sizeof(struct { char a; }) 2 = sizeof(struct { short a; }) 4 = sizeof(struct { int a; }) 8 = sizeof(struct { long a; }) 4 = sizeof(struct { float a; }) 8 = sizeof(struct { double a; }) 16 = sizeof(struct { char a; double b; }) 16 = sizeof(struct { short a; double b; }) 16 = sizeof(struct { long a; double b; }) 4 = sizeof(struct { char a; char b; short c; }) 16 = sizeof(struct { char a; char b; long c; }) 4 = sizeof(struct { short a; short b; }) 6 = sizeof(struct { char a[3]; char b[3]; }) 8 = sizeof(struct { char a[3]; char b[3]; short c; }) 16 = sizeof(struct { long double a; }) 32 = sizeof(struct { char a; long double b; }) 16 = sizeof(struct { char a; long long b; }) 

32位编译的输出:

  1 = sizeof(char) 1 = sizeof(unsigned char) 2 = sizeof(short) 2 = sizeof(unsigned short) 4 = sizeof(int) 4 = sizeof(unsigned int) 4 = sizeof(long) 4 = sizeof(unsigned long) 8 = sizeof(long long) 8 = sizeof(unsigned long long) 8 = sizeof(uintmax_t) 4 = sizeof(float) 8 = sizeof(double) 16 = sizeof(long double) 4 = sizeof(size_t) 4 = sizeof(ptrdiff_t) 4 = sizeof(time_t) 4 = sizeof(void *) 4 = sizeof(char *) 4 = sizeof(short *) 4 = sizeof(int *) 4 = sizeof(long *) 4 = sizeof(float *) 4 = sizeof(double *) 4 = sizeof(int (*)(void)) 4 = sizeof(double (*)(void)) 4 = sizeof(char *(*)(void)) 1 = sizeof(struct { char a; }) 2 = sizeof(struct { short a; }) 4 = sizeof(struct { int a; }) 4 = sizeof(struct { long a; }) 4 = sizeof(struct { float a; }) 8 = sizeof(struct { double a; }) 12 = sizeof(struct { char a; double b; }) 12 = sizeof(struct { short a; double b; }) 12 = sizeof(struct { long a; double b; }) 4 = sizeof(struct { char a; char b; short c; }) 8 = sizeof(struct { char a; char b; long c; }) 4 = sizeof(struct { short a; short b; }) 6 = sizeof(struct { char a[3]; char b[3]; }) 8 = sizeof(struct { char a[3]; char b[3]; short c; }) 16 = sizeof(struct { long double a; }) 32 = sizeof(struct { char a; long double b; }) 12 = sizeof(struct { char a; long long b; }) 

你可以用结构玩各种各样的游戏。 关键是不同类型的对齐要求确实不同。 根据平台的不同,您可能会有或多或少的严格要求。 SPARC很挑剔; 如果你做错了访问,英特尔往往会做更多工作(所以它很慢,但有效); 旧的DEC Alpha芯片(我认为MIPS RISC芯片)可以切换到不同的行为,要么更高效,总是需要对齐访问,要么效率低,无法模仿英特尔芯片的function。

在许多平台上,错误对齐的内存访问会带来性能损失甚至可能导致程序中断。

例如,在x86上,如果设置了EFLAGS.ACCR0.AM则通过错误对齐的指针访问内存可能导致SIGBUS被引发(请参阅此答案 )。

是的,它们确实有不同的内存对齐要求。 在现实生活中,通常假定/要求在边界处对齐特定类型,该边界与类型的大小相同,尽管理论上大小对齐的概念彼此没有关联。

在某些特定情况下,平台可能需要将一条数据对齐到比相应数据类型的大小更严格(更大)的边界。 出于性能原因,例如,或出于某些其他特定于平台的原因,这可能是必需的。

如果数据未对齐,则行为取决于平台。 在某些硬件平台上,尝试访问未对齐的数据将导致崩溃(例如,Sun机器)。 在其他硬件平台上,它可能会导致访问效率和/或primefaces性的轻微损失,而没有其他不利影响(例如,Intel x86机器)。

这里值得一提的一个重要细节是,从迂腐的角度来看,对于C程序,术语平台是指编译器提供的环境,而不是硬件提供的环境。 编译器总是可以自由地实现一个抽象层,它将C程序与底层硬件平台隔离开来,完全(或几乎完全)隐藏任何硬件强加的要求。 例如,即使底层硬件平台确实强加了这样的要求,也可以实现一个将从C程序中删除任何对齐要求的实现。 然而在实践中,出于对C语言哲学很重要的效率考虑,大多数时候(如果不总是)硬件对齐要求也适用于C程序。

是。 在一个典型但不通用的例子中:

1个字符
2短
4 int
4漂浮
8双

CPU的作用是CPU和编译器的业务。 在约束的CPU上,编译器会考虑到这一点。 在RISC-y芯片上,CPU可能必须加载32位并移位和屏蔽以获得char。