各编译器下sizeof()值:
32位编译器: 32位系统下指针占用4字节
char :1个字节char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器:64位系统下指针占用8字节
char :1个字节char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节
内存对齐方式
以16位CPU为例,假设我们同时声明两个变量: char a; short b;用&(取地址符号)观察变量a, b的地址的话,我们会发现, 如果a的地址是0x0000,那么b的地址将会是0x0002或者是0x0004。
那么就出现这样一个问题:0x0001这个地址没有被使用,那它干什么去了? 答案就是它确实没被使用。 因为CPU每次都是从以2字节(16位CPU)或是4字节(32位CPU)的整数倍的内存地址中读进数据的。
如果变量b的地址是0x0001的话,那么CPU就需要先从0x0000中读取一个short,取它的高8位放入b的低8位,然后再从0x0002中读取下一个short,取它的低8位放入b的高8位中,这样的话,为了获得b的值,CPU需要进行了两次读操作。
但是如果b的地址为0x0002,
那么CPU只需一次读操作(只需从0x0000中读取一个short,取它的高8位放入b的低8位就可以了)就可以获得b的值了。
所以编译器为了优化代码,往往会根据变量的大小,将其指定到合适的位置,即称为内存对齐(对变量b做内存对齐,a、b之间的内存被浪费,a并未多占内存
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.
数据对齐的好处:以空间换时间的方式提高CPU速率
对齐规则:(1)结构体中每个成员分别按自己的对齐字节数和PPB(指定的对齐字节数,32位机默认为4)两个字节数最小的那个对齐,这样可以最小化长度。如在32bit的机器上,int的大小为4,因此int存储的位置都是4的整数倍的位置开始存储。
(2) 复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,结构体数组的时候,可以最小化长度。看下面例子。
(3)结构体对齐后的长度必须是成员中最大的对齐参数(PPB)的整数倍,这样在处理数组时可以保证每一项都边界对齐。
(4)结构体作为数据成员的对齐规则:在一个struct中包含另一个struct,内部struct应该以它的最大数据成员大小的整数倍开始存储。如 struct A 中包含 struct B, struct B 中包含数据成员 char, int, double,则 struct B 应该以sizeof(double)=8的整数倍为起始地址。
举例说明
例1
struct test
{
char x1;
short x2;
float x3;
char x4;
};
由于编译器默认情况下会对这个struct作自然边界(有人说“自然对界”我觉得边界更顺口)对齐,结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然边界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大边界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。
#include<iostream>
using namespace std;
//windows 64 位默认 结构体对齐系数为8,32位 结构体对齐系数为4
//测试系统对齐系数
// #pragma pack(8) my_struct_1 为16字节
// #pragma pack(4) my_struct_1 为12字节
// 不加#pragma pack(8) my_struct_1 为16字节
//顾系统默认对齐系数为8
struct my_struct_1
{
char a; //1
double b; //之前补7 +8 8/8==1
};
#pragma pack(4)
struct my_struct_2
{
char a; //1
double b; //3+8
int c; //4 16/4=4
};
#pragma pack()
#pragma pack(2)
struct my_struct_3
{
char a; //1
double b; //1+8
int c; //4 14/2
};
#pragma pack()
#pragma pack(4)
struct my_struct_4
{
char a[5]; //5
double b; //3+8 16/4
};
#pragma pack()
#pragma pack(2)
struct my_struct_5
{
char a[5]; //5
double b; //1+8 14/2
};
#pragma pack()
#pragma pack(4)
struct my_struct_6
{
char a; //1
char b[3]; //3
char c; //1 1+3+1
};
#pragma pack()
#pragma pack(4)
struct my_struct_7
{
char a; //1
char b[3]; //3
char c; //1
int d; //补齐 3 +4
};
#pragma pack()
#pragma pack(4)
struct test
{
char x1; //1
short x2; //补齐1+ 2
float x3; //4
char x4; //1 补齐+3
};
#pragma pack()
异质的数据结构
C语言提供了两种将不同类型的对象组合到一起创建数据类型的机制
1、结构,用关键字struct声明,将多个对象集合到一个单位中
2、联合,用关键字union声明,允许用几种不同的类型来引用一个对象
结构
结构的所有组成部分都存放在内存中一段连续的区域内,而指向结构的指针就是结构第一个字节的地址long x;
char a;
};
联合
用不同的字段引用相同的内存块
union u3{
char c;
int i[2];
double v;
};
对于union u3的指针p,p->c、p->i[0]、p->v引用的都是数据结构的起始位置。
注意:一个联合的总的大小等于它最大字段的大小
- 联合和大小端
- 大端模式:内存地址从低地址开始读,
小端模式:内存地址从高地址开始读。
数组地址:地址从低地址开始。#include<iostream> using namespace std; union X { int32_t a; struct { int16_t b; int16_t c; }; }; int main() { X x; x.a = 0x20160237; cout << &(x.a) << endl; cout << &(x.b) << " " << &(x.c) << endl; system("pause"); return 0; }