C语言基础---6.动态数组和静态数组详解---入门

0.堆 & 栈

内存分配有如下三种形式:

  • 从静态存储区域分配:它是由编译器自动分配和释放的,即内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,直到整个程序运行结束时才被释放,如全局变量与 static 变量。
  • 在栈上分配:它同样也是由编译器自动分配和释放的,即在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元将被自动释放。需要注意的是,栈内存分配运算内置于处理器的指令集中,它的运行效率一般很高,但是分配的内存容量有限。
  • 从堆上分配:也被称为动态内存分配,它是由程序员手动完成申请和释放的。即程序在运行的时候由程序员使用内存分配函数(如 malloc 函数)来申请任意多少的内存,使用完之后再由程序员自己负责使用内存释放函数(如 free 函数)来释放内存。也就是说,动态内存的整个生存期是由程序员自己决定的,使用非常灵活。需要注意的是,如果在堆上分配了内存空间,就必须及时释放它,否则将会导致运行的程序出现内存泄漏等错误。

1.分配与释放方式不同:

栈内存是由编译器自动分配与释放的,它有两种分配方式:静态分配和动态分配。
静态分配是由编译器自动完成的,如局部变量的分配(即在一个函数中声明一个 int 类型的变量i时,编译器就会自动开辟一块内存以存放变量 i)。与此同时,其生存周期也只在函数的运行过程中,在运行后就释放,并不可以再次访问。

动态分配由 alloca 函数进行分配,但是栈的动态分配与堆是不同的,它的动态分配是由编译器进行释放,无需任何手工实现。值得注意的是,虽然用 alloca 函数可以实现栈内存的动态分配,但 alloca 函数的可移植性很差,而且在没有传统堆栈的机器上很难实现。因此,不宜使用于广泛移植的程序中。当然,完全可以使用 C99 中的变长数组来替代 alloca 函数。

2.分配的碎片问题:

对堆来说,频繁分配和释放(malloc / free)不同大小的堆空间势必会造成内存空间的不连续,从而造成大量碎片,导致程序效率降低;而对栈来讲,则不会存在这个问题。

3.分配的效率:

大家都知道,栈是机器系统提供的数据结构,计算机会在底层对栈提供支持,例如,分配专门的寄存器存放栈的地址,压栈出栈都有专门的执行指令,这就决定了栈的效率比较高。一般而言,只要栈的剩余空间大于所申请空间,系统就将为程序提供内存,否则将报异常提示栈溢出。

而堆则不同,它是由 C/C++ 函数库提供的,它的机制也相当复杂。例如,为了分配一块堆内存,首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。而对于大多数系统,会在这块内存空间的首地址处记录本次分配的大小,这样,代码中的 delete 语句才能正确释放本内存空间。另外,由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动将多余的那部分重新放入空闲链表中。很显然,堆的分配效率比栈要低得多。

4. 申请的大小限制:

由于操作系统是用链表来存储空闲内存地址(内存区域不连续)的,同时链表的遍历方向是由低地址向高地址进行的。因此,堆内存的申请大小受限于计算机系统中有效的虚拟内存。

而栈则不同,它是一块连续的内存区域,其地址的增长方向是向下进行的,向内存地址减小的方向增长。由此可见,栈顶的地址和栈的最大容量一般都是由系统预先规定好的,如果申请的空间超过栈的剩余空间时,将会提示溢出错误。由此可见,相对于堆,能够从栈中获得的空间相对较小。

5. 存储的内容:

