【数组】动态开辟二维数组(二级指针和结构体)/堆栈的区别/内存碎片

栈和堆的区别:

``

  1. 管理方式:栈由系统自动管理;堆由程序员控制,使用方便,但易产生内存泄露。
  2. 生长方向: 栈向低地址打展(即”向下生长”),是连续的内存区域;堆向高地址扩展(即"向上生长”),是不连续
    的内存区域。这是由于堆区管理系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。
  3. 空间大小: 栈顶地址和栈的最大容量由系统预先规定(通常默认1M或10M);堆的大小则受限于计算机系统中有
    效的虚拟内存,32位Linux系统中堆内存可达2.9G空间。
  4. 存储内容: 栈在函数调用时,首先压入是函数实参,然后主调函数中下条指令(函数调用语句的下条可执行语句)的
    地址压入,最后是被调函数的局部变量。本次调用结束后,局部变量先出栈,指令地址出栈,最后栈平衡,程序
    由该点继续运行下条可执行语句。堆通常在头部用一个字节存放其大小,堆用于存储生存期与函数调用无关的数
    据,具体内容由程序员安排。
  5. 分配方式: 栈可静态分配或动态分配。静态分配由编译器完成,如局部变量的分配。动态分配由alloca函数在栈
    上申请空间,用完后自动释放不需要调动free函数。堆只能动态分配且手工释放。
  6. 分配效率:栈由计算机底层提供支持:分配专[的寄存器存放栈地址,压栈出栈由专门的指令执行,因此效率较
    高。堆由函数库提供,机制复杂,效率比栈低得多。
  7. 分配后系统响应: 只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则报告异常提示栈溢出。
    操作系统为堆维护一个记录空闲内存地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空
    间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点空间分配给程序。若无足够大小的
    空间(可能由于内存碎片太多),有可能调用系统功能去增加程序数据段的内存空间,以便有机会分到足够大小的内
    存,然后进行返回。大多数系统会在该内存空间首地址处记录本次分配的内存大小,供后续的释放函数(如
    free/delete)正确释放本内存空间。
    此外,由于找到的堆结点大小怀-定正好等于申请的大小,系统会自动将多余的部分重新放入空闲链表中。
  8. 碎片问题:栈不会存在碎片问题,因为栈是先进后出的队列,内存块弹出栈之前,在其上面的后进的栈内容已弹
    出。 而频繁申请释放操作会造成堆内存空间的不连续,从而造成大量碎片,使程序效率降低。
    可见,堆容易造成内存碎片;由于没有专门]的系统支持,效率很低;由于可能引发用户态和内核态切换,内存申请
    的代价更为昂贵。所以栈在程序中应用最广泛,函数调用也利用栈来完成,调用过程中的参数、返回地址、栈基指针和
    局部变量等都采用栈的方式存放。所以,建议尽量使用栈,仅在分配大量或大块内存空间时使用堆。
    最后使用栈和堆时应避免越界发生,否则可能程序崩溃或破坏程序堆、栈结构,产生意想不到的后果。
    ``

内存碎片:

  • 内部碎片
    是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免;
    如:申请 43 Byte,因为没有合适大小的内存,会分配 44 Byte 或 48 Byte,就会存在 1 Byte 或 3 Byte 的多余空间。
  • 外部碎片
    是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。
    比如有一块共有 100 个单位的连续空闲内存空间,范围为 0 ~ 99,如果从中申请了一块 10 个单位的内存块,那么分配出来的就是 0 ~ 9 。这时再继续申请一块 5个单位的内存块,这样分配出来的就是 10 ~ 14 。如果将第一块释放,此时整个内存块只占用了 10 ~ 14 区间共 5 个单位的内存块。然后再申请 20个单位的内存块,此时只能从 15 开始,分配 15 ~ 24 区间的内存块,如果以后申请的内存块都大于 10 个单位,那么 0 ~ 9 区间的内存块将不会被使用,变成外部碎片。
  • 现在普遍采用的段页式内存分配方式就是将进程的内存区域分为不同的段,然后将每一段由多个固定大小的页组成。通过页表机制,使段内的页可以不必连续处于同一内存区域,从而减少了外部碎片,然而同一页内仍然可能存在少量的内部碎片,只是一页的内存空间本就较小,从而使可能存在的内部碎片也较少。
  • 一维数组和二维数组比较
    int ar[4];------------------------------------------ int br[3][4];
    sizeof(ar); ----------------------------------------sizeof(br];
    :ar表示的是整个数组的大小 ------------------------同ar
    int *p = ar; ar代表数组首元素的地址-----------------int *(p)[4] = br; br代表数组首元素的地址,二维数组的首元素是一维数组
    int *(p)[4] = &ar----------------------------------------int *(s)[3][4] = &br ;s存放了三行四列,元素类型为整型的二维数组。
    :p存放了元素个数为4类型为整型的数组
  • C语言按照行优先存放
    一维数组的数组名等于首元素的地址
    二维数组的首元素是一个一维数组,所以例如:int ar[3][4] int (*p)[4] = ar; (p是一个指针,占有四个字节,可以存放数组(开辟空间大小为4)的空间,)
    二维数组的数组名,是其首元素的地址
    int ar[4] int (*s)[4] =&ar
void Print_Ar(int br[3][4],int row,int col)
{
   int size = sizeof(br);//占字节数为4,退化为指针  (*br)[4],而不是**br
}
               

br[3][4]退化为指针 (*br)[4],而不是br**

请添加图片描述

  • 分块查询
  int FindValue(int(*br)[4],int row,int col,int val)
{
	int i = 0;
	while(i<row && val > br[i][col-1])  //和每行最大值比
	{
	//下标-1	
		++i;
	}
	if (i == row) return -1;

	//分块查询
	//如何使用二分查询


	int j = col - 1;   //找到所在行的val
	while (j >= 0)
	{
		if (br[i][j] == val)
		{
			return i * col+j;
		}
		--j;
	}
	return -1;

}
int main()
{
	int br[3][4] = {
	11,22,33,44,
	55,66,77,88,
	99,111,222,333
	};
	int pos = FindValue(br, 3, 4, 222);
	printf("%d\n", pos);
	if (pos != -1)
	{
		printf("row = %d:col = %d\n", pos / 4, pos %4);
	}
}
  • 如何使用二分查询
  • 利用二级指针实现二维数组
    请添加图片描述堆区开辟的空间是不连续的。(上越界标记,下越界标记)

请添加图片描述

int** Get2Arry(int **s,int row, int col)
{
    s = (int**)malloc(sizeof(int) * row);
    if (s == NULL)exit(1);
    for (int i = 0; i < col; ++i)
    {
        s[i] = (int*)malloc(sizeof(int) * col);
        if (s[i] == NULL) exit(1);
    }
    return s;  //值的传递
}
void Init_2Ar(int** s, int row, int col)
{
    assert(s != NULL);
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; ++j)
        {
            s[i][j] = i + j;
        }
    }
 }
