C中的不同数据类型(例如char
,short
,int
,long
,float
,double
是否具有不同的内存对齐方式边界?在32位字对齐字节可寻址操作系统中,如何访问char
或short
与访问int
或float
不同?在这两种情况下,CPU是否读取完整的32位字?当int
不在边界时会发生什么?如何在任何内存地址读取char
?
答案 0 :(得分:6)
正如其他人所指出的那样,简短的回答是编译器会为它编译的架构做最好的事情。它可以将它们与原始单词大小对齐。它可能不会。这是一个展示这一点的示例程序:
#include <iostream>
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之间添加了一些填充。它并没有打扰短片。在其他情况下,反之亦然。优化规则很复杂。
并且,警告:编译器比你聪明。除非你有一个非常非常好的理由,否则不要使用填充和对齐。只要相信编译器正在做的事情是正确的。
答案 1 :(得分:5)
这取决于编译器和您定义变量的方式。大多数编译器的默认行为是将变量对齐,以便在给定平台上实现最快的访问。对齐变量可以为您提供最佳性能。
然而,像gcc这样的编译器提供compiler specific directives可以用来“打包”不同类型的相邻变量(以及因此大小),以节省成本来节省内存(但这就是你得到的)通过使用包装指令来决定。)见question.
当读取char / short时,CPU可能会读取一个完整的32位字(可能还有更多来获取整个缓存行)。
答案 2 :(得分:4)
很多问题......
C中的不同数据类型如char,short,int,long,float,double是否有不同的内存对齐边界?
是。确切的对齐边界是特定于编译器的,有些可以让您更改它们打包struct
的方式。 (最好插入填充字段,以免让它成为一个问题。)
在32位字对齐的字节可寻址操作系统中,如何访问char或short来访问int或float?
实际上,这取决于架构。我已经看到一些在总线上有字节启用线路,并将使用它们来访问他们想要的内存部分。在其他情况下,非I / O内存访问会导致读取或写入整个缓存行。
在这两种情况下,CPU是否读取完整的32位字?
不一定。使用字节启用,您不必读取完整的32位字。 Byte Enables还允许您在&gt; 8位架构上写入单个字节,而无需执行读 - 修改 - 写入。
当int不在边界时会发生什么?
某些体系结构(例如x86,IIRC)将执行多次访问并为您加入这些部分。其他(例如PowerPC)将生成总线错误或类似的异常。
如何在任何内存地址读取字符?
因为地址是按体系结构中的字节量化的。并非所有架构都是如此。 DSP以字对齐指针而闻名,即指针是字地址,而不是字节地址。 (我必须为其中一个编写一个串口驱动程序。sizeof(char) == sizeof(short) == 1
== 16位。所以你必须在浪费一半RAM的简单代码和大量字节打包/解包代码之间做出选择。)
答案 3 :(得分:2)
简短回答:这取决于您的编译器和架构。大多数编译器都有某种命令行选项或#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;
通过查看每个结构成员与sizeof()
该成员的地址,您可以了解编译器如何对齐不同的数据类型。
答案 4 :(得分:2)
你可能会研究这个程序的输出 - 在运行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 <stdio.h>
#include <time.h>
#include <stddef.h>
#if __STDC_VERSION__ >= 199901L
#include <inttypes.h>
#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芯片)可以切换到不同的行为,或者更高效,总是需要对齐访问,或者效率更低,无法模仿英特尔芯片的功能。
答案 5 :(得分:1)
在许多平台上,错误对齐的内存访问会带来性能损失,甚至可能导致程序中断。
例如,在x86上,如果设置了SIGBUS
和EFLAGS.AC
,则通过错误对齐的指针访问内存可能会导致CR0.AM
被提升(请参阅this answer)。< / p>
答案 6 :(得分:1)
是的,它们确实有不同的内存对齐要求。在现实生活中,通常假定/需要在与该类型的 size 相同的边界处对齐特定类型,尽管理论上 size 和<的概念em> alignment 彼此之间没有任何联系。
在某些特定情况下,平台可能需要将一条数据对齐到比相应数据类型的大小更严格(更大)的边界。出于性能原因,例如,或出于某些其他特定于平台的原因,这可能是必需的。
如果数据未对齐,则行为取决于平台。在某些硬件平台上,尝试访问未对齐的数据将导致崩溃(例如,Sun机器)。在其他硬件平台上,它可能会导致访问效率和/或原子性的轻微损失,而不会产生其他不利影响(例如,英特尔x86机器)。
这里值得一提的一个重要细节是,从迂腐的角度来看,对于C程序,术语 platform 是指编译器提供的环境,而不是硬件提供的环境。编译器总是可以自由地实现一个抽象层,它将C程序与底层硬件平台隔离开来,完全(或几乎完全)隐藏任何硬件强加的要求。例如,即使底层硬件平台确实强加了这样的要求,也可以实现一个将从C程序中删除任何对齐要求的实现。然而在实践中,出于对C语言哲学很重要的效率考虑,大多数时候(如果不总是)硬件对齐要求也适用于C程序。
答案 7 :(得分:0)
是。在一个典型但不通用的例子中:
1个字
2短期
4 int
4浮子
8双
CPU的作用是CPU和编译器的业务。在约束的CPU上,编译器会考虑到这一点。在RISC-y芯片上,CPU可能必须加载32位并移位和屏蔽以获得字符。