一、动态内存申请
C语言支持动态存储分配,即在程序执行期间分配内存单元的能力。利用动态存储分配,可以设计出能根据需要扩大(和缩小)的数据结构。
虽然动态存储分配适用于所有类型的数据,但主要用于字符串、数组和结构。动态分配的结构式特别有趣的,因为可以把它们链接成表、树或其他数据结构。
1、内存分配函数
为了动态的分配存储空间,需要调用三种内存分配函数的一种,这些函数都是声明在<stdlib.h>头文件中的。
· malloc函数——分配内存块,但是不对内存块进行初始化。初始化清零要与memset函数连用;
· calloc函数——分配内存块,并且对内存块进行清零。
· realloc函数——调整先前分配的内存大小。
三种函数中,malloc函数是最常用的一种。因为malloc函数不需要对分配的内存块进行清零,所以它比calloc函数更加高效。
当为申请内存块而调用内存分配函数时,由于函数无法知道计划存储在内存块中的数据是什么类型的,所以它不能返回int类型、char类型等普通类型的指针。取而代之的,函数会返回void *类型的值。void *类型的值是通用指针(万能指针),本质上它只是内存地址。
2、空指针
当调用内存分配函数时,总存在这样的可能性:找不到满足我们需要的足够大的内存块。如果真的发生了这类问题,函数会返回空指针。空指针是“不指向任何地方的指针”,这是一个区别于所有有效指针的特殊值。在把函数的返回值存储到指针变量中以后,需要判断该指针变量是否为空指针。
p = malloc(sizeof(int)*2500) //即10000字节
if(p == NULL)
{...}
二、动态分配数组
1、使用malloc函数为数组分配存储空间
可以使用malloc函数为数组分配存储空间,我们需要使用sizeof运算符来计算出每个元素所需要的空间数量。
假设正在编写的程序需要n个整数构成的数组,这里的n可以在程序执行期间计算出来。首先需要申明指针变量:
int *a;
一旦n的值已知了,就让程序调用malloc函数为数组分配存储空间:
a = malloc(n*sizeof(int));
一旦a指向动态分配的内存块,就可以忽略a是指针的事实,可以把它用作数组的名字;
2、calloc函数
可以用malloc函数来为数组分配内存,但是C语言还提供了另外一种选择(calloc函数).
calloc函数会通过把所有位设置为0的方式初始化。
3、realloc函数
一旦为数组分配完内存,稍后可能会发现数组过大或小。realloc函数可以调整数组的大小。
三、释放存储空间
C语言当中 free() 函数与 malloc() 函数应是成对出现的:
malloc() 函数负责空间的申请, malloc() 函数的对头free() 函数则负责将 malloc() 函数申请的空间给释放掉;
#include <stdio.h>
#include <stdlib.h>
#define MALLOC_SIZE 250
int main(void)
{
//申请一段长度为250,类型为 char 的空间
char *pointer = null;
pointer = (char *)malloc( MALLOC_SIZE * sizeof(char) );
//释放空间
if(null != pointer)
{
free( pointer );
pointer = null;
}
return 0;
}
以上是个申请空间又释放掉的显得闲得蛋疼的例子,那问题来了:
free() 函数是如何确定要释放空间大小的?
free() 函数释放掉的是什么内容呢?
指针 pointer 不指向 null 行不行?
不判断 null != pointer 是否必要?
free( pointer + 1),能否释放?
以下为个人的理解:
第一点:申请空间的大小是由用户决定的,系统有基于链表的内存管理,申请得到的空间在物理空间上不一定连续的,但申请到的空间映射出的虚拟空间是连续的,申请空间时,对于系统来说,系统只会关注申请空间的大小,并不关注空间类型,所以申请空间返回的空间也不具有特定的空间类型,只是分配了一段空间供用户使用; malloc函数返回来的是申请到空间的地址,然后用户使用一个指针指向这块空间,便可以使用了;在申请到空间的开始地址前的空间会记录着空间的大小,因此在 free() 释放空间时会先去确认空间的大小,再进行空间释放。
第二点:
空间结构的大致图示,假设此时指针 pointer 指向空间首地址0x0001,空间中存用户想要存储的数据,调用 free( pointer ) 函数,会将空间的内存给释放掉,也算是将空间的使用权给释放掉,释放后用户无法合法访问这段空间,释放掉后的空间存储的数据是随机值,释放后的空间可在下次申请空间时循环使用,若使用完之后空间不释放,则这块空间无法归还给系统,无法再次利用,导致内存泄漏;
第三点:调用调用 free( pointer ) 函数,指针 pointer 是不会被释放掉的,即便释放空间后,指针 pointer 还是指向地址0x0001的地方,释放后再继续调用指针则会越界,为了避免此类错误,最好在释放空间后将指针指向null;
第四点:释放空间前最好先判断一下 null != pointer ,因为有可能出现系统空间不足导致申请空间失败的情况,也就无法进行空间释放了;
第五点:free( pointer + 1) 是无法释放空间的,因为申请空间的地址不匹配了,而且无法找到这段空间大小的记录,导致释放空间失败;只有地址与申请空间的首地址匹配,系统查找到合理的空间长度记录才会进行空间释放操作;
四、一些作业
0个小孩围成一圈分糖果,老师顺次分给每个人的糖数为12 2 8 22 16 4 10 6 14 20.然后按下列规则进行调整,所有小孩同时把自己的糖果分一半给右边的小孩,糖块数变奇数的人,再向老师补要一块,问经过多少次调整后,大家的糖块一样多,且每人多少块?
#include<stdio.h>
int main()
{
int count = 0;
int arr[10] = {12, 2, 8, 22, 16, 4, 10, 6, 14, 20};
int arr1[10] = {0};
while(1)
{
for(int i = 0 ; i < 10; i++)//判断是否是偶数,偶数除以2,否则加一除以2
{
if(arr[i] % 2 == 0)
{
arr[i] /= 2;
arr1[i] = arr[i];
}
else
{
arr[i] = (arr[i] + 1) / 2;
arr1[i] = arr[i];
}
}
for(int i = 0; i < 9; i++)//左边的给右边的
{
arr[i + 1] += arr1[i];
}
arr[0] += arr1[9];
for(int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
count++;//分一次计数
int flag = 0;
for(int i = 0; i < 10; i++)//看是否分完
{
for(int j = 0; j < 10; j++)
{
if(arr[i] != arr[j])
{
flag = 1;
}
}
}
printf("\n");
if(flag == 1)
{
flag = 0;
}
else
{
break;
}
}
printf("%d次后每个孩子的糖一样, 有%d个", count, arr[0]);
return 0;
}