C++有关内存的那些事

个人主页:PingdiGuo_guo

收录转栏:C++干货专栏

前言

本篇博客是讲解关于C++内存的一些知识点的。

文章目录

前言

1.内存函数

1.1memcpy函数

1.2memmove函数

[1.3 memset函数](#1.3 memset函数)

2.各数据类型占用

2.1bool类型

2.2char类型

2.3short、int、long类型及整数

2.4float类型及double类型及浮点数

3.学习内存有什么用

总结


1.内存函数

内存函数是在计算机程序中用来操作内存的一类函数。内存函数可以用于分配和释放内存,读取和写入内存中的数据,以及进行内存的复制和移动等操作。

在这里,我们主要介绍几种可以复制的内存函数。

1.1memcpy函数

memcpy函数是C++中的一个标准库函数,用来实现内存拷贝操作。它的原型如下:

void *memcpy(void *dest, const void *src, size_t n);

在C++中,也可以使用内存拷贝操作来复制数组元素。C++提供了memcpy函数,它与C的memcpy函数功能相同,但被包含在std命名空间中。

下面是使用memcpy实现数组元素拷贝的示例代码:

#include <iostream>
#include <cstring>

using namespace std;

int main() {
    int srcArray[] = {1, 2, 3, 4, 5};
    int destArray[5];

    // 使用memcpy函数拷贝数组元素
    memcpy(destArray, srcArray, sizeof(srcArray));// 思考:sizeof(srcArray)是什么?这个函数拷贝拷贝了多少元素?
    memcpy(destArray, srcArray,20);//思考:这里可以拷贝多少元素?
    //思考:这里是谁拷贝的谁?

    // 打印目标数组的元素
    for (int i = 0; i < sizeof(destArray) / sizeof(destArray[0]); i++) {
        cout << destArray[i] << " ";
    }
    
    return 0;
}

1.sizeof(srcArray)是srcArray数组的字节大小,即sizeof(int) * 5,所以sizeof(srcArray)是20。

2.这个函数拷贝了5个元素。因为memcpy函数根据参数指定的字节数进行拷贝,sizeof(srcArray)指定了srcArray数组的字节大小,所以拷贝了整个srcArray数组的元素。

3.第二个memcpy函数使用了20作为拷贝的字节数。因为是5每个int占4个字节,所以这个函数可以拷贝5个元素。

4.这里是destArray拷贝了srcArray数组。
memcpy函数在处理内存重叠问题时是未定义行为。也就是说,如果源内存块和目标内存块重叠,memcpy函数可能会导致不可预测的结果。

输出结果:

1.2memmove函数

memmove与memcpy类似,但不同的地方是memmove是可以处理内存块的重叠的。它的函数原型为:

void *memmove(void *dest, const void *src, size_t count);

拷贝数组:

#include <bits/stdc++.h>
using namespace std;
int main()
{
 int arr1[] = { 1,2,3,4,5,6,7,8,9,10};
 memmove(arr1+2, arr1, 20);
 for (int i = 0; i < 10; i++)
 {
     cout<<arr1[i]<<' ';
 }
 return 0;
}

输出结果:

这里出现了一个问题,那就是内存重叠了。

那么,我们如何处理这个内存重叠问题呢?

我们可以检查它们的内存是否重叠。

如果是,那我们就直接从后往前拷贝,否则,我们就从前往后拷贝。

图示:

src:srcmemory source 的缩写,表示源地址,即需要被复制的内存块的起始位置。

dest:destdestination 的缩写,表示目标地址,即复制后的内存块的起始位置。

代码:

// 自定义 memmove 函数,解决内存重叠问题
void* me(void* d, const void* sr, size_t n) {
    void* ret = d;

    if (d <= sr || (char*)d >= ((char*)sr + n)) {
        // 从前往后
        while (n--) {
            *(char*)d = *(char*)sr;
            d = (char*)d + 1;
            sr = (char*)sr + 1;
        }
    } else {
        // 从后往前
        d = (char*)d + n - 1;
        sr = (char*)sr + n - 1;
        while (n--) {
            *(char*)d = *(char*)sr;
            d = (char*)d - 1;
            sr = (char*)sr - 1;
        }
    }
    
    return ret;
}

1.3 memset函数

memset函数式将指定大小的内存块设置为给定的值。它的函数原型为:

void * memset ( void * ptr, int value, size_t num);

使用:

#include <iostream>
#include <cstring>
using namespace std;
int main() {
    char str[10];
    
    // 将str的前5个字节设置为字符 'A'
    memset(str, 'A', 5);
    
    cout << str << endl;  // 输出 "AAAAA"
    
    return 0;
}

运行结果:

2.各数据类型占用

我们可以用sizeof(数据类型)格式来计算个数据类型的占用内存大小。这里我们要了解一个知识点:

字节是计算机中的最小存储单位,通常用来表示一个字母、一个数字或者一个符号。一个字节等于8个二进制位,即8个0或1。字节是计算机中信息存储和传输的基本单位,用来表示各种数据类型和文件大小。
二进制位是计算机中的最小计数单位,用于表示数字的最基本形式。二进制位只能是0或1两种状态,用于表示八进制、十进制、十六进制等不同进制数系统的数值。计算机中的所有数据都是以二进制位的形式存储和处理的,二进制位的组合可以表示各种不同的数值和字符。8个二进制位组合在一起形成一个字节,即8位二进制位表示一个字节的数据。

2.1bool类型

代码:

cout<<sizeof(bool)<<endl;

占用一字节。

解释:

bool类型占用内存是一个字节。虽然大家可能觉得bool类型的取值范围只有true和false两种,占用内存应该很小,但是为了在内存中存储和处理bool类型的值,需要用一个字节来表示。这是因为计算机在内存中最小的存储单元就是一个字节,无法将一个布尔值存储在更小的存储单元中。因此,无论bool类型的值占用的实际位数是多少,它始终会占用1个字节的内存空间。

2.2char类型

代码:

cout<<sizeof(char)<<endl;

占用一个字节,及八位二进制位,图表:

|-----|-----|-----|-----|-----|-----|-----|-----|
| 1/0 | 1/0 | 1/0 | 1/0 | 1/0 | 1/0 | 1/0 | 1/0 |

其中,每一个二进制位的变化都可以表示一个不同的值,也就是2^8=256个值,只是当有符号和无符号时表示的范围并不相同,我们平时的所用的每一个字符在内存中都由8位2进制位来表示。

比如,字符'A'在ASCLL码中对应65,在内存则表示为:

|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |

2.3short、int、long类型及整数

short、int、long类型都是储存整数的,所以放到一块讲了。

short类型:

cout<<sizeof(short)<<endl;

占用二字节,也就是十六位二进制位,可以表示2^16=65536个值。

int类型:

cout<<sizeof(int)<<endl;

占用四字节,三十二位二进制位,可以表示2^32=4294967296个值。

long类型:

cout<<sizeof(long)<<endl;

也是占用四字节,表示三十二位二进制位,可以表示2^32=4294967296个值,和int类型一样。

整数:

整数的存储都是由原码、反码、补码来表示的:

对于整数:

  1. 原码:只要通过正负数判断即可获得原码,(正数1,负数0)

  2. 反码:在原码的基础上,对负数的各位取反

  3. 补码:在反码的基础上,对负数的最低有效位加1。即将反码(符号位除外)加1得到补码。

注:正整数的原、反、补码是相同的。

在计算机内存中,整数通常用二进制补码的形式来储存。为什么呢?

因为计算机中通过补码运算可以实现加法、减法、乘法和除法等操作。在进行运算时,计算机会自动进行补码的转换和处理。

2.4float类型及double类型及浮点数

因为float和double都是储存浮点数的,所以归为一类了。

float类型

代码:

cout<<sizeof(float)<<endl;

占用四字节,三十二位二进制位。

这里需要了解一个表示方式,就是二进制的科学表示法:

± mantissa × 2 exponent
(mantissa:尾数,exponent:指数,均使用二进制表示)

它的储存采用了IEEE 754单精度浮点格式,存储方式如下:

|------------|-------------------|-----------------------------------------------|
| 1 bit(符号位) | 8 bit(指数位) | 23 bit(尾数位) |
| 0 | 0 1 1 1 1 1 0 0 0 | 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
| 30 | 31 23 | 22 0 |

这里的bit也是二进制位,是二进制位的缩写。如上图所示,该格式最高一位是符号位,0位正,1位负,后面8位为无符号整型数,表示范围为0~265,后面23位小数为,索引从22到0分别对应2^-1到2^-23。

double类型

代码:

cout<<sizeof(double)<<endl;

double型在内存中有八个字节,储存的数据较大,六十四位二进制位(bit)。他和float类型一样,都是采用的二进制的科学计数法。

它的储存采用了IEEE 754双精度浮点格式,储存方式如下:

|------------|-------------|-------------|
| 1 bit(符号位) | 11 bit(指数位) | 52 bit(尾数位) |
| 第一位为63 | 倒数第一位为52 | 倒数第一位为0 |

如上图所示,该格式最高位也为符号位,0位正,1位负,后面11位为无符号整型数,表示范围为0~2^11-1,后面52位小数为,索引从51到0分别对应2^-1到2^-52。

浮点数

浮点数的储存方式可看上面的两张图表,接下来讲一下浮点数如何转化为二进制。

步骤:

  1. 将浮点数分为整数部分和小数部分。例如,考虑浮点数12.375,整数部分为12,小数部分为0.375。

  2. 将整数部分转化为二进制。对于整数部分,可以使用短除法将其转化为二进制。例如,12转化为二进制是1100。对于短除法,这里就不过多讲述了,大家可以去查一查。

  3. 将小数部分转化为二进制。对于小数部分,可以使用乘2取整法将其转化为二进制。将小数部分乘以2,取整数部分,然后将小数部分的整数部分再乘以2,依此类推,直到小数部分为0或达到所需的精度。例如,0.375转化为二进制的过程如下:

    0.375 x 2 = 0.75 -> 0//整数部分为0,继续乘2
    0.75 x 2 = 1.5 -> 1//整数部分为1,舍弃整数部分,继续乘2
    0.5 x 2 = 1.0 -> 1//乘得积为1,停止运算
    

所以,0.375转化为二进制是0.011。
4. 合并整数部分和小数部分的二进制。将步骤2和步骤3得到的二进制合并在一起,注意小数点的位置。对于上述例子,合并后的二进制是1100.011。
5. 二进制小数转化为十进制验算。比如,二进制小数1100.011=1*2**^2+0*2^2+1*2^2**+(0 * 2^-1) + (1 * 2^-2) + (1 * 2^-3) =12.375,正确。

3.学习内存有什么用

大家可能会有一些疑问,学内存知识有什么用呢?学内存有以下几个方面的作用:

  1. 内存管理:C++是一种低级语言,需要手动管理内存分配和释放。了解C++内存知识可以帮助我们正确地分配和释放内存,避免内存泄漏和野指针等问题,提高程序的健壮性和效率。
  2. 优化性能:理解C++内存模型和内存使用方式可以帮助我们优化程序性能。例如,了解内存对齐和缓存行的概念可以避免访问内存的延迟,提高程序的运行速度。
  3. 安全性和稳定性:内存相关的错误往往是导致程序崩溃和漏洞的主要原因之一。学习C++内存知识可以帮助我们避免常见的内存错误,提高程序的安全性和稳定性。
  4. 调试和错误排查:当程序出现内存相关的问题时,了解C++内存知识可以帮助我们更快地定位和修复问题,提高调试的效率。

总的来说,学习C++内存知识对于使用和开发C++程序非常重要,可以帮助我们进行内存管理、优化性能、提高安全性和稳定性,深入理解语言特性,以及进行调试和错误排查。

总结

本篇博客到这里就结束了,感谢大家的支持与观看,有好的建议欢迎留言,如果这篇博客对您有帮助,那请给PingdiGuo_guo一个免费的赞,谢谢大家啦!

相关推荐
老猿讲编程16 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye1 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*1 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue1 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man1 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
霁月风2 小时前
设计模式——适配器模式
c++·适配器模式
萧鼎3 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
学地理的小胖砸3 小时前
【一些关于Python的信息和帮助】
开发语言·python
疯一样的码农3 小时前
Python 继承、多态、封装、抽象
开发语言·python