指针
了解
1、指针是什么
指针是一种特殊的数据类型,使用它可以定义指针变量,指针变量存储整型数据,代表了内存的编号,可以通过编号访问对应的内存
2、什么时候使用指针:
1、函数之间共享变量(要返回很多数据时也可用指针)
2、提高函数传参效率时4\8字节(64位时8)
3、使用堆内存
3、如何使用指针:
定义: 类型名* 变量名_p=NULL; 使用:
赋值:必须是有权限、有意义的内存地址
栈内存:
int p=NULL;
p=#
堆内存:
int p=malloc(4);
解应用:*p;
通过指针变量中记录的编号去访问对应的内存,一般容易产生段错误,原因可能是存储了非法的内存地址
使用
一、使用指针时需要注意的问题:
空指针:值为NULL的指针变量叫做空指针
如果对空指针解引用一定会产生段错误
注意:NULL一般作为一种错误标志,当一个函数的返回值是指针类型时,可以使用return NULL;作为函数执行出错的返回结果
如何避免空指针带来的段错误:
使用来历不明的指针前先做判断 if(NULL==p) if(!p)
1、但函数的参数是指针类型时,使用前先判断(别人传给你的指针可能是空指针)
2、从函数获取的返回值是指针类型时,可能会返回空指针
注意:NULL在绝大多数的 系统中是0,个别是1
野指针:
指向不确定的内存空间的指针叫做野指针(值不确定)
对野指针解引用的后果:
1、有权限,一切正常
2、非法内存,段错误
3、脏数据
野指针比空指针危害更严重,因为野指针无法判断出来,而且可能是隐藏性的错误,短时间不暴露
所有的野指针都是程序员自己制造出来的,如何避免产生野指针
1、定义 指针变量时一定要初始化
int*p=NULL;
2、函数不要返回栈内存(函数内的局部变量,因为操作系统会自动释放 )的地址
3、指针指向的内存被释放后free(指针),指针变量要及时置空NULL
二、指针的运算
指针变量中存储的是整数 ,理论上整数可以使用的运算符它都可以使用,但绝大多数运算符毫无意义的
指针+n 指针+指针类型宽度n 前进了n个元素
指针-n 指针-指针类型宽度n 后退了n个元素
指针-指针 (指针-指针)/指针类型宽度 (必须同类型指针) 计算两个指针之间间隔了多少个指针元素
三、指针与const(当我们为了提高传参效率而使用指针作为函数参数是,传参效率提高了,但是变量共享存在被修改的风险,可以使用const保护指针所指向内存)
const int* p; 保护指针所指向的内存数据不被修改
int const p; 同上
int const p; 保护指针变量不被修改
const int* const p; 指针变量 和指针所指向内存都不能修改
int const* const p; 同上
四、指针数组和数组指针
指针数组:由指针变量组成的数组,它的成员都是类型相同的指针变量
类型* arr[长度];
int* arr【10】={};
数组指针:是专门指向数组的指针
类型(* arrp)[长度];
五、数组名与指针:
数组名就是一种特殊的指针
数组名是常量,是数组的首地址,不能修改它的值,数组名没有自己的存储空间,它与数组首地址之间是映射关系
数组名 ==&数组名 数组名可以解引用 指针也能用中括号 ,当数组用
指针变量是拥有自己的存储空间,它与所指向的内存是指向关系
当指针变量指向数组首地址时,指针可以当做数组名使用,数组名也可以当指针使用。
数组名【i】==*(数组名+i)
堆内存
1、什么是堆内存(heap)
是进程的一个内存段,由程序员手动 管理
优点足够大,缺点使用麻烦
2、为什么要使用堆内存
1、随着程序的复杂,数据量变多
2、其他内存段的申请释放不受控制,堆内存的申请释放受控制,可以适时地节约内存
3、如何使用堆内存
注意:C语言中没有控制(管理)堆内存的语句,只能用c标准库中的函数
#include <stdlib.h>
void *malloc(size_t size);
(类型)int* p=malloc(n长度);
功能:从堆内存中申请size个字节的连续内存块,申请到的内存中数据的值不确定
返回值:成功返回申请到的连续内存的首地址,失败返回NULL
注意:void* 在 C语言中是万能指针,可以与任意类型指针互换,但是在c++中不能自动转换成其他类型的指针,如果想让代码在c++编译器中兼容,需要进行强制类型转换
c:int* p=malloc(4);
c++:int* p=(int*)malloc(4);
void free(void *ptr); free(p);
功能:释放一块堆内存
ptr:要释放的堆内存的首地址
注意:free 释放只是使用权限,数据不会全部清理,要及时置空,不能连续释放,但NULL可以连续释放
void *calloc(size_t nmemb, size_t size); short* p=calloc(20,2)==malloc(40)
功能:从堆内存申请nmemb块,每块size字节大小的内存calloc(10,4)==malloc(40)
返回值:成功返回申请到的连续内存的首地址,失败返回NULL
注意:calloc申请到的内存会被初始化为0,速度慢
void *realloc(void *ptr, size_t size); p=realloc(p,10);
功能:改变已有的堆内存的大小,size表示调整后的大小,在原有的基础上调大调小
返回值:调整后内存块的新首地址,一定要重新接收返回值,可能不是在原位置进行调整
如果无法在原位置调整:
1、申请一块 新的符合大小的内存
2、拷贝原内存中的数据
3、释放原内存,返回新内存首地址
4、malloc的内存管理机制
1、当首次向malloc申请内存,malloc会向操作系统 申请内存,操作系统会直接分配33页内存(一页=4096字节)交给malloc管理,但是不意味着可以越界访问,因为malloc可能把其他的内存分配给 “其他人”,,这样就会产生 脏数据
2、每个内存块之间会有空隙(4-12字节),一部分是为了内存的对齐,其中一定有4字节记录了malloc的维护信息,这些维护信息决定了下一次malloc分配内存的位置,如果破坏了维护信息,会影响下一次malloc或者free的过程,导致栈崩溃
5、使用堆内存需要注意的问题
内存泄露:
内存无法再使用,也无法被释放,而需要再次使用时只能重新申请内存,然后继续重复以上过程,日积月累后可用的内存越来越少
注意:一旦进程结束属于该进程的所以资源都会被操作系统回收
ps -aux
如何尽量地避免内存泄漏:
谁申请谁释放,谁知道该释放谁释放
如何判断、定位内存泄漏:
1、查看内存使用情况 win 用任务管理器 linux ps -aux命令
2、借助代码分析工具 mtrace,检查malloc和free是否成对出现
3、封装malloc、free,记录申请、释放的信息到日志文件中
void *m_malloc(size_t size)
{
}
内存碎片:
已经释放了但无法继续使用的内存叫做内存碎片,由于申请和释放的时间不协调导致的,无法完全避免 只能尽量的减少
如何减少内存碎片:
1、尽量的使用栈内存
2、不要频繁地申请、释放内存
3、尽量申请较大块的内存自己管理
6、内存清理函数
bzero
#include <strings.h>
void bzero(void *s, size_t n);
功能:把一块内存块清理为0
s:内存块的首地址
n:内存块的字节数
#include <string.h>
void bzero(void *s,int c, size_t n);
功能:把内存块按字节设置为c
s:内存块的首地址
c:想要设置的ASCII值
n:内存块的字节数
返回值:成功设置后的内存首地址
7、堆内存中定义二维数组
指针数组:int *arr[10];
可以定义n行,m列的二维数组,如果malloc()里的数变化,每一行的列数变化,可以定义不规则的二维数组
缺点:申请麻烦、容易产生内存碎片
优点:可以 不规则、对内存要求低
int arr[10]={};
for(int i=0;i<10;i++)
{
arr[i]=malloc(msizeof(类型));
}
数组指针: int (*arrp)【n】
int (*arrp)【10】=malloc(sizeof(int)*10(n列)5(m行))5行10列
缺点:对内存要求高,可能申请失败
优点:申请简单
arrp【i】【j】或者((*arrp+i)+j)
注意:所谓的多维数组其实都是用一维数组模拟