C/C++开发,无可避免的内存管理(篇一)-内存那些事

news/2024/4/29 1:50:50/文章来源:https://blog.csdn.net/py8105/article/details/129180213

一、内存管理机制

        任何编程语言在访问和操作内存时都会涉及大量的计算工作。但相对其他语言,c/c++开发者必须自行采取措施确保所访问的内存是有效的,并且与实际物理存储相对应,以确保正在执行的任务不会访问不应该访问的内存位置。C/C++语言及编译器为了灵活性、为了兼容、为了速度,也将内存管理很大一部分工作交给使用者来决策与实现。

       1.1  内存存储

        内存”这个术语“,通常指的是主板上专用芯片提供的数据存储。这些芯片提供的存储通常称为随机存取存储器(RAM,Random Access Memory)、主存储器(main memory)和基础存储器(primary storage)。它是电子设备可以有效运行的核心。这些芯片提供的存储是易失性的,也就是说,当电源关闭时,芯片中的数据会丢失。

        各种类型的RAM:

        1)DRAM(Dynamic RAM,动态RAM),每秒充电数千次(刷新)

        2)SDRAM(Synchronous DRAM,同步动态DRAM),以最有效时钟运行速度刷新;

        3)SRAM(Static RAM,静态RAM),不需要向DRAM那样刷新,更快也更昂贵;

        4)VRAM(Video,视频RAM),视频硬件使用的内存;

        5)DDR SDRAM(Double Data Rate SDRAM,双倍数据速率SDRAM),在系统时钟周期的上升和下降时刷新,基本上是正常可用带宽的两倍;

        6)RDRAM(Rambus DRAM,Rambus公司的DRAM)高性能、芯片对芯片接口技术的新一代存储产品。

        7)ESDRAM(Enhanced SDRAM,增强型SDRAM),更便宜的SDRAM代替SRAM的型号。

        RAM芯片中是由一个个基本单元集成,每个单元的基本结构是晶体管和电容器的特定配置组成。每个单元都是一个数字开关,可以打开或关闭(即1或0),在逻辑实现上由单个二进制数字(即1或0)-位来标识。这些单元被分组为8位(8bits,目前的事实标准)单元调用字节。字节是测量存储设备提供的内存量的基本单位。

        内存计算单位如下:

