1. 操作系统内存分配
Linux操作系统内存分配策略采用 伙伴系统与Slab分配器
伙伴系统:按照一定单位大小(4KB),把内存分割成许多连续的内存快,进程每次请求 X块内存(4X KB)时,就会到空闲链表中找到 2*X块 连续的内存块,把其中的一半分配给进程,剩下的一半添加到空闲链表中(这里X是2的指数倍)
Slab分配器:当进程请求内存小于伙伴系统的最小块时,为了减小碎片,会将4KB大小的额内存块通过Slab分配器分割为更小的单位(Byte)
简而言之,伙伴系统用于分配大块内存,而Slab用于分配小块内存
2. 数据格式化
为了便于计算机进行处理,我们会将数据进行格式化,也就是用 int、char、long 等数据类型将数据分类,以及构建复杂数据类型 (struct、class)来表示数据
3. sizeof 运算符
sizeof运算符的作用就是计算一个类型或变量在内存中实际所占用的空间大小(Byte)
typedef struct __Person
{
char name[5];
char sex [5];
}Person;
void func()
{
cout << sizeof(char) << endl; // output = 1
cout << sizeof(int) << endl; // output = 4
cout << sizeof(Person) << endl; // output = 10
}
4. malloc与new 分配内存
extern void *malloc(unsigned int num_bytes);
作用是返回固定字节大小的连续内存空间
比如:
typedef struct __Person
{
char name[5];
char sex [5];
}Person;
void *pVoid = malloc(sizeof(Person));
此时 pVoid 指向的内存大小为 10 个字节,我们可以为这10个字节赋值,然后输出
char *pChar = (char *)pVoid;
char *pStr = "lzb\0\0male\0";
// 赋值
for (int i = 0; i < 10; i++)
{
pChar[i] = pStr[i];
}
// 输出
for (int i = 0; i < 10; i++)
{
cout << pChar[i] << endl;
}
//output:
//lzb male
用 Person对象指针输出
Person *pPerson = (Person *)pVoid;
cout << pPerson->name << endl;
cout << pPerson->sex << endl;
//output:
//lzb
//male
而我们常用的为对象分配内存与赋值的做法是
void func()
{
Person *pPerson = (Person *)malloc(sizeof(Person));
//赋值
strcpy(pPerson->name, "lzb");
strcpy(pPerson->sex , "male);
//输出
cout << pPerson->name << endl;
cout << pPerson->sex << endl;
}
//output:
//lzb
//male
从上面分析可以得出对象与内存之间的对应关系
以Person对象为例,其实际存储结构如下
成员 | 偏移量 |
name | 0 |
sex | 5 |
假设构造了一个Person对象,其内存中的地址为 0x12345678
Peson psn; //假设 &psn = 0x12345678
则当我们访问其各个成员的时候
cout << psn.name << psn.sex << endl;
等价于
cout << (char *)(&psn + 0) << (char *)(&psn + 5) << endl;
cout << (char *)(0x12345678 + 0) << (char *)(0x12345678 + 5) << endl;
所以我们在操作对象的成员的时候,其实是通过 (首地址 + 偏移地址的形式) 操作成员,这也就是用C语言构建复杂数据类型的时候因为成员数据的相互覆盖而到出错的原因
typedef struct __Person
{
char name[5];
char sex [5];
}Person;
void func()
{
Person psn;
strcpy(psn.name, "lzb123");
strcpy(psn.sex, "male");
cout << psn.name << endl;
cout << psn.sex << endl;
}
//output:
//lzb12male
//male
这里 name 的长度为5,而 ”lzb123"的长度为 7,这一步操作接受,10个字节的数据如下
l z b 1 2 3 \0 X X X
对 sex 进行赋值后,10个字节的数据内容如下
l z b 1 2 m a l e \0
所以会出现上面的错误输出
new与malloc基本相同,不同点在于使用 new 的使用会调用对象的构造函数对内存进行初始化
5. 数据对齐
typedef struct __example
{
char ch1;
int tmp;
char ch2;
}example;
sizeof(example) == 12
对齐规则不解释,这里因为内存对齐,导致申请空间的时候,总会有 6Byte 的空间浪费
成员 | 偏移量 |
ch1 | 0 |
tmp | 4 |
ch2 | 8 |
也就是说,ch 实际占有空间为 4 个字节,但是我们通过 example->ch 的访问方式,只能操作 1Byte 的空间大小,所以在构建复杂数据类型的时候,应当对成员顺序合理分布,如下所以
typedef struct __example
{
char ch1, ch2;
int tmp;
}example;
sizeof(example) == 8
此时,就只有 2Byte空间的浪费