1. 数据的宽度与单位
计算机内部任何数据都被表示成二进制编码形式。二进制数据的每一位(0 or 1)二进制信息的最小单位,称为一个"比特"(bit
),简称"位",bit
是计算机中存储,运算和传输信息的最小单位。
每个西文字符需要用8个比特表示,而每个汉字需要用16个比特才能表示。计算机内部,二进制信息的计量单位是"字节"(Byte
),也成为"位组"。 1 Byte = 8 bit
计算机中运行和处理二进制信息时使用的单位除了比特和字节之外,还经常使用"字"(word
)作为单位,必须注意,不同的计算机,字的长度和组成不完全相同,有的由2个字节组成,有的由4个,8个,甚至16个字节组成。
在考察计算机性能时,一个重要的指标就是机器的"字长"。平时所说的"某种机器是32位机或是64位机",其中的32,64就是指的字长。所谓机器字长通常指CPU内部用于整数运算的数据通路的宽度,也就是说,"字长"等于CPU内部用于整数运算的运算器位数和通用寄存器宽度。
注 : "字"和"字长"的概念不同
|- "字"用来表示被处理信息的单位,用来度量各种数据类型的宽度,大小以机器为准
|-"字长"表示数据运算,存储和传送的部件的宽度,它反映了计算机处理信息的一种能力。
单位换算
1 B = 8 b
K :1KB = 2^10 B = 1024 字节
M :1MB = 2^20 B
G : 1GB = 2^30 B
T : 1TB = 2^40 B
C语言中数值数据类型的宽度(字节(Byte)为单位)
类型 | 典型的32位机器(Linux) | 64位机器 |
---|---|---|
char | 1 | 1 |
short int | 2 | 2 |
int | 4 | 4 |
long int | 4 | 8 |
--- | --- | --- |
char * | 4 | 8 |
--- | --- | --- |
float | 4 | 4 |
double | 8 | 8 |
另外,对于相同类型的数据,并不是所有机器都采用相同的数据宽度,分配的字节数随处理器和编译器的不同而不同
例如:指针类型一般认为32位机器是 4 个字节,64位机器是 8 个字节,这个没错,但是在64位的机器上编译程序,计算指针的大小,返回的是 4,这时认为是不是以前书上讲的是错的,其实不是,只不过编译选项里的平台是Win32,也就是在64位系统运行的是32位的程序,所以说是受编译器的影响
2. 数据的储存和排列顺序
现代计算机基本上都采用字节编址方式,即对存储空间的存储单元编号时,每个地址编号中存放一个字节。
- 例如:在一个按字节编址的计算机中,假定
int
型变量i
的地址为0800H
,i
的机器数为01 23 45 67H
,根据不同的地址排序方式,i
的4个字节01H,23H,45H,67H
有不同的排列顺序
两种排列方式:
大端模式(big endian) : 将数据的最高有效字节存放在低地址单元,最低有效字节存放在高地址单元
小端模式(small endian) : 将数据的最高有效字节存放在高地址单元,最低有效字节存放在低地址单元
字节编址地址 | …… | 0800H | 0801H | 0802H | 0803H | …… |
---|---|---|---|---|---|---|
大端 | …… | 01H | 23H | 45H | 67H | …… |
小端 | …… | 67H | 45H | 23H | 01H | …… |
补充: 网络序是大端模式
Linux网络编程中,为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
3. 数据对齐原则
3.1 为什么要数据对齐?
可以将存储器看成由连续的位(cell)构成,每8位为一个字节,每个字节有一个地址编号,称之为按字节编址,假定计算机系统中访问机制限制每次访存最多只能只能读写64位,即8个字节,那么,第0-7字节可以同时读写,第8-15字节可以同时读写,以此类推,这种称为8字节宽的存储机制;因此,如果一条指令要访问存储器的数据不在 8i ~ 8i + 7 (i = 0,1,2 ...)之间的存储单元内,那么就需要多次访存,因此延长了指令的执行时间,引入数据对齐的最重要的原因就是为了避免多次访存带来的指令执行效率的降低!!!
对齐方式下程序的执行效率更高,为此,操作系统通常按照对齐方式分配管理内存,编译器也按照对齐方式转换代码。
3.2 基本数据类型的对齐策略
-
最简单的对齐策略:要求不同的基本类型按照其数据长度进行对齐;
例如,int类型数据的长度为4个字节,因此规定int型数据的地址是4的倍数,char的长度为1个字节,时刻是对齐的,其他的同理;这种策略下,对于8字节宽的存储机制来说,所有的基本类型都仅需访存一次;windows采用的就是这种策略 -
Linux的对齐策略 : 更加宽松,规定short数据的地址是2的倍数,其他的如int ,float,double和指针等类型的数据的地址都是4的倍数;
这种情况,对于8字节宽的存储机制,double型数据就可能需要访存两次
3.3 结构体 & 结构数组的对齐策略
满足下面三个条件
- (1). 结构体变量的首地址能够被其最宽基本类型成员的大小所整除
[备注]:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为对齐的模数,也就是我们经常说的4字节对齐(32位机器默认),8字节对齐(64位机器默认)。 - (2). 结构体中的每个数据成员都按照"基本数据类型对齐策略"的要求进行对齐
也就是说:结构体每个成员相对于结构体首地址的偏移量(offset)都是成员自身大小的整数倍,如有需要,编译器会在成员之间加上填充字节(internal adding) - (3). 结构体的最终大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)
也就是说:若结构体的总大小是最大基础成员大小的整数倍,那么也就一定是其他任一基础成员大小的整数倍,那么每个结构体的起始地址就一定是其任一基础成员大小的整数倍 ,这样的话就保证了所有基础类型数据、非基础类型数据全部对齐。
[补充] (1)(2) 是为了保证内存对齐,提高处理器访问内存的效率;(3) 是为了保证结构体数组中的每个元素都能满足对齐要求
举个例子:
EX1
结构体
struct SD
{
int i;
short si;
char c;
double d;
};
按照字节对齐原则,结构体SD所占的存储空间大小为 16
个字节
假设结构体的首地址为0
EX2
结构体数组
struct SDT
{
int i;
short si;
double d;
char c;
};
struct SDT sa[10];
按照字节对齐原则,结构体SD所占的存储空间大小为 24
个字节
假设结构体数组的第一个数组元素sa[0]
的首地址为0
补充概念 : 首地址 n 字节对齐原则
VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数,否则必须为n的倍数。下面举例说明其用法。
#pragma
pack(push) //保存对齐状态
#pragma
pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma
pack(pop)//恢复对齐状态
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的>大小为24。
网友评论