1 byte          = 8 bits
1 word          = 2 bytes
1 double word   = 4 bytes
1 quad word     = 8 bytes
1 octal word    = 8 bytes
1 paragraph     = 16 bytes
1 kilobyte (KB) = 1,024 bytes
1 megabyte (MB) = 1,024KB = 1,048,576 bytes
1 gigabyte (GB) = 1,024MB = 1,073,741,824 bytes
1 terabyte (TB) = 1,024GB = 1,099,511,627,776 bytes
1 petabyte (PB) = 1,024TB = 1,125,899,906,842,624 bytes

        另外需要特别指出的是,CPU本身也有自己内存-寄存器以及L1、L2、L3等缓存,寄存器其实就是位于处理器(CPU)内部的小存储空间,是一块一块小的存储空间组成。寄存器是处理器最喜欢的工作区。处理器的大部分日常工作都是对寄存器中的数据进行的。将数据从一个寄存器移动到另一个寄存器是移动数据的最方便的方式。寄存器就像是CPU与RAM交互的中转站,并且它离 CPU 很近,其存取速度要比内存快得多。关键字register 修饰符定义的数据就是用CPU寄存器存放在的。

        高速缓存L*提供了比DRAM更快访问的临时存储。通过将程序的计算密集型部分放置在缓存中,处理器可以避免必须连续访问DRAM的开销。节省的资金可能是巨大的。有不同类型的缓存。一级缓存是位于处理器本身上的存储空间。二级缓存通常是处理器外部的SRAM芯片,有些计算设备还提供三级缓存空间。

        另外,磁盘空间可以用于创建虚拟内存。虚拟内存是通过使用磁盘空间模拟的内存。通常存储在DRAM中的部分内存被写入磁盘,因此处理器可以访问的内存量大于实际的物理内存量。例如,如果您有100MB的DRAM,并且使用200MB的磁盘空间来模拟内存,则处理器可以访问300MB的虚拟内存。

        使用虚拟内存是一种迫不得已的选择,需要软件程序对数据存储作出优化,即那些数据适合使用虚拟内存。因为磁盘虽然带来的很多额外内存,但在性能方面你会付出巨大的代价。磁盘I/O涉及一系列强制操作,其中一些是机械操作。PS:一个基本的事实就是,如果您将所有可能的东西都缓存在内存中,只有在绝对没有其他选择的情况下才移动到磁盘存储,这样呈现出来的处理效率是最高的。

        1.2 内存地址

        DRAM中的每个字节都分配了一个称为地址的唯一数字标识符,地址是一个整数值,就像街道上的房子门牌号一样。内存中的第一个字节分配了一个零地址。地址零附近的内存区域被称为内存底部或低内存。最后一个字节附近的内存区域称为高内存。处理器能够寻址的物理(即DRAM)字节数称为处理器的物理地址空间。

        处理器的物理地址空间指定可寻址的潜在字节数,而不是实际存在的物理字节数。处理器的物理地址空间由其拥有的地址线的数量决定。地址线是将处理器连接到DRAM芯片的一组电线。每个地址行指定给定字节地址中的单个位。例如,有32条地址线。这意味着每个字节都分配了一个32位地址,因此其地址空间由2^32个可寻址字节(4GB)组成。

        为了访问和更新物理内存,处理器使用控制总线和数据总线。总线是将处理器连接到硬件子系统的相关导线的集合。

        控制总线用于指示处理器是要从存储器读取还是要向存储器写入。数据总线用于在处理器和存储器之间来回传送数据。即:

        当处理器读取内存时,通过这些步骤:1)处理器将要读取的字节的地址放在地址线上;2)处理器在控制总线上发送读取信号;3)DRAM芯片返回数据总线上指定的字节。
        当处理器写入内存时,通过这些步骤:1)处理器将要写入的字节的地址放置在地址线上;2)处理器在控制总线上发送写信号;3)处理器通过数据总线发送要写入存储器的字节。

        上述内容只是简单阐述了处理器如何将字节读取和写入内存,但更高级的做法是,处理器还支持两种高级内存管理机制:分段和分页。

        分段是通过将计算机的地址空间划分为特定区域(称为分段)来实现的。在没有分段时,内存的换入换出都是以整个进程内存空间为单位,非常的耗时并且对内存的利用率也不高。内存分段下,程序是由若干个逻辑分段组成的,如可由代码段、数据段、栈段、堆段组成,不同的段是有不同的属性的,使用分段是一种隔离内存区域的方法,分段提供了所谓的内存保护,这样程序就不会相互干扰。内存分段后,采用虚拟地址访问内存空间,虚拟地址到物理地址的映射采用段表来实现,虚拟地址由段地址(段选择器索引)和段偏移量组成来确定物理地址。

        段地址表示特定的存储器段,并且总是存储在16位段寄存器之一中。具体地,段地址指定存储器段的基地址(最低地址)。每个段寄存器都有特定用途:

CS, Segment address of code currently being executed
SS, Segment address of stack
DS, Data segment address
ES, Extra segment address (usually data)
FS, Extra segment address (usually data)
GS, Extra segment address (usually data)

        偏移地址可以存储在通用寄存器中,大小为多少位表示。假定偏移地址为16位,这将每个段的大小限制为64KB(2^16bit)。

        分页是重新组织物理或虚拟内存实现虚拟管理内存的一种方法。分页会把整个虚拟和物理内存空间切成一段段固定尺寸的大小,这样一个连续并且尺寸固定的内存空间,叫页(Page)。如果启用了分页,处理器能够寻址的总字节数称为其虚拟地址空间。这个虚拟地址空间中的字节地址不再与处理器放置在地址总线上的地址相同。这意味着必须建立转换数据结构和代码,以便将虚拟地址空间中的字节映射到物理字节(无论该字节是在DRAM中还是在磁盘上),虚拟地址与物理地址之间通过页表来映射,内存管理单元 (MMU)就可以依据页表做将虚拟内存地址转换成物理地址的工作。

        当引用内存位置时,在内存地址解析到其物理位置的同时,处理器执行一系列检查。因为这些检查是在地址解析周期同时执行的,所以不会影响性能。如果处理器的各种检查中有一项发现保护违规,处理器将生成异常。异常是处理器产生的信号。使用处理器中断处理功能来捕获并处理异常。通常,处理器将使用特殊的数据结构,如中断描述符表(IDT),将异常切换到操作系统,然后由操作系统决定要做什么。操作系统实际上负责在处理器启动时代表处理器建立和设置IDT等。这允许操作系统自由地向IDT注册特殊处理程序,以便在发生内存冲突时可以调用适当的例程。

