目录
[void 指针(无类型指针)](#void 指针(无类型指针))
C程序的内存分配
C程序中,不同数据在内存中分配说明:
- 全局变量和静态局部变量------内存中的静态存储区/全局区
(随着程序加载到结束才销毁)
- 非静态的局部变量------内存中的动态存储区:stack 栈
(例如当调用一个函数时,就加载到栈里了,当函数执行完后函数内部的局部变量就弹出栈)
-
临时使用的数据------建立动态内存分配区域,需要时随时开辟,不需要时及时释放------heap 堆
-
根据需要向系统申请 所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名来引用这些数据,只能通过指针来引用)

void 指针(无类型指针)
每一块内存都有地址,通过指针变量可以获取指定地址的内存块。
指针变量必须有类型,否则编译器无法知道如何解读内存块保存 的二进制数据。但是,向系统请求内存的时候,有时不确定会有 什么样的数据写入内存,需要先获得内存块,稍后再确定写入的 数据类型。
综上,为了满足这种需求,C 语言提供了一种不定类型的指针,叫 做 void 指针。它只有内存块的 地址信息 , 没有类型信息 ,等到使 用该块内存的时候,再向编译器补充说明,里面的数据类型是什 么。此外,由于void 指针等同于无类型指针(typeless pointer),可以 指 向任意类型 的数据,但是 不能解读数据 。void 指针与其他所有类 型指针之间是 互相转换关系 ,任一类型的指针都可以转为 void 指 针,而 void 指针也可以转为任一类型的指针。
cs
#include <stdio.h>
#include <stdlib.h>
int main()
{
int x = 10;
int *p = &x;
printf("%d ",*p);
void *q = &x;
//无类型的指针变量可以和其他类型的指针变量相互转换(赋值)
int*r = q;
//这是错误的因为q是void指针,void不知道里面存的是什么类型的数据所以将没有办法去解读
//int y = *q;
//需要强转
int z = *((int *)q);
printf("%d ",z);
return 0;
}
内存动态分配函数
头文件 <stdlib.h>声明了四个关于内存动态分配的函数。
malloc()
函数原型
cs
void *malloc(unsigned int size); //size的类型为无符号整型
作用:在内存的 动态存储区(堆区) 中分配一个长度为size的连续空间 。并将该空间的首地址作为函数值返回,即此函数是一个指针函数。
由于返回的指针的基类型为 void,应通过显式类型转换后才能存入 其他基类型的指针变量,否则会有警告。如果分配失败,返回值空指针(NULL)。(堆里面没有那么大一块连续空间或者程序再运行中出现了一些错误)
cs
#include <stdio.h>
#include <stdlib.h>
//声明结构体
struct node
{
int date;
struct node * next;
}student;
int main()
{
//函数原型:void *malloc(unsigned int size);
//举例1:给int类型的变量分配空间
int *i;
i = (int *)malloc(sizeof(int));
//利用sizeof函数去动态的获取int类型的字节数,注意这时返回的是void类型的指针,现在如果明确的说是int类型的指针,需要强转,然后再接受
*i = 10;
//初始化int元素
printf("%d",*i);
//举例2:给int类型的数组分配空间
int arr = 10;
int *r;
r = (int *)malloc(arr * sizeof(int));//这样在堆空间开辟了一个int类型连续有十个的空间
//初始化数组的元素
for(int i=0;i<arr;i++)
{
r[i] = i;
}
//举例3:给结构体变量分配空间
struct node * q;
q = (struct node *)malloc(sizeof(struct node));
return 0;
}
关于返回值为NULL: malloc() 分配内存有可能分配失败,这时返回常量 NULL。Null 的值为0,是一个无法读写的内存地址,可以理解成一个不指向任何地方的指针。它在包括 stdlib.h 等多个头文件里面都有定义,所以只要可以使用 malloc() ,就可以使用 NULL 。由于存在分配失败的可能,所以最好在使用 malloc() 之后检查一下,是否分配成功。
cs
//关于NULL的判断
if(q != NULL)//意味分配成功
{
//针对此数据的初始化、运算等操作
//、、、、、
}
else//意味着分配失败
free()
函数原型:
cs
void free(void *p);
函数无返回值。p是最近一次调用malloc()或calloc()函数时的返回值。
作用:释放指针变量p所指向的内存空间,使这部分内存能重新被其 它变量使用。否则这个内存块会一直占用到程序运行结束。
cs
//关于NULL的判断
if(q != NULL)//意味分配成功
{
//针对此数据的初始化、运算等操作
//、、、、、
//释放内存空间
free(q);
}
else//意味着分配失败
注意:
1、指针 p 必须是经过动态分配函数 malloc 成功后返回的首地址。
2、分配的内存块一旦释放,就不应该再次操作已经释放的地址,也不应该再次使用 free()对该地址释放第二次。
3、如果忘记调用free()函数,同时p所在的函数调用结束后p指针已经消失了,导致无法访问未回收的内存块,构成内存泄漏。
calloc()
函数原型:
cs
void *calloc(unsigned int n,unsigned int size);
作用:在内存的动态存储区(堆区)中分配n个,单位长度为size的连续空间,这个空间一般比较大,总共占用n*size 个字节。并将该空 间的首地址作为函数的返回值。如果函数没有成功执行,返回 NULL。
calloc()函数适合为 一维数组 开辟动态存储空间,n为数组元素个数,每个元素长度为size。
举例:
cs
int *p;
p = (int *)calloc(10,sizeof(int));
//实际上就是个一维数组,开辟空间的同时,其内容初始化为零
//等同于
int* p;
p = (int *)malloc(10 * sizeof(int));
//开辟空间后,没有初始化
memset(p, 0, sizeof(int) * 10);
上面示例中,calloc()相当于malloc() + memest()
realloc()
函数原型:
cs
void *realloc(void* p, unsigned int size)
作用:重新分配malloc()或calloc()函数获得的动态空间大小,即调整 大小的内存空间。将先前开辟的内存块的指针p指向的动态空间大小 改变为size,单位字节。返回值是一个全新的地址(数据也会自动复 制过去),也可能返回跟原来一样的地址。分配失败返回NULL。
realloc() 优先在原有内存块上进行缩减,尽量不移动数据,所以 通常是返回原先的地址。
如果新内存块小于原来的大小,则丢弃超出的部分;如果大于原 来的大小,则不对新增的部分进行初始化(程序员可以自动调用 memset() )。
cs
int *b;
b = (int *)malloc(10*sizeof(int));
b = (int *)relloc(b,200*sizeof(int));
//指针b原来指向10个成员的整数数组,使用relloc()调整为200个成员的数组
题目:动态创建数组,输入5个学生的成绩,另外一个函数检测成绩 低于60 分的,输出不合格的成绩。
cs
#include <stdio.h>
#include <stdlib.h>
#define N 5
void checkscore(int *ptr)
{
printf("低于60分的成绩有:\n");
for(int i = 0;i<N;i++)
{
if(ptr[i]<60)
{
printf("%d \n",ptr[i]);
}
}
}
int main()
{
//1.使用mallco()动态申请内存空间,对应着int类型的长度为5的数组
int *ptr_score;
ptr_score = (int *)malloc(N * sizeof(int));
if(ptr_score !=NULL )
{
//2.给数组元素赋值使用scanf()函数从键盘上获取
printf("请输入%d个成绩:\n",N);
for(int i = 0;i<N;i++)
{
scanf("%d",ptr_score+i);
}
//3.声明个函数,在函数中判断成绩低于60分的情况,将低于60分的输出
checkscore(ptr_score);
//4.释放申请的空间
free(ptr_score);
}
return 0;
}
动态分配内存的基本原则
1)避免分配大量的小内存块。分配堆上的内存有一些系统开销,所 以分配许多小的内存块比分配几个大内存块的系统开销大
2)仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释 放它,否则可能出现内存泄漏。 这里需要遵守原则:谁分配,谁释放。
3)总是确保释放以分配的内存。在编写分配内存的代码时,就要确 定在代码的什么地方释放内存。
常见的内存错误及其对策
1)内存分配未成功,却使用了它 新手常犯这种错误,因为他们没有意识到内存分配会不成功。 常用解决办法是,在使用内存之前检查指针是否为NULL。比如,如果指针p是函数的参数,那么在函数的入口处应该用 if(p==NULL) 或 if(p!=NULL) 进行防错处理。
2)内存分配虽然成功,但是尚未初始化就引用它
犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为 内存的缺省初值全为零,导致引用初值错误。
cs
int * p = NULL;
p = (int*)malloc(sizeof(int));
if (p == NULL){/*...*/}
/*初始化为0*/
memset(p, 0, sizeof(int));
3)内存分配成功并且已经初始化,但操作时提示内存越界
在使用数组时经常发生下标"+1"或者"-1"的操作,特别是在for循环 语句中,循环次数很容易搞错,导致数组操作越界。
数组访问越界在运行时,它的表现是不定的,有时什么事也没有, 程序一直运行(当然,某些错误结果已造成);有时,则是程序一 下子崩溃。
4)忘记了释放内存,造成内存泄漏
含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统 的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提 示:内存耗尽。
动态内存的申请与释放必须配对,程序中 malloc() 与 free() 的 使用次数一定要相同,否则肯定有错误。
5)未正确的释放内存,造成内存泄漏
程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是 否已经释放了内存。此时应该重新设计数据结构,从根本上解决对 象管理的混乱局面。
cs
#include <stdio.h>
#include <stdlib.h>
void getMemory(int *p) {
p = (int *) malloc(sizeof(int));
// 在这里修改的是局部指针 p,不会影响 main 函数中的原始指针 ptr
//....
}
int main() {
int *ptr = NULL;
getMemory(ptr);
// 将 ptr 的值传递给 getMemory,但是在函数内部修改的是 p,而不是 ptr
printf("ptr = %d\n", *ptr);
// 这里的 *ptr 是未定义行为,因为 ptr 没有指向有效的内存
free(ptr); // 这里试图释放未分配的内存,会导致问题
}
在本例中,getMemory()中的p申请了新的内存,只是把 p所指的内 存地址改变了,但是ptr丝毫未变。getMemory()中的p也始终没有进 行内存的释放。事实上,因为没有用free释放内存,每执行一次 getMemory()就会泄漏一块内存。
在计算机系统,特别是嵌入式系统中,内存资源是非常有限的。尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何有效地管理内存资源 。