void Print_2Ar(int** s, int row,int col)
{
    assert(s != NULL);
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; ++j)
        {
            printf("%5d", s[i][j]);
        }
        printf("\n");
        }
}
void Free_2Ar(int** s, int row)
{
    for (int i = 0; i < row; i++)
    {
        free(s[i]);
    }
    free(s);
}
int main()
{
    int** s = NULL;
    int row = 0, col = 0;
    scanf_s("%d,%d", &row, &col);
    s = Get2Arry(row, col );//得有人接收
    Init_2Ar(s, row, col);
    Print_2Ar(s, row, col);

 }
  • 利用结构体实现二维数组
伪代码
struct Array2
{
    ELEMTYPE* data;
    int row;
    int col;
};
//int Init_Ar2(Array2* br, int row, int col)
int Init_Ar2(Array2&br, int row, int col)
{
    //assert(br != NULL);没有所谓的空引用
    //形参的改变不能改变实参的值,因此需要使用结构体指针指向实参,将形参的值赋给实参
    br.row = row;   //需要对结构体里的成员初始化
    br.col = col; //初始化
    int n = br.row * br.col;
    br.data = (ELEMTYPE*)malloc(sizeof(ELEMTYPE) * n);
    assert(br.data != NULL);
    for (int i = 0; i < n; ++i) 
    {
        br.data[i] = i + 1;
    }
};
ELEMTYPE GetItem2(Array2&br,int r,int c)
{
   // assert(&br != NULL);
    assert(r < br.row && c < br.col); //一定要判断
    return br.data[r * br.col + c];   //行*列  1*4
    return 0;
}

int main()
{
    struct Array2 ar;
    int row, col;
    scanf_s("%d %d", &row, &col);
    Init_Ar2(ar,row,col);
    int x =  GetItem2(ar,2,3);
  
}

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值