#include<stdio.h>int main(int argc, char* argv[])
{int array[4]={0};int i;printf("sizeof(array)=%d\n",sizeof(array)/sizeof(int));for(i=0;i<10;i++){array[i]=i;printf("set array[%d]=%d\n",i,array[i]);}getchar();return 0;
}

         编译上述代码,g++ main.cpp -o test.exe,运行测试,如下:

         是否觉得很奇怪呢,命名数组越界了呀,怎么还能继续执行呢,再看看系统日志是怎样的,操作系统是明确知道栈堆存储故障的,但是程序使用者在这个程序使用上,他认为程序是正确的,使用者是不会去查看源码或系统日志而知道有内存错误的,所有c/c++开发,内存使用不当带来很多潜在危害而不自知。

         还好,由于程序本身的内存空间是独立的,不会影响到操作系统和其他程序,操作系统只是把它作为异常处理了并报告了异常事件。但是如果我们不行指定了内存地址做了非法越界操作呢,下面代码或许无意中覆盖位于内存底部的中断向量表,但是绝对是引起系统崩溃的灾难。PS:这段代码别拿去测试。这段代码别拿去测试。这段代码别拿去测试。

int main(int argc, char* argv[])
{unsigned char *ptr;ptr = (unsigned char *)0x0;    //这将是一个灾难for(int i=0;i<64;i++){ptr[i]=0x0;}return 0;
}

        如果程序在段或页面中引用的字节不在内存中或非法修改了内容,则会生成段或页面错误。当这种情况发生时,处理器将产生段或页面错误,并使用异常处理数据结构将其移交给本地操作系统。操作系统一旦接收到故障信号,操作系统进行异常报告及相关处理,如果是危害系统的事务,就可能造成系统崩溃重启等现象。

        1.3 系统内存管理

        高级内存管理功能由四个系统寄存器(GDTR-Global Descriptor Table Registers、LDTR-Local Descriptor Table Registers、IDTR-Interrupt Descriptor Table Registers 、TR-Task Registers )和五个模式控制寄存器(CR0、CR1、CR2、CR3、CR4)实现。

  •  GDTR,48位寄存器,全局描述符表寄存器,用于存放全局描述符表GDT的32位的线性基地址和16位的表限长值。
  • LDTR,16位寄存器,局部描述符表寄存器,保存16位的选择符。16位的选择符从GDT中选择一个LDT描述符,送入描述符高速缓冲寄存器(64位)中,以确定当前局部描述符LDT所在的位置的基地址和界限值。
  • IDTR,48位寄存器,中断描述符表寄存器,用于存放中断描述符表IDT的32位线性基地址和16位表长度值。
  • TR,16位寄存器,保存任务状态段TSS的16位段选择符。
  • CR0用于控制处理器模式和处理器状态。
  • CR1是保留的。
  • CR2寄存器用于存储导致页面错误的线性地址。
  • CR3在物理地址的解析中起着核心作用,保存页目录的基地址。
  • CR4寄存器用于启用一些高级机制。

        通用计算机(如PC)通电时,BIOS(烧录到ROM中)会立即启动并寻找可引导设备。BIOS将磁盘的引导扇区加载到地址0000[0]:7C00(即,物理地址0x07C00)的DRAM中。一旦BIOS加载了引导扇区,它就会将机器的控制权移交给现在位于0000[0]:7C00的机器指令。然后按指令集引导完完成后面的自检、中断使能、参数配置等一系列设备启动操作。

        而程序加载到内存,是由系统内存管理单元在内存里面,找到一段连续的内存空间,然后分配给装载的程序,然后把这段连续的内存空间地址,和整个程序指令里指定的内存地址做一个映射。这是一个虚拟内存到物理内存的映射表,这样实际程序指令执行的时候,会通过虚拟内存地址,找到对应的物理内存地址,然后执行,这就是前面所示的分段。当系统内存管理单元发现没有一段连续内存空间分配给程序时,就启用Swap内存交换模块去重新置换出满足程序运行需要的内存。另外内存是昂贵的,因此程序执行时,一般不会按程序实际需要内存大小全加载,而是先加载一小块或者几小块,等真用到了再去真实的物理地址取一块,这样一块一块的就是分页。而分页是把整个物理内存空间切成一段段固定尺寸的大小,也很利于内存交换。

        通过虚拟内存(非磁盘虚拟化内存,而是无论内存虚拟化管理)、内存交换和内存分段分页的技术组合,便能让程序运行加载不需要考虑实际的物理内存地址、大小和当前分配空间,而把这些都交给了系统内存管理来处理。

        1.4 应用程序内存

        用户应用程序通常将其地址空间分段设计,划分为四部分,实际上,在Linux内核里,对分段管理用得极少,而DS、SS的指向基本上也从来没有做过区分:

  • code section,代码部分,存放当前正在运行的程序代码所在段的段基址,标识当前使用的指令代码可从该段寄存器指定的存储器段中取得,相应的偏移量由IP提供。
  • data section,数据部分,当前程序使用的数据所存放段的最低地址,即存放数据段的段基址。
  • stack/heap section,堆栈部分
  • Extra section,附加部分

        作为c/c++开发者的角度,我们主要关注内存管理的三个部分:静态区,栈,堆。

  •  静态区:保存自动全局变量和 static 变量(包括 static 全局和局部变量)。静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配。
  •  栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限。
  •  堆:由 malloc 系列函数或 new 操作符分配的内存。其生命周期由 free 或 delete 决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。

        其中,容易混淆的是栈(stack)和堆(heap)的区分,stack空间有限,其空间由操作系统自动分配/释放,heap有较大内存区,其空间通过malloc 系列函数(c)或 new系列操作符(c++)手动分配/释放的。下面这些代码,来区分一下那些是堆,那些是栈或区分那些是全局变量、那些是局部变量。

