计算数据类型长度问题
在c/c++学习中,我们不可避免的会接触到数据存储问题,而计算机中用sizeof函数来计算数据存储需要的长度。
(1)基本数据类型
在32位编译器下
Sizeof(char)结果为1 表示存储一个char类型变量需要1个字节大小的位置
Sizeof( short ) 结果为2
Sizeof(unsigned int ) 结果为4
Sizeof( int ) 结果为4
Sizeof( float ) 结果为4
Sizeof( long) 结果为4
Sizeof( unsigned long) 结果为4
Sizeof( char */int */等指针变量) 结果为4
Sizeof( double ) 结果为8
Sizeof( long) 结果为4
Sizeof(string)结果为32
需要注意的是sizeof(void)在有点编译器下结果为1,有的则编译不通过。
(2)单独函数所需要的存储长度
此时注意的是函数返回类型,无论函数里面包含了什么内容,sizeof(函数())返回大小为函数返回类型所需的大小。注意返回类型为void的情况。
int fun() {} sizeof(fun())=4
(3)数组大小计算
数组的sizeof值等于数组所占用的内存字节数,如:
char a1[]= "abc";
inta2[3];
sizeof(a1 ); // 结果为4,字符末尾还存在一个NULL终止符
sizeof(a2 ); // 结果为3*4=12(依赖于int)
一些朋友刚开始时把sizeof当作了求数组元素的个数,现在,你应该知道这是不对的。那么应该怎么求数组元素的个数呢?
Easy,通常有下面两种写法:
int c1 =sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度
int c2 =sizeof( a1 ) / sizeof( a1[0]); // 总长度/第一个元素的长度
写到这里,提一问,下面的c3,c4值应该是多少呢?
*********************************************************
voidfoo3(char a3[3])
{
int c3 = sizeof( a3 ); // c3 ==
}
voidfoo4(char a4[])
{
int c4 = sizeof( a4 ); // c4 ==
}
*********************************************************
也许当你试图回答c4的值时已经意识到c3答错了,是的,c3!=3。
这里函数参数a3已不再是数组类型,而是蜕变成指针。相当于char* a3,为什么仔细想想就不难明白。
我们调用函数foo1时,程序会在栈上分配一个大小为3的数组吗?不会!
数组是“传址”的,调用者只需将实参的地址传递过去,所以a3自然为指针类型(char*),c3的值也就为4。
(4)结构体
结构体的问题涉及到内存对齐的问题,我们需要好好了解一下:
Struct s
{
char a;
int b;
double c;
}
Sizeof(s)此时的大小为16,而不是1+4+8=13,这就是传说中的内存对齐问题。计算机组成原理教导我们,这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上。以此类推,这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
但是内存对齐到底是怎么一回事我们接下来慢慢了解:
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
结构体大小的计算方法和步骤:
i. 将结构体内所有数据成员的长度值相加,记为 sum_a ;
ii. 将各数据成员为了内存对齐,按各自对齐模数而填充的字节数累加到sum_a上,记为sum_b。
对齐模数是 #pragma pack 指定的数值与该数据成员自身长度相比较得到的数值较小者。该数据相对起始位置应该是对齐模数的整数倍。
iii. 将和 sum_b 向结构体模数对齐。
该模数则是 #pragma pack 指定的数值与结构体内最大的基本数据类型成员长度相比较得到的数值较小者。结构体的长度应该是该模数的整数倍。
计算结构体所需内存大小需要分两种情况:
1) 在没有#pragmapack宏的情况下:
例子1:
内存分配状态为:
对于结构体的第一个成员 a,起始位置为0x…38 (也为 4 的倍数),所占内存为 0x…38 ~ 0x…3b,共占4个字节;
对于结构体的第二个成员 b,自身长度为1,对齐模数也为1,所以内存分配可以紧接着a的结尾位置 0x…3b,所以起始位置为 0x…3c,共占1个字节;
对于结构体的第三个成员 c,自身长度为2,对齐模数也为2,所以起始位置距离a 的起始位置应该是2的倍数,所以 0x…3d处只距离5,不符合要求,所以空着,继续往下找,而在 0x…3e处满足要求,所以可以作为c的起始位置,共占2个字节;
此时3个成员及其中间空着的位置所占内存单元总和为8,而结构体内最大的基本数据成员是 a,其长度为4,所以结构体模数为 4,而8是4的倍数,满足要求,故不再加内存。
例子2:
与例子1相比,三个类型的声明顺序变了:
内存分配状态为:
要注意的是,对 a而言,对齐模数为 4,所以当 b的起始位置在0x7f…830之后,0x7f…831、0x7f…832、0x7f…833的位置距离起始位置0x7f…830分别是1,2,3,都不是 4 的倍数,所以那三个位置都空着,直到0x7f…834才满足要求,所以作为 a 的起始位置。当最后一个成员 c 占的内存末尾在0x7f…839时,所有数据成员及其之间的空位所占内存单元总和为10,而结构体模数为4,10不是4的倍数,所以要扩大到12才满足要求,此时又多了2个空位置,就是0x7f…83a和0x7f…83b。
例子3:
当结构体中有数组时:
内存分配状态为:
亦即相同类型数据的数组之间多分配的空间会被相邻数组的元素所占用。
2) 在没有#pragmapack宏的情况下:
方法类似,只是模数可能会按上面说的规则而有所变化。
内存分配状态为:
注意,当没有#pragma pack(2)时,成员a要确定自身的q起始位置,是以自身的长度4为对齐模数,但有了#pragma pack(2),则将括号里的2与a的长度4比较,2为较小者,所以以2为a的对齐模数,即地址从0x7f…839往下找到0x7f…83a时,已经距离结构体的起始位置0x7f…838为2,是2的倍数,满足要求(虽然不是4的倍数),可以作为a的起始位置。而最后,所有数据成员及其之间的空位所占内存单元总和为8,因为2和4(结构体中最大的数据成员长