作者:茶香未散尽_385_312 | 来源:互联网 | 2023-10-12 19:45
c++中由于数组导致的内存溢出处理
之前实现图论算法的时候碰到过超大的整型数组,用c++去实现着实会碰到一些莫名其妙的内存溢出问题。
例如:我们想定义一个 2000*2000 的 2d 数组,甚至是这样大小的 2d 的 vector,如果是直接定义成局部变量,那么将会直接报内存溢出错(大家可以计算一下这样大小的数组会占据多少内存空间)。
那么为什么定义成局部变量会导致内存溢出呢?笔者认为是因为程序中函数的调用或者说是运行其实是在栈中执行的,而栈的空间是很有限的,所以说定义过大的局部变量必然导致栈溢出(Stack overflow)错误。
因此对于较大的局部变量我们需要将其定义为全局的,尤其在函数的递归调用中。亲测 2d vector 当大小超过100,就已经能够导致溢出错了(vector 中的自增长方法会开辟更大的空间)。
众所周知,数组是一种顺序存储的数据结构,在定义数组时,首先要确定数组的大小。前面提到的静态数组在编译时就需要确定数组的大小,所以,为了防止内存溢出,我们尽量将数组定义的大一些,但是这样太过浪费内存。甚至当超过一定大小之后,编译会报错,例如直接定义一个全局的:
int a[3000][3000];
这样程序可能连编译都无法通过。
这样大型的数组需要通过定义动态数组。动态数组不需要在编译时就确定大小,它的大小在程序运行过程中确定,所以可以根据程序需要而灵活的分配数组的大小,相比静态数组,它更“灵活”、“自由”。但是动态数组需要进行显式的内存释放。
int **Ctable;
void DynamicCreate2Array()
{int m &#61; 3000, n &#61; 3000;int i;cout << "############## call the CTable create func #############" << endl;Ctable &#61; new int*[m]; for (int i &#61; 0; i < m; i&#43;&#43;)Ctable[i] &#61; new int[n]; int val &#61; 0;for (i &#61; 0; i < m; i&#43;&#43;)for (int j &#61; 0; j < n; j&#43;&#43;){Ctable[i][j] &#61; 0;val &#43;&#43;; } cout << "items: " << val << endl;
}
int main(){DynamicCreate2Array(); int count &#61; 3000;for(int i &#61; 0; i < count; i&#43;&#43;)for(int j &#61; 0; j < count; j&#43;&#43;)Ctable[i][j] &#61; 0;for(int i &#61; 0; i < count; i&#43;&#43;)for(int j &#61; 0; j < count; j&#43;&#43;)printf("%d\t", Ctable[i][j]);for (int i &#61; 0; i < 3000; i&#43;&#43;)delete[] Ctable[i];delete[] Ctable;}
Java 中对大数组的处理
Java 程序在运行时都要开辟空间&#xff0c;任何软件在运行时都要在内存中开辟空间&#xff0c;Java虚拟机运行时也是要开辟空间的。JVM运行时在内存中开辟一片内存区域&#xff0c;启动时在自己的内存区域中进行更细致的划分&#xff0c;因为虚拟机中每一片内存处理的方式都不同&#xff0c;所以要单独进行管理。
JVM内存的划分有五片&#xff1a;
-
寄存器
-
本地方法区
-
方法区
-
栈内存
-
堆内存
我们重点来说一下堆和栈&#xff1a;
栈内存
栈内存首先是一片内存区域&#xff0c;存储的都是局部变量&#xff0c;凡是定义在方法中的都是局部变量&#xff08;方法外的是全局变量&#xff09;&#xff0c;for循环内部定义的也是局部变量&#xff0c;是先加载函数才能进行局部变量的定义&#xff0c;所以方法先进栈&#xff0c;然后再定义变量&#xff0c;变量有自己的作用域&#xff0c;一旦离开作用域&#xff0c;变量就会被释放。栈内存的更新速度很快&#xff0c;因为局部变量的生命周期都很短。
堆内存
存储的是数组和对象&#xff08;其实数组就是对象&#xff09;&#xff0c;凡是new建立的都是在堆中&#xff0c;堆中存放的都是实体&#xff08;对象&#xff09;&#xff0c;实体用于封装数据&#xff0c;而且是封装多个&#xff08;实体的多个属性&#xff09;&#xff0c;如果一个数据消失&#xff0c;这个实体也没有消失&#xff0c;还可以用&#xff0c;所以堆是不会随时释放的&#xff0c;但是栈不一样&#xff0c;栈里存放的都是单个变量&#xff0c;变量被释放了&#xff0c;那就没有了。堆里的实体虽然不会被释放&#xff0c;但是会被当成垃圾&#xff0c;Java有垃圾回收机制不定时的收取。
下面我们通过一个图例详细讲一下堆和栈&#xff1a;
比如主函数里的语句
int [] arr &#61; new int [3];
在内存中是怎么被定义的&#xff1a;
主函数先进栈&#xff0c;在栈中定义一个变量arr,接下来为arr赋值&#xff0c;但是右边不是一个具体值&#xff0c;是一个实体。实体创建在堆里&#xff0c;在堆里首先通过new关键字开辟一个空间&#xff0c;内存在存储数据的时候都是通过地址来体现的&#xff0c;地址是一块连续的二进制&#xff0c;然后给这个实体分配一个内存地址。数组都是有一个索引&#xff0c;数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化&#xff08;这是堆内存的特点&#xff0c;未初始化的数据是不能用的&#xff0c;但在堆里是可以用的&#xff0c;因为初始化过了&#xff0c;但是在栈里没有&#xff09;&#xff0c;不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体&#xff1a;
那么堆和栈是怎么联系起来的呢?
我们刚刚说过给堆分配了一个地址&#xff0c;把堆的地址赋给arr&#xff0c;arr就通过地址指向了数组。所以arr想操纵数组时&#xff0c;就通过地址&#xff0c;而不是直接把实体都赋给它。这种我们不再叫他基本数据类型&#xff0c;而叫引用数据类型。称为arr引用了堆内存当中的实体。&#xff08;可以理解为c或c&#43;&#43;的指针&#xff0c;Java成长自c&#43;&#43;和c&#43;&#43;很像&#xff0c;优化了c&#43;&#43;&#xff09;
如果当int [] arr&#61;null;
arr不做任何指向&#xff0c;null的作用就是取消引用数据类型的指向。
当一个实体&#xff0c;没有引用数据类型指向的时候&#xff0c;它在堆内存中不会被释放&#xff0c;而被当做一个垃圾&#xff0c;在不定时的时间内自动回收&#xff0c;因为Java有一个自动回收机制&#xff0c;&#xff08;而c&#43;&#43;没有&#xff0c;需要程序员手动回收&#xff0c;如果不回收就越堆越多&#xff0c;直到撑满内存溢出&#xff0c;所以Java在内存管理上优于c&#43;&#43;&#xff09;。自动回收机制&#xff08;程序&#xff09;自动监测堆里是否有垃圾&#xff0c;如果有&#xff0c;就会自动的做垃圾回收的动作&#xff0c;但是什么时候收不一定。
所以堆与栈的区别很明显&#xff1a;
1.栈内存存储的是局部变量而堆内存存储的是实体&#xff1b;
2.栈内存的更新速度要快于堆内存&#xff0c;因为局部变量的生命周期很短&#xff1b;
3.栈内存存放的变量生命周期一旦结束就会被释放&#xff0c;而堆内存存放的实体会被垃圾回收机制不定时的回收。
import java.util.Scanner;public class MyArray {public static void main(String[] args) {int col, row;Scanner sc &#61; new Scanner(System.in);String line &#61; sc.nextLine();String [] nums &#61; line.split(" ");col &#61; Integer.parseInt(nums[0]);row &#61; Integer.parseInt(nums[1]);System.out.println(col &#43; row);int [][] bigArray &#61; new int[col][row];for (int i &#61; 0; i < col; i&#43;&#43;) {for (int j &#61; 0; j < row; j&#43;&#43;) {bigArray[i][j] &#61; i &#43; j;}}System.out.println(bigArray[col-1][row-1]);}
}