static int j;	//静态区
int k = 0;		//静态区
void fun(void)
{static int i = 0;	//静态区i++;j++;k=i;
};int *p1i = NULL;	//堆,c
int *p2i = new int; //堆,c++,int *p2i = new int();
int main(int argc, char* argv[])
{int n;	//栈for(n=0;n<10;n++){fun();}std::cout << "j = " << j <<"\n";std::cout << "k = " << k <<"\n";std::cout << "n = " << n <<"\n";char *pc = new char('a');	//堆,c++int *p3i = (int*)malloc(2*sizeof(int));	//堆,cint *p4i = new int[2];		//堆,c++p1i = &n;*p3i = 11;*(p3i+1) = 12;memcpy(p4i,p3i,2*sizeof(int));if(NULL!=pc)std::cout << "*pc = " << *pc <<"\n";if(NULL!=p1i)std::cout << "*p1i = " << *p1i << "\n" ;if(NULL!=p2i)std::cout << "*p2i = " << *p2i << "\n" ;p2i = (p3i+1);if(NULL!=p2i)std::cout << "*p2i =" << *p2i << "\n" ;if(NULL!=p3i)std::cout << "(*p3i) = " << (*p3i) << "\n" ;if(NULL!=(p3i+1))std::cout << "(*p3i+1) = " << (*p3i+1) << "\n";if(NULL!=p4i)std::cout << "(*p4i) = " << (*p4i) << "\n" ;if(NULL!=(p4i+1))std::cout << "(*p4i+1) = " << (*p4i+1) << "\n";if(NULL!=pc){delete pc;pc = NULL;}if(NULL!=p3i){//free(p3i);delete[] p3i;p3i = NULL;}if(NULL!=p2i){//free(p2i);delete p2i;    //全局指针,在某些系统下,调用该语句会出现无效指针告警p2i = NULL;}if(NULL!=p1i){//free(p1i);delete p1i;    //全局指针,在某些系统下,调用该语句会出现无效指针告警p1i = NULL;}delete[] p4i;return 0;
}
//out log
j = 10
k = 10
n = 10
*pc = a
*p1i = 10
*p2i = 15117832    //这个随机
*p2i =12
(*p3i) = 11
(*p3i+1) = 12
(*p4i) = 11
(*p4i+1) = 12

        1.5 堆内存问题

        内存释放建议new和delete 、new[]和delete[] 、malloc和free配套使用。但像上面这段代码中,基本类型的定义编译器并不严格执行:

    int *p1i = NULL;	        //堆,c//free(p1i);                //OKdelete pi; pi = NULL;       //OK,建议

        更令人疑惑的是,malloc分配0大小,居然指针不为null,也能给指针赋值,编译运行后,也无异常输出,系统日志也无事件告警。

////n = 10;int *p5i = (int*)malloc(0);	//堆,cif(NULL!=p5i)    //非null{p5i = &n;    //OK//*p5i = n;    //也OKstd::cout << "*p5i = " << *p5i << "\n" ;}free(p5i);p5i = NULL;
// out log
*p5i = 10

        另外,对于全局指针变量,由于p2i是全局指针,在某些系统,尤其是linux下,能编译,但编译器编译出来的程序,在运行到该位置时,在释放时会报告invalid pointer无效,出现异常,这主要是操作系统内存管理机制的严格与否,毕竟遵循谁new,谁delete 的原则,不能局部内干掉全局变量。虽然程序结束时会自动释放(整个程序内存区域清除,全局指针同样在内),但建议最好不要创建全局指针变量,再重复一遍,最好不要创建全局指针变量

