文章

c++(7`数据大小、内存大小和计算)

数据类型的大小

1743508607402

长度的单位为字节,右边是可表示的十进制数字范围

1743579107423

1743660760999

不同位系统,long和指针的字节数可能不同

内存五大分区

1745026926690

  1. 内核空间:存放操作系统内核代码、内核数据(如进程调度、内存管理……),是不可直接读写的
  2. 堆区:存储由程序员动态分配(new/malloc/智能指针……)的对象,存放对象的生命周期是动态的
    1. 由于new/delete并不会遵守堆的先进先出的特性,会造成内存碎片,使程序效率降低(寻址)
    2. 空间大:对于32位系统,可达2^32,但是仍要保持清理内存,否则访问越界会造成程序崩溃
    3. 堆的机制是很复杂的,它比栈的效率要低
  3. 栈区:存储局部对象,它们的生命周期是自动的
    1. 先进后出的结构,不会造成内存碎片
    2. 容量小:大约只有2MB
    3. 执行效率很高
  4. 静态区/数据段:存储全局变量,静态变量,它们的生命周期是静态的
  5. 常量区/代码段:存储程序指令、函数编译后的可执行的二进制代码、只读常量,它们的生命周期是也是静态的,但它们是只读的不能修改

注意: 指针变量本身是存放在栈区的,而指针所指向的对象可以位于栈区、堆区或者全局/静态存储区,它是根据对象本身决定的

size_t

size_t 是一种平台相关的无符号整数类型,足够容纳任何对象大小的无符号类型,在32位系统上,它通常是4字节的unsigned int;而在64位系统上,它通常是8字节的 unsigned long

32 位和 64 位指的是CPU(处理器)的字长,字长是CPU在一次计算中能处理的二进制数据的最大位数,它决定了数据处理速度、寻址范围(通常地址总线位数和字长一致)

1
2
3
4
5
6
7
size_t size = sizeof(double); //标准库兼容性
size_t length = vec.size();//标准库兼容性

int arr[5000000000];//对象数量
for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { 
}
const size_t huge_size = sizeof(……); //内存大小

展开

size_t足够容纳任何对象大小的无符号类型:

假设是32位CPU,size_t表示的范围是0——4294967295

按字编址,地址总线的位数是32,即寻址范围共2^32字节 == 4,294,967,296 字节 == 4G

也就是说一个对象最大的sizeof值不会超过4,294,967,296,size_t恰好可以存储

作用:

  • 标准库兼容性:C++标准库中的许多函数和操作都会使用size_t作为返回类型,
  • 用于循环计数,避免因使用 int而可能导致的溢出问题,比如 int arr[5000000000],这个在64位是可以存储下的,数组的个数是5000000000,因此int不支持,而size_t此时相当于 long,确保代码的可移植性和安全性
  • 需要注意的是,它是unsigned的,因此不要和signed混用,会产生错误的结果,并且具有逻辑上的非负性,所以通常用来表示内存大小/对象数量,而非需要运算的情况

sizeof和strlen

1
2
3
int *arr = (int *)malloc(n * sizeof(int)); 
size_t personSize = sizeof(struct Person); 
size_t arrLength = sizeof(arr) / sizeof(arr[0]); 

展开

sizeof()是运算符:

  • 返回一个对象/类型所占的内存大小,单位是字节,数可以是数组、指针、类型、对象、函数等,
  • 其值在编译时 就已经计算好了,因此参数不可以是动态分配的对象
  • 通常用于为静态类型分配足够的空间,免除了复杂的计算
  • 帮我们计算复杂类型的大小
  • 计算数组的元素个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct T{  
    char a;  //偏移量0,
    short b; //偏移量 == 上一个偏移量 0 + 上一个成员的大小 1,调整为2
    char c;  //偏移量 == 2 + 2
	//结构体大小 == 最后一个偏移量 4 + 最后一个成员的大小 1,调整为6
}; 
struct T  
{  
      short a;  //偏移量0
      struct   
      {  
           char b;  //偏移量 == 0 + 2,调整为4
           int c;  //偏移量 == 4 + 1,调整为8
      } tmp;   
      int d;  //偏移量 8 + 4,
	  // 结构体大小 == 12 + 4 
};
struct T  
{  
    float f;  //偏移量0
    char p;  //偏移量 == 0 + 4
    int arr[3];//展开为3个  
	//偏移量 == 4 + 1,调整为8
	//偏移量 == 8 + 4
	//偏移量 == 12 + 4
	//结构体大小 = 16 + 4

};

展开

sizeof计算结构体大小:

并不会简单的把每个成员的大小相加,因为它会考虑内存对齐问题,以便以空间换取寻址速度

因此它在计算时会遵守几个规则:

  • 偏移量必须为遍历到的所有成员大小的整数倍
  • 结构体大小必须是所有成员大小的整数倍(最小公倍数)
1
2
char str[] = "Hello, World!";
size_t length = strlen(str); 

展开

strlen(…)是函数,

  • 要在运行时 才能计算。参数必须是字符型指针(char*)
  • 一般用于返回字符串的实际长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Test1()
{
	char p[] = "hello";//5,6
	cout << "p: " << p << "   " << strlen(p) << "   " << sizeof(p) << endl;
	char p1[] = "hello\0";//5,7
	cout << "p1: " << p1 << "   " << strlen(p1) << "   " << sizeof(p1) << endl;
	char p2[] = "hello\\0";//7,8,第一个\是转义\0的,所以它不算字符,它会让后面的\0变为普通的字符
	cout << "p2: " << p2 << "   " << strlen(p2) << "   " << sizeof(p2) << endl;
	char p3[] = "hello\\\0";//6,8,第一个\只转义了第二个\,使得第二个\不能再转义后面
	cout << "p3: " << p3 << "   " << strlen(p3) << "   " << sizeof(p3) << endl;
	char p4[] = "hel\0lo";//3,7
	cout << "p4: " << p4 << "   " << strlen(p4) << "   " << sizeof(p4) << endl;
	char p5[] = "hel\\0lo";//7,8
	cout << "p5: " << p5 << "   " << strlen(p5) << "   " << sizeof(p5) << endl;
}

展开

注意:strlen 计算的是字符串的实际长度,遇到\0(空字符)即停止;sizeof 计算整个字符串所占内存字节数的大小,当然\0也要+1计算

memcpy

按字节内存拷贝(memory copy),无需通过for循环拷贝了

void * memcpy ( void * destination, const void * source, size_t num );

  • 目标数组(复制到这里)
  • 源数组(从这里复制数据)
  • 字节数
1
2
3
4
5
6
int arr1[10] = { 1,2,3,4,5,6,7 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 7 * 4);//arr2 : 1,2,3,4,5,6,7
int arr1[10] = { 1,2,3,4,5,6,7 };//1,2,3,4,5,6,7,0,0,0
int arr2[10] = { 0 };
memcpy(arr2, arr1 + 3, 7 * 4);//arr2 : 4,5,6,7,0,0,0,会跳过前3个元素,复制后面的7个元素

展开

如果src不足num个,它会导致读取未初始的内存,如果des不足num个,会导致缓冲区溢出

malloc、free、calloc、realloc

1
2
void* malloc(size_t size);//参数是字节数量,返回无类型的指针
int* p = (int*)malloc(40);//分配40个字节空间,强制转换为int,相当于int arr[10];

展开

malloc(memory allocation内存分配):C 风格内存分配,如果开辟成功,则返回内存指针,如果失败,返回nullptr指针,

1
2
3
void free (void* ptr);//ptr必须要是动态分配的
free(p);//避免内存泄漏
p = NULL;//避免空悬指针

展开

需要用free释放内存

1
void* calloc(size_t num, size_t size);

展开

calloc(contiguous allocation动态内存分配并清零):为 num个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0

1
void* realloc(void* ptr, size_t size);//

展开

realloc: 对动态开辟内存大小的调整,ptr指向被调整的旧内存起始地址,size调整后的新大小,返回调整后的新内存起始地址

本文由作者按照 CC BY 4.0 进行授权
本页总访问量