对栈而言,一般用于存放函数的参数与局部变量等。例如,在函数调用时,第一个进栈的是(主函数中的)调用处的下一条指令(即函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数 C 编译器中,参数是由右往左入栈的,最后是函数中的局部变量(注意 static 变量是不入栈的)。

6.各类型变量的存储位置和作用域:

  • 全局变量:
    从静态存储区域分配,其作用域是全局作用域,也就是整个程序的生命周期内都可以使用。与此同时,如果程序是由多个源文件构成的,那么全局变量只要在一个文件中定义,就可以在其他所有的文件中使用,但必须在其他文件中通过使用extern关键字来声明该全局变量。
  • 全局静态变量:
    从静态存储区域分配,其生命周期也是与整个程序同在的,从程序开始到结束一直起作用。但是与全局变量不同的是,全局静态变量作用域只在定义它的一个源文件内,其他源文件不能使用。
  • 局部变量:
    从栈上分配,其作用域只是在局部函数内,在定义该变量的函数内,只要出了该函数,该局部变量就不再起作用,该变量的生命周期也只是和该函数同在。
  • 局部静态变量:
    从静态存储区域分配,其在第一次初始化后就一直存在直到程序结束,该变量的特点是其作用域只在定义它的函数内可见,出了该函数就不可见了。

文章来源:http://c.biancheng.net/c/stack/

1.静态数组

int a[20];
int b[] = {1, 2, 3}

静态数组的长度为常量,在栈中分配内存空间,会自动释放。使用sizeof时,计算的是整个数组的字节大小

  • 如果数组大小已经确定,可以使用静态数组,效率较高;
  • 如果数组大小需要在运行时确定(比如用户输入,函数参数传递等),则使用动态数组。
  • 如果需要在函数中返回数组,则必须注意用静态数组时,由于内存在栈中分配,函数执行完毕时会自动销毁,所以返回一个静态数组变量是无意义的;使用动态数组就可以返回,并在不需要时注意delete释放堆中的内存。

2.动态数组

动态数组在堆中分配内存,必须手动释放。使用sizeof时,计算的是指针变量所占内存的字节大小。

1.C中使用malloc & free
#include <stdio.h>


int getSpace() {

	int* p = malloc(sizeof(int) * 5);    // 创建动态数组

	if (NULL == p) {
		return NULL;
	}

	int j = 0;
	for (int i = 0; i < 5; ++i) {
		p[i] = 100 + i;
	}

	return p;
}



int main() {

	int* ret = getSpace();
	for (int i = 0; i < 5; ++i) {
		printf("%d  ", ret[i]);
	}
	printf("\n");
	printf("%d \n", *ret);
	printf("%p \n", ret);

	free(ret);					    // 释放内存空间,相当于解绑指针和内存的指定
	printf("==============\n");

	printf("%d \n", *ret);			// 此时指针没有指定具体的内存空间,相当于定义但未初始化的指针,打印结果是个随机数
	printf("%p \n", ret);			// 此时指针所保存的内存地址还保留的,所以地址还能打印出来

	ret = NULL;						// 正确的做法是,free之后,紧接着设置ret=NULL;这样指针的地址也就为00000000了
	printf("==============\n");


	printf("%p \n", ret);			// 已经指向为NULL,NULL的地址为00000000
	//printf("%s \n", *ret);		// 因为已经指向为NULL了,此时程序对这个指针没有权限了,所以无法访问具体的值了


}



100  101  102  103  104
100
016E8758
==============
-572662307
016E8758
==============
00000000

我个人理解的new & free的原理过程如下(也可能不对,欢迎批评指针):

1.指针指向变量内存之后的情况:
在这里插入图片描述
2.free之后的情况:
在这里插入图片描述

  • 此时指针还保存着原始变量的地址,但是如果想获取指针对应的值,是没法获取的,因为指针和内存已经没有指向关系了。
  • 此时内存被释放,里面的值也都为空了,等待下次系统的调用

3.set ret = NULL之后:

在这里插入图片描述

此时指针保存的内容才为NULL,相当于仅仅定义但未初始化的指针;

2.C++中使用new & delete
  • new函数只有C++中存在,底层原理也是调用malloc函数的
  • new和malloc函数的区别:https://www.cnblogs.com/ywliao/articles/8116622.html

#include<iostream>
using namespace std;

int main()
{
	int leng = 20;
	int* a = new int[leng];					// new开辟了一段内存空间后会返回这段内存的首地址,需要用指针接收
	cout << a << endl;						// 打印a数组的地址
	cout << *a << endl;						// 打印a数组的第一个元素值
	delete[] a;								// delete []  这个才是一个整体,代表释放数组的指针
	cout << a << endl;						// 虽然释放内存空间了,
	a = 0;
	cout << a << endl;
	//cout << *a << endl;					// 因为已经释放内存空间了,a没有具体值了,会报错
	printf("===============\n");

	int* arr = new int[100]{ 1, 2 };		// 动态:为长度为100的数组array初始化前两个元素
	cout << arr << endl;
	cout << *arr << endl;
	delete[] arr;
	cout << arr << endl;
	// cout << *arr << endl;

}



00120168
-842150451
00008123
00000000
===============
0011D260
1
00008123


3.错误用法集锦:

1.数组的大小,需要使用常量定义,否者报错
int len;
int p[len];   // C++编译器会报错说len的大小不能确定,因为用这种形式声明数组,数组的大小需要在编译时确定

2.动态数组的大小,需要用指针接收

int len;
int p[]=new int[len];   
// 编译器会说不能把int*型转化为int[]型,因为用new开辟了一段内存空间后会返回这段内存的首地址,所以   要把这个地址赋给一个指针,所以要用int   *p=new   int[len];

3.静态数组在函数中:无法传递参数给另一个函数
int* test(int* b)				//b可以是静态数组的数组名,也可以是动态数组的首地址
{
	for (int i = 0; i < 5; i++) //输出传入的数组各元素 
	{
		cout << "test函数--传入各个元素:"<< *(b + i) << " \n";
	}
	cout << endl;
	int c[5];					//创建静态数组,主函数中调用test无法得到c数组  	
	for (int i = 0; i < 5; i++) //新数组的各项值等于传入的数组各项值加5  
	{							
		*(c + i) = *(b + i) + 5;
	}
	return c;					//静态数组,无法起到传递参数的作用

}


int main() {
	int b[5] = { 0,1,2,3,4 };	// 原始静态数组
	int* c = test(b);			 //将b作为参数,调用test函数
	for (int i = 0; i < 5; i++)  //输出test返回的数组的各个元素  
	{
		cout << *(c + i) << " ";
	}
	cout << endl; 
	return 0;
}


test函数--传入各个元素:0
test函数--传入各个元素:1
test函数--传入各个元素:2
test函数--传入各个元素:3
test函数--传入各个元素:4

5 -858993460 0 -858993460 539087659		// 发现在test函数中,创建的静态函数,根本没有传递给main函数中,而是一组随机值


4.动态数组:可以做为参数传递给其他函数
int* test(int* b)
{
	for (int i = 0; i < 5; i++) 
	{
		cout << "test函数--传入各个元素:" << *(b + i) << " \n";
	}
	cout << endl;
	int* c = new int[5];		//创建动态数组 	
	for (int i = 0; i < 5; i++) 
	{
		*(c + i) = *(b + i) + 5;
	}
	return c;					

}


int main() {
	int b[5] = { 0,1,2,3,4 };	// 原始静态数组
	int* c = test(b);			
	for (int i = 0; i < 5; i++) 
	{
		cout << *(c + i) << " ";
	}
	cout << endl;
	return 0;
}



test函数--传入各个元素:0
test函数--传入各个元素:1
test函数--传入各个元素:2
test函数--传入各个元素:3
test函数--传入各个元素:4

5 6 7 8 9


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello-alien

您的鼓励,是我最大的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值