int *p1i = NULL;	//堆,c
int *p2i = new int; //堆,c++,int *p2i = new int();
int main(int argc, char* argv[])
{
...if(NULL!=p2i){//free(p2i);    //无效指针告警//delete p2i;   //无效指针告警p2i = NULL;}if(NULL!=p1i){//free(p1i);    //无效指针告警//delete p1i;   //无效指针告警p1i = NULL;}
...
}

        一般来说,大家在对于使用delete释放时,都会习惯将指针赋值为null,但是对于free释放时,就很多人没有这个习惯了,那么free释放是否会产生野指针呢,是否还需要null赋值呢。

////n = 10int *p5i = (int*)malloc(0);	//堆,cif(NULL!=p5i){//p5i = &n;*p5i = 11;std::cout << "*p5i = " << *p5i << "\n" ;}free(p5i);//p5i = NULL;    //注释该项if(NULL!=p5i)    //居然也OK{*p5i = 11;std::cout << "*p5i = " << *p5i << "\n" ;}free(p5i);    //还能继续释放,神奇吧p5i = NULL;   //
//out log
*p5i = 11
*p5i = 11

        看看,还能继续给该指针赋值,那是因为free仅是斩断指针变量与指向内存的关系。至于指针变量 p 本身保存的地址并没有改变,还是存在的,而非想象的null值。因此再次给起赋值是可以的,并又重新建立了指针变量与指向内存的关系,当然也可再次调用free释放,编译器不会报错。既然使用 free 函数之后指针变量 p 本身保存的地址并没有改变,那我们还是养成跟着重新把p 的值变为 NULL吧。

        那么,如果已经重新设置为NULL的指针再次调用free呢,又会怎样:

////n=10int *p5i = (int*)malloc(0);	//堆,cif(NULL!=p5i){//p5i = &n;*p5i = 11;std::cout << "*p5i = " << *p5i << "\n" ;}free(p5i);//p5i = NULL;if(NULL!=p5i){*p5i = 11;std::cout << "*p5i = " << *p5i << "\n" ;}free(p5i);p5i = NULL;free(p5i);    //来个叛逆的测试getchar();

        嗯,编译时没问题的,但是程序运行时,本人在win10电脑下g++ 编译的程序卡死了,只能强制关闭运行该程序的命令窗口,如下图。所有还是遵循malloc和free建议的使用习惯,malloc和free内存分配及释放一一对应,并记得free后随即将指针赋值为NULL。

         1.6 指针作为形参引起内存问题探讨

        指针变量可以是全局变量,也可以是局部变量,尤其指针作为函数内的局部变量时,不少教程都告诉我们,函数结束会自动释放掉,使用它作为返回值往往不是我们期望的结果了,但真的就是这样吗。看下面这段代码曾经视为经典教程的代码:

char * GetMemory(char * p, int num)
{p = (char *)malloc(num*sizeof(char));return p;
}int main(int argc, char* argv[])
{//char *str = NULL;str = GetMemory(str,10);if(NULL!=str){strcpy(str,"hello");printf("*str:%s\n",str);free(str);str = NULL;}//getchar();return 0;
}//out log
*str:hello

        程序输出居然是“*str:hello”,神奇吧,str 居然不为空,还成功分配了空间,能给该字符指针赋值成功。再看看它的变种:

void GetMemory(char ** p, int num)
{*p = (char *)malloc(num*sizeof(char));
};//
int main(int argc, char* argv[])
{char *str_ = NULL;GetMemory(&str_,10);if(NULL!=str_){strcpy(str_,"hello");printf("*str:%s\n",str_);free(str_);str_ = NULL;}//getchar();return 0;
}//out log
*str:hello

        嘿,同样也成功了。不甘心啊,再次变更一下看看:

char * GetMemory(int num)
{char *p = (char *)malloc(num*sizeof(char));return p;
};int main(int argc, char* argv[])
{//char *str_c = NULL;str_c = GetMemory(10);if(NULL!=str_c){strcpy(str_c,"hello");printf("*str:%s\n",str_c);free(str_c);str_c = NULL;}//getchar();return 0;
}
//out log
*str:hello

        直接在函数内部定义指针返回时也能分配到空间,三种方法都能得到同样效果,和想象的很不一样呀。

         难道是编译器的问题,切换到linux环境(centos7)下尝试,同样能得到正确结果:

         显然,采用指针做局部变量:如果申请了空间(用new等,赋值不算)又没有delete之前,那么这个空间在你程序运行结束之前不会释放,只要你知道这个空间的地址,就可以访问。但赋值不算,比如,你先定义一个数组,然后把数组名赋值指针。

        1.7 数值作为形参引起的内存问题

        既然说到数组了,我们再看看数组作为形参时,数组为局部变量转换为指针后,又会怎样。

char * GetMemory_G(char p[], int num)
{p = (char *)malloc(num*sizeof(char));return p;
};void GetMemory_G(char * p[], int num)
{*p = (char *)malloc(num*sizeof(char));
};
//
int main(int argc, char* argv[])
{//char *str_g1 = NULL;str_g1 = GetMemory_G(str_g1,10);if(NULL!=str_g1){strcpy(str_g1,"hello");printf("*str:%s\n",str_g1);free(str_g1);str_g1 = NULL;}////char *str_g2 = NULL;GetMemory(&str_g2,10);if(NULL!=str_g2){strcpy(str_g2,"hello");printf("*str:%s\n",str_g2);free(str_g2);str_g2 = NULL;}//getchar();return 0;
}
//out log,正确输出哦,无论是linux还是win
*str:hello
*str:hello

        再看数组在函数内定义,返回数组地址又会怎样:

char * GetMemory_G(int num)
{char p[num] = "hello";return p;
};//编译输出
//WIN
D:\workForMy\workspace\mem_mgr>g++ main.cpp -o test.exe
main.cpp: In function 'char* GetMemory_G(int)':
main.cpp:50:7: warning: address of local variable 'p' returned [-Wreturn-local-addr]char p[num];
//linux
[pengyong@pyfree mem_mgr]$ g++ main.cpp -o test
main.cpp: 在函数‘char* GetMemory_G(int)’中:
main.cpp:50:7: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]char p[num];

        显然,如果数组不是作为形参传入,在内部定义返回其指向地址会有局部变量返回异常告警,但是作为形参时已经转化为指针形式,那就和指针的性质是一样的,只要数组的地址没有被作出改变,只要知道这个数组空间的地址,就可以访问。

        c/c++编程中,内存管理这块绝对重灾区,很多常见的内存错误很多编程老手那么都经历过,还是会不知觉地又中招了。这些常见的内存错误介绍,待续。

