文章目录
- 概念
- 物理地址内存的分配与释放
- 虚拟用户进程空间内存的分配与释放
- allocator模板类
- new delete
- malloc free
- strcpy 与 memcpy 与 memset
- strcpy
- memcpy
- memset
概念
物理地址内存的分配与释放
主要采用链表结构
使用了一个名叫page的结构体管理物理内存,结构体中包括了页的大小、页的状态以及指向相邻页的指针。
Linux内核使用这些指针来构建了一个逻辑链表,当需要分配内存的时候,会从链表中查找第一个空闲页并把它标记为已使用。
释放内存的时候,会把相应的页标记为空闲,并把它插入到链表对应的位置
虚拟用户进程空间内存的分配与释放
C++语言层次
new delete
智能指针 栈上的对象出作用域自动析构 自动管理内存的分配与释放
C语言层次
malloc free
malloc() 分配的是虚拟内存。
如果分配后的虚拟内存没有被访问的话,虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了。
系统调用
sbrk() brk() mmap()
管理进程的堆(heap)空间。
问:什么场景下 malloc() 会通过 brk() 分配内存?又是什么场景下通过 mmap() 分配内存?
malloc() 源码里默认定义了一个阈值:
如果用户分配的内存小于 128 KB,则通过 brk() 申请内存;
如果用户分配的内存大于 128 KB,则通过 mmap() 申请内存;
allocator模板类
#include <iostream>
#include <memory>int main() {std::allocator<int> allocator;// 在堆上动态的分配大小为5*sizeof(int)的内存int* ptr = allocator.allocate(5);int* ptrnum = new int[5];int abc[5]; // abc也是指针// 构造对象for (int i = 0; i < 5; ++i) {allocator.construct(ptr + i, i);allocator.construct(ptrnum + i, i);allocator.construct(abc + i, i);}// 访问对象for (int i = 0; i < 5; ++i) {std::cout << ptr[i] << " ";std::cout << ptrnum[i] << " ";std::cout << abc[i] << " ";}std::cout << std::endl;// 销毁对象for (int i = 0; i < 5; ++i) {allocator.destroy(ptr + i);allocator.destroy(ptrnum + i); }// 释放内存allocator.deallocate(ptr, 5);delete ptrnum;ptrnum = nullptr;return 0;
}
new delete
堆上分配内存
T* ptr = new T; // 分配单个对象的内存并构造对象
T* arr = new T[N]; // 分配对象数组的内存并构造对象
delete ptr; // 释放单个对象的内存并调用析构函数
delete[] arr; // 释放对象数组的内存并调用每个对象的析构函数
new 运算符在堆上分配的内存可以通过相应的 delete 运算符来释放,从而销毁对象并释放内存。
动态:
为了简化内存管理,C++11 引入了智能指针(如 std::shared_ptr 和 std::unique_ptr),它们提供了更安全和更方便的内存管理机制。智能指针可以自动管理动态分配的内存,避免显式使用 delete,从而减少了内存泄漏和资源管理的错误。
malloc free
堆上分配内存
void* malloc(size_t size);
malloc() 返回一个指向分配内存块的指针,该内存块大小为 size 字节。分配的内存块在堆上连续存储,可以手动管理其使用和释放。
void free(void* ptr);
free输入的是指向内存块的指针
问1:malloc(1) 会分配多大的虚拟内存
malloc() 在分配内存的时候,并不是老老实实按用户预期申请的字节数来分配内存空间大小,而是会预分配更大的空间作为内存池。
具体会预分配多大的空间,跟 malloc 使用的内存管理器有关系,我们就以 malloc 默认的内存管理器(Ptmalloc2)来分析。
#include <stdio.h>
#include <malloc.h>int main() {printf("使用cat /proc/%d/maps查看内存分配\n",getpid());//申请1字节的内存void *addr = malloc(1);printf("此1字节的内存起始地址:%x\n", addr);printf("使用cat /proc/%d/maps查看内存分配\n",getpid());//将程序阻塞,当输入任意字符时才往下执行getchar();//释放内存free(addr);printf("释放了1字节的内存,但heap堆并不会释放\n");getchar();return 0;
}
程序输出:
此1字节的内存起始地址:d73010
之后,使用cat /proc/…/maps查看内存分布情况。我在 maps 文件通过此 1 字节的内存起始地址过滤出了内存地址的范围。
[root@xiaolin ~]# cat /proc/3191/maps | grep d730
00d73000-00d94000 rw-p 00000000 00:00 0 [heap]
可以看到,堆空间的内存地址范围是 00d73000-00d94000,这个范围大小是 132KB,也就说明了 malloc(1) 实际上预分配 132K 字节的内存。
但是程序里打印的内存起始地址是 d73010,而 maps 文件显示堆内存空间的起始地址是 d73000,为什么会多出来 0x10 (16字节)呢?这个问题在问2中。
问2:free() 函数只传入一个内存地址,为什么能知道要释放多大的内存?
malloc 返回给用户态的内存起始地址比进程的堆空间起始地址多了 16 字节,这个多出来的 16 字节就是保存了该内存块的描述信息,比如有该内存块的大小。
这样当执行 free() 函数时,free 会对传入进来的内存地址向左偏移 16 字节,然后从这个 16 字节的分析出当前的内存块的大小,自然就知道要释放多大的内存了。
strcpy 与 memcpy 与 memset
内存数据拷贝函数
strcpy 和 memcpy 是 C 语言中的库函数,用于内存数据的拷贝操作。它们有不同的使用方式:
memset 是 C 语言中的库函数,用于将一块内存区域设置为指定的值。
strcpy是提供了对字符串的复制,memcpy是内存的复制,对复制的内容没有限制,使用范围更广!!!
strcpy和memcpy主要有以下3方面的区别。
复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
strcpy
char* strcpy(char* dest, const char* src);
strcpy 用于将一个以 null 结尾的字符串从源地址 src 复制到目标地址 dest,并返回目标地址的指针。
实例
char source[] = "Hello, World!";
char destination[20];
strcpy(destination, source);
memcpy
void* memcpy(void* dest, const void* src, size_t n);
memcpy 用于将源地址 src 的前 n 字节的数据复制到目标地址 dest,无返回值。
示例:
int source[] = {1, 2, 3, 4, 5};
int destination[5];
memcpy(destination, source, sizeof(source));
需要注意的是,使用这两个函数时,需要确保目标地址 dest 具有足够的空间来容纳要复制的数据。
一个数据报文的构成实例:
char buffer[kBufferSize];memset(buffer, 0, sizeof(buffer));// Headerbuffer[0] = 0x5A;buffer[1] = 0xA5;// CMD_IDconst char* CMD_ID = "00000000000000001";memcpy(buffer+2, CMD_ID, strlen(CMD_ID));// Frame_Typebuffer[19] = 0xD1;// Packet_Typebuffer[20] = 0x01;// Frame_Nobuffer[21] = 0x01;// Sub_Packet_Typebuffer[22] = 0x00;buffer[23] = 0x00;// Time_Stampsrand(time(NULL));int time_stamp = rand() % 1000000;memcpy(buffer+24, &time_stamp, sizeof(int));// Xfloat x = 123.456f;memcpy(buffer+28, &x, sizeof(float));// Yfloat y = 789.012f;memcpy(buffer+32, &y, sizeof(float));// Zfloat z = 345.678f;memcpy(buffer+36, &z, sizeof(float));// Versionint version = 1;memcpy(buffer+40, &version, sizeof(int));// CRC16uint16_t crc = 0;for (int i = 0; i < 42; i++) {crc += (uint8_t)buffer[i];}memcpy(buffer+42, &crc, sizeof(uint16_t));// Endbuffer[44] = 0x96;
memset
memset 是 C 语言中的库函数,用于将一块内存区域设置为指定的值。
void* memset(void* ptr, int value, size_t num);
memset 将指针 ptr 指向的内存区域的前 num 字节都设置为值 value。它返回指向 ptr 的指针。
它可以用来快速地将一块内存区域设置为特定的值,例如将数组全部设置为零或将某个标记数组全部设置为特定的标记值。
示例:
int array[5];
memset(array, 0, sizeof(array)); // 将数组全部设置为零char str[10];
memset(str, 'A', sizeof(str)); // 将 str 数组的每个元素都设置为字符 'A'
for (int i = 0; i < sizeof(str); i++) printf("%c ", str[i]);
需要注意的是,memset 的参数 value 是一个整数,会被解释为无符号字符。因此,如果需要将内存区域设置为非零的特定值,需要确保该值在无符号字符的范围内。
在 C++ 中,也可以使用 std::fill 算法或使用初始化语法来实现相似的功能,以提供更安全和易用的方式来初始化和设置内存。