三、本文测试代码参考

        main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <memory>
#include <cstring>
#include <iostream>static int j;	//静态区
int k = 0;		//静态区
void fun(void)
{static int i = 0;	//静态区i++;j++;k=i;
};int *p1i = NULL;	//堆,c
int *p2i = new int; //堆,c++,int *p2i = new int();
//
char * GetMemory(char * p, int num)
{p = (char *)malloc(num*sizeof(char));return p;
};void GetMemory(char ** p, int num)
{*p = (char *)malloc(num*sizeof(char));
};char * GetMemory(int num)
{char *p = (char *)malloc(num*sizeof(char));return p;
};char * GetMemory_G(char p[], int num)
{p = (char *)malloc(num*sizeof(char));return p;
};void GetMemory_G(char * p[], int num)
{*p = (char *)malloc(num*sizeof(char));
};char * GetMemory_G(int num)
{char p[num];return p;
};int main(int argc, char* argv[])
{/*int array[5]={0};int i;printf("sizeof(array)=%d\n",sizeof(array)/sizeof(int));for(i=0;i<5;i++){array[i]=i;printf("set array[%d]=%d\n",i,array[i]);}*/int n;	//栈for(n=0;n<10;n++){fun();}std::cout << "j = " << j <<"\n";std::cout << "k = " << k <<"\n";std::cout << "n = " << n <<"\n";char *pc = new char('a');	//堆,c++int *p3i = (int*)malloc(2*sizeof(int));	//堆,cint *p4i = new int[2];		//堆,c++p1i = &n;*p3i = 11;*(p3i+1) = 12;memcpy(p4i,p3i,2*sizeof(int));if(NULL!=pc)std::cout << "*pc = " << *pc <<"\n";if(NULL!=p1i)std::cout << "*p1i = " << *p1i << "\n" ;if(NULL!=p2i)std::cout << "*p2i = " << *p2i << "\n" ;p2i = (p3i+1);if(NULL!=p2i)std::cout << "*p2i =" << *p2i << "\n" ;if(NULL!=p3i)std::cout << "(*p3i) = " << (*p3i) << "\n" ;if(NULL!=(p3i+1))std::cout << "(*p3i+1) = " << (*p3i+1) << "\n";if(NULL!=p4i)std::cout << "(*p4i) = " << (*p4i) << "\n" ;if(NULL!=(p4i+1))std::cout << "(*p4i+1) = " << (*p4i+1) << "\n";if(NULL!=pc){std::cout << "_1_\n";delete pc;pc = NULL;}if(NULL!=p3i){std::cout << "_2_\n";//free(p3i);delete[] p3i;p3i = NULL;}if(NULL!=p2i){//free(p2i);//delete p2i;	//某些系统下运行会异常,无效指针p2i = NULL;		//某些系统下运行会异常,无效指针}if(NULL!=p1i){//free(p1i);	//某些系统下运行会异常,无效指针//delete p1i;	//某些系统下运行会异常,无效指针p1i = NULL;}delete[] p4i;///*int *p5i = (int*)malloc(0);	//堆,cif(NULL!=p5i){//p5i = &n;*p5i = 11;std::cout << "*p5i = " << *p5i << "\n" ;}free(p5i);//p5i = NULL;if(NULL!=p5i){*p5i = 11;std::cout << "*p5i = " << *p5i << "\n" ;}free(p5i);p5i = NULL;//free(p5i);    //来个叛逆的测试*///char *str = NULL;str = GetMemory(str,10);if(NULL!=str){strcpy(str,"hello");printf("*str:%s\n",str);free(str);str = NULL;}//char *str_ = NULL;GetMemory(&str_,10);if(NULL!=str_){strcpy(str_,"hello");printf("*str:%s\n",str_);free(str_);str_ = NULL;}//char *str_c = NULL;str_c = GetMemory(10);if(NULL!=str_c){strcpy(str_c,"hello");printf("*str:%s\n",str_c);free(str_c);str_c = NULL;}////char *str_g1 = NULL;str_g1 = GetMemory_G(str_g1,10);if(NULL!=str_g1){strcpy(str_g1,"hello");printf("*str:%s\n",str_g1);free(str_g1);str_g1 = NULL;}////char *str_g2 = NULL;GetMemory(&str_g2,10);if(NULL!=str_g2){strcpy(str_g2,"hello");printf("*str:%s\n",str_g2);free(str_g2);str_g2 = NULL;}//getchar();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73462.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mongoDB的安装与使用

MongoDB安装MongoDB官方网站&#xff1a;https://www.mongodb.com/try/download/community-kubernetes-operator2软件安装权限不足&#xff1a;https://www.javaclub.cn/database/56541.htmlstep1:打开安装包直接点击Nextstep2&#xff1a;继续点击Nextstep3&#xff1a;点击自…

DMotion - 基于DOTS的动画框架和状态机

【博物纳新】专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目&#xff0c;帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果&#xff0c;并探索将其应用到自己项目的可行性。很多时候&#xff0c;我们并不知道自己想要什么&#xff0c;直到某…

day51【代码随想录】动态规划之回文子串、最长回文子序列

文章目录前言一、回文子串&#xff08;力扣647&#xff09;二、最长回文子序列&#xff08;力扣516&#xff09;前言 1、回文子串 2、最长回文子序列 一、回文子串&#xff08;力扣647&#xff09; 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目…

数据库防护做不好,分分钟要被勒索比特币,每个接触数据库的都必须知道

公司有个公网数据库被黑了&#xff0c;对方留言勒索0.006比特币&#xff0c;按目前比特币的价值&#xff0c;大概1009元人民币左右&#xff0c;虽然不多&#xff0c;但发生这个事情着实让人丢脸&#xff0c;说明平时对防护还做不到位&#xff01; 还好公司平时有做数据库防范措…

骨传导耳机靠谱吗,骨传导耳机的原理是什么

很多人刚开始接触骨传导耳机时都会具有一个疑问&#xff0c;骨传导耳机是不是真的靠谱&#xff0c;是不是真的不伤害听力&#xff1f;骨传导耳机传输声音的原理是什么&#xff1f; 下面就给大家讲解一下骨传导耳机传输声音的原理以及骨传导耳机对听力到底有没有伤害。 骨传导…

DeepLabV3+:对预测处理的详解

相信大家对于这一部分才是最感兴趣的&#xff0c;能够实实在在的看到效果。这里我们就只需要两个.py文件&#xff08;deeplab.py、predict_img.py&#xff09;。 创建DeeplabV3类 deeplab.py的作用是为了创建一个DeeplabV3类&#xff0c;提供一个检测图片的方法&#xff0c;而…

如何通过jar包得知maven坐标,以及如何替换依赖的依赖的版本

问题一&#xff1a;我只能得到这个jar包的名字&#xff0c;如果得知这个jar包的maven坐标&#xff08;groupId以及artifactId&#xff09;&#xff1f; 思路1&#xff1a;将jar包的名字&#xff08;去除版本号&#xff09;在mvn仓库中搜索&#xff0c;地址&#xff1a;https:/…

Linux期末考试应急

Linux期末考试应急 虚拟机添加硬盘、分区、格式化、挂载、卸载 fdisk -l#查看系统现有分区fdisk <指定磁盘>#指定磁盘分区sudo mkfs.ext3 <指定分区>#格式化磁盘###挂载磁盘1.新建一个目录sudo mkdir /mnt/test2.将指定分区挂载到对应目录sudo mount /dev/sdb10 /…

PHPExcel 表格设置

4.5.3。通过行和列设置单元格值 通过设置坐标单元格值可以使用工作表的setCellValueByColumnAndRow方法来实现。 //设置单元格B8 $objPHPExcel->getActiveSheet()->setCellValueByColumnAndRow(1, 8, ‘Some value’); 4.5.4。由列和行中检索的小区 检索的小区的值&#…

什么蓝牙耳机打游戏好?打游戏好用的无线蓝牙耳机

午休或是周末约上好友玩两局游戏&#xff0c;是忙里偷闲的快乐时刻&#xff0c;对于普通游戏玩家&#xff0c;其实耳机够用就行&#xff0c;下面就分享几款打游戏好用的蓝牙耳机。 一、南卡小音舱蓝牙耳机 蓝牙版本&#xff1a;5.3 推荐系数&#xff1a;五颗星 南卡小音舱li…

酷开系统AI人工智能技术,为营销抢夺更多目标消费者

随着越来越多的年轻群体回归家庭&#xff0c;互联网电视产业正在时代的浪潮下快速发展&#xff0c;如今已经有数以万计的家庭消费者倾向于在客厅场景中使用大屏电视观看更多丰富的电视节目&#xff0c;而这一趋势&#xff0c;对于急需线上互动营销渠道的企业和品牌方来说&#…

乘上算力发展的东风,联想这次能否变革突起?

“逆水行舟&#xff0c;不进则退”笔者认为这句话也同样适用到现在的联想集团身上&#xff0c;近3年受到疫情的影响全球电子领域普遍不突出&#xff0c;智能手机出货量上涨乏力&#xff0c;个人电脑&#xff08;PC&#xff09;的销量也波动频繁&#xff0c;联想集团在这种不乐观…

追梦之旅【数据结构篇】——详解C语言实现链栈

详解C语言实现链栈~&#x1f60e;前言&#x1f64c;整体实现内容分析&#x1f49e;1.头文件编码实现&#x1f64c;2.功能文件编码实现&#x1f64c;3.测试函数功能代码&#x1f64c;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右…

茂名市 2021 年高中信息技术学科素养展评

没事干&#xff0c;发一下去年去比赛的题目。 目录 第一题 30分 第二题 30分 第一题 30分 题目&#xff1a; “姐姐&#xff0c;乘除法运算太难了&#xff0c;有什么办法能熟练掌握吗&#xff1f;”今年 读小学四年级的表弟向李红求救。为了提高表弟的运算能力&#xff0c;…

Linux 服务器CPU超高如何快速定位

前言 在生产环境中有时会遇见服务器CPU超高的问题&#xff0c;特别是重大版本发布后如果有内存泄露很容出现CPU超高&#xff0c;严重可能会达到100%。现在我们使用的服务器都是多核CPU&#xff0c;当出现CPU告警我们需要及时发现问题代码并处置&#xff0c;不然严重情况下会导致…

HashMap~

HashMap&#xff1a; HashMap是面试中经常被问到的一个内容&#xff0c;以下两个经常被问到的问题&#xff0c; Question1&#xff1a;底层数据结构&#xff0c;1.7和1.8有何不同&#xff1f; 答&#xff1a;1.7数组&#xff0b;链表&#xff0c;1.8数组&#xff0b;(链表|红…

【Redis中bigkey你了解吗?bigkey的危害?】

一.Redis中bigkey你了解吗&#xff1f;bigkey的危害&#xff1f; 如果面试官问到了这个问题&#xff0c;不必惊慌&#xff0c;接下来我们从什么是bigkey&#xff1f;bigkey划分的类型&#xff1f;bigkey危害之处&#xff1f; 二.什么是bigkey&#xff1f;会有什么影响&#xff…

苹果设计可变色Apple Watch表带,智能穿戴玩法多

苹果最新技术专利显示&#xff0c;苹果正在为 Apple Watch 设计一款可变色的表带&#xff0c;可以根据佩戴者所穿着的服装、所在的环境等自动改变颜色。据介绍&#xff0c;这款表带里的灯丝具有电致变色功能&#xff0c;可以通过施加不同的电压&#xff0c;来实现显示多种颜色或…

jvm常识

Jvm工作原理学习笔记0126一、JVM的生命周期1.JVM实例对应了一个独立运行的java程序它是进程级别a)启动。启动一个Java程序时&#xff0c;一个JVM实例就产生了&#xff0c;任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点b)运行。ma…

web中git漏洞的形成的原理及使用

目录 1.Git漏洞的成因 1.不正确的权限设置&#xff1a; 2.代码注入漏洞&#xff1a; 3.未经身份验证的访问&#xff1a; 4.非安全传输&#xff1a; 5.跨站脚本攻击&#xff08;XSS&#xff09;&#xff1a; 2.git泄露环境的搭建 git init&#xff1a; git add&#xff1…