IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《深入理解C语言指针》精华摘录与解读(一)初识指针&内存分配(malloc&free...) -> 正文阅读

[C++知识库]《深入理解C语言指针》精华摘录与解读(一)初识指针&内存分配(malloc&free...)


解读《深入理解C指针》,以及附上一些书中未提到的知识点,结合来理解。

关于指针&函数,数组,结构体,字符串的详解会在解读(二)指针&函数、数组、结构体、字符串中。

一、认识指针

1.1指针和内存

①、指针是一个保存内存地址的变量。
②、C的动态内存分配实际上就是通过使用指针实现的。
③、在数据类型后面跟*,再加上指针变量的名字可以声明指针,声明时,*两边的空白符无关紧要。
④、指向未初始化的内存的指针可能会产生问题。尽管不经过初始化就可以使用指针,但只有初始化后,指针才会正常工作。最好尽快初始化指针
⑤、赋值时注意是否是指针类型变量以及数据长度,例如int int*是两种类型变量,可以将void指针赋值给其他任何指针类型
⑥、间接引用操作符(*)返回指针变量指向的值,一般称为解引指针
⑦、null指针和未初始化的指针不同。未初始化的指针可能包含任何值,而包含NULL的指针则不会引用内存中的任何地址。

int* p = 0;		//这里0代表null指针NULL
*p = 0;			//这里0代表整数0

⑧、指针被声明为全局或静态,就会在程序启动时被初始化为NULL。
⑨、指针类型和所指向类型:自身类型即是声明时除变量名以外的便是自身类型,例如:int* p;指针自身类型是int *。所指向类型即是除变量名以及最近的*以外的便是指向类型,例如:int* p所指向类型即是int;int** p所指向类型即是int*
⑩、倒着读更好理解指针:
在这里插入图片描述

1.2指针的长度和类型

①、声明诸如字符数或者数组索引这样的长度变量时用size_t是好的做法。typedef unsigned int size_t;
②、sizeof操作符可以用来判断指针长度。sizeof(char*)
(16位指针变量都是2字节,32位都是4字节,64位都是8字节,同系统数据指针的长度一样,理解成地址即可。但是指针变量+1,指针的值递增所指向类型的大小(单位字节),例如int就是地址+4)。
③、函数指针的长度是可变的。

??1.2.1函数指针&指针函数

详解会在解读(二)指针&函数、数组、结构体、字符串

/*函数指针是指向函数的指针变量,其本质是一个指针变量*/
int f();
int main(){
	int (*p)();   		//*p就是一个函数指针
	p=f;				//将函数f的首地址赋值给指针p
}

/*指针函数是指带指针的函数,本质是一个函数*/
int *p(x,y);   			//返回值是int*类型

??1.2.2数组指针&指针数组

详解会在解读(二)指针&函数、数组、结构体、字符串

/*指针数组的本质是数组,内容是指针(地址)*/
int* p[3];  		//p是数组,内容是三个指向int类型的指针

/*数组指针的本质是指针*/
int (*p)[3];     	//因为写法规定,因此比较容易混淆,其实就是一个指向一个3个int类型数据的数组的指针变量。
int arr[3];
p = &arr;

:指针数组的内容可以是字符串,因为字符串是一个以首字母地址开头,‘\0’地址结束的char类型数组,因此字符串可等同于指针使用。

1.3指针操作符

指针操作符①、指针加上整数:实际=指针+该整数*指针指向类型字节数。减去同理

int* p = 100;	//100是地址
p += 2; 		//p=100+2*4 ,int是4字节大小

②、一个指针减去另一个指针会得到两个地址的差值。这个差值通常没什么用,但可以判断数组中的元素顺序。
③、指针可以用标准的比较操作符来比较。通常,比较指针没什么用。然而,当把指针和数组元素相比时,比较结果可以用来判断数组元素的相对顺序。例如指针变量p0>p1,为真为1。

1.4指针的常见用法

①多层间接引用。(参考上面指针数组的解读)
多层间接引用

??1.4.1常量&指针

原文非常绕,感兴趣去wx读书自己找过来看看,简单举例就是如下:
常量&指针

/* 指向常量类型数据的指针 */
const int *pci;       //pci可以赋值,但是*pci不能

/* 指向非常量的常量指针 */
int *const ipc=&addr; //ipc不能赋值,但是*ipc可以

/* 指向常量的常量指针:很少派上用场。这种指针本身不能修改,它指向的数据也不能通过它来修改。*/
const int * const cipc = &addr;

/* 指向“指向常量的常量指针”的指针 ....(╯‵□′)╯︵┻━┻*/
const int * const cipc = &addr; 
const int* const* cipcp;  
cipcp = &cipc ;  //就是套娃

二、C的动态内存管理

2.1 动态内存分配

①、在C中动态分配内存的基本步骤有:
??(1) 用malloc类的函数分配内存;
??(2) 用这些内存支持应用程序;
??(3) 用free函数释放内存。

int *p=(int *)malloc(sizeof(int));  //分配一个int大小的内存
*p = 5;     //将分配的内存赋值
/*
int *p;
*p=(int *)malloc(sizeof(int));
上面这种用法是错误的,分配内存时不应该解引指针,正确如下:
p=(int *)malloc(sizeof(int));
*/
free(p); 

每次调用malloc(或类似函数),程序结束时必须有对应的free函数调用,以防止内存泄漏。最好总是把被释放的指针赋值为NULL以防万一。

②、内存泄漏:内存泄漏的一个问题是无法回收内存并重复利用,堆管理器可用的内存将变少。
导致内存泄漏的可能:
??(1) 丢失地址:
丢失地址如上图的例子,这里之所以会丢失地址,是因为当指针最后指向’\0'时,NUL字符没有真实地址,从而丢失了起始地址。
??(2)隐式内存泄漏:如果程序应该释放内存而实际却没有释放,也会发生内存泄漏。在释放用struct关键字创建的结构体时也可能发生内存泄漏。如果结构体包含指向动态分配的内存的指针,那么可能需要在释放结构体之前先释放这些指针

2.2 动态内存分配函数

①、stdlib.h头文件中一般有以下几种操作动态内存的函数:
动态内存分配动态内存从堆上分配,分配的内存会根据指针的数据类型对齐,比如说,4字节的整数会分配在能被4整除的地址边界上。堆管理器返回的地址是最低字节的地址。

??2.2.1malloc函数

②、malloc函数:从堆上分配一块内存,所分配的字节数由该函数唯一的参数指定,返回值是void指针,如果内存不足或因其他原因无法分配内存,就会返回NULL,因此建议每次先判断返回值是否是NULL。(void *malloc(size_t )
??(1) 从堆上分配内存;
??(2) 内存不会被修改或是清空;
??(3) 返回首字节的地址。

a、可以将void指针赋值给其他任何指针类型,所以将malloc返回值进行显式类型转换不是必要的,但出于可读性考虑可以进行显示类型转换
b、声明指针之后,如果没有在使用前对指向的地址进行内存分配,该指针里会包含一些未知数据,尽管不会报错。
c、注意参数的正确,要是unsigned类型整数(size_t),进行数据类型分配字节数时,最好用sizeof()
d、在编译器看来,作为初始化操作符的=和作为赋值操作符的=不一样。 因此初始化静态或全局变量时不能直接调用函数。但可以分成两个语句分配,如下:

static int* p;
p = (int*)malloc(sizeof(int));

*重要
C语言中是不可以直接给指针赋值的,需要先申请一块内存,才可以给指针赋值。但字符串可以直接赋值。如下例子:注意p*p
同时根据下面实验可以验证,字符串表达式使用上相当于地址。
一个字符串常量生成的时候就会先申请常量区内存,然后在末尾加上’\0’,最后返回地址,所以直接赋值时相当于赋值了返回的地址

/*********  不分配内存  *************/
	int *p;
	*p =5;
	printf("p:%d",*p);
	/*编译结果:什么也没有(不可以直接赋值但可以取前面声明过的变量的地址)*/
/*********  分配内存  *************/
	int *p;
	p=(int*)malloc(4);
	*p =5;
	printf("p:%d",*p);
	/*编译结果:p:5*/
/*********  strcpy直接复制字符串  *************/
	char *p;
	strcpy(p,"012");
	printf("*p:%s",p);
	/*编译结果:什么也没有*/
/*********  strcpy先分配内存再复制字符串  *************/
	char *p;
	p=(char*)malloc(4);
	strcpy(p,"012");
	printf("*p:%s",p);
	/*编译结果:*p:012*/
/*********  直接字符串赋值给p  *************/
	char *p;
	p="012";
	printf("*p:%s",p);
	/*编译结果:*p:012*/

??2.2.2calloc函数

③、calloc函数:会在分配的同时清空内存,将其内容置为二进制0。根据numElementselementSize两个参数的乘积来分配内存,并返回一个指向内存的第一个字节的指针。如果不能分配内存,则会返回NULL。此函数最初用来辅助分配数组内存。
void *calloc(size_t numElements,size_t elementSize);
不用calloc的话,用malloc函数和memset函数可以得到同样的结果:

/*memset函数会用某个值填充内存块。
第一个参数是指向要填充的缓冲区的指针,
第二个参数是填充缓冲区的值,
最后一个参数是要填充的字节数。*/
int *p = (int *)malloc(5*sizeof(int));
memset(p,0,5*sizeof(int));

??2.2.3realloc函数

④、realloc函数:realloc函数会重新分配内存。realloc函数返回指向内存块的指针。该函数接受两个参数,第一个参数是指向原内存块的指针,第二个是请求的大小。
void *realloc(void* p,size_t size);
realloc相关参数行为:
realloc行为这里着重说一下第二个参数比原内存块小的情况,如果没有释放内存,堆管理器可以重用原始的内存块,且不会修改其内容
参考如下示例:重新分配的内存范围更小,但是也能正常输出,但正常使用的时候应该避免这种情况。

	char *s1;
    char *s2;
    s1 = (char *)malloc(5);
    strcpy(s1,"0123");
    s2 = realloc(s1,3);
    printf("s1_val:%s,\ts1_addr:%p\n\r",s1,s1);
    printf("s1_val:%s,\ts1_addr:%p\n\r",s2,s2);
    /*编译结果:
s1_val:0123,     s1_addr:0000000000981400
s1_val:0123,     s1_addr:0000000000981400
*/

??2.2.4alloca函数和变长数组

⑤、alloca函数:(微软为malloca)在函数的栈帧上分配内存(堆上内存手动分配,栈上内存自动分配)。函数返回后会自动释放内存。若底层的运行时系统不基于栈,alloca函数会很难实现,所以这个函数是不标准的,如果应用程序需要可移植就尽量避免使用它。
C99引入了变长数组(VLA),允许函数内部声明和创建其长度由变量决定的数组。
VLA的长度不能改变,一经分配其长度就固定了。如果你需要一个长度能够实际变化的数组,那么需要使用类似realloc的函数。参考2.2.3。

2.3 用free函数释放内存

①、free函数可以将不再使用的内存释放返还给系统(void free(void *ptr)
指针参数指向由malloc类函数分配的内存地址,将该地址返还给堆。

像以下示例是不被允许的,因为传入指针所指向的内存不是由malloc类的函数分配的。

int num;
int *p = #
free(p);		  //错误的释放行为		

:应该在同一区块进行内存的分配和释放。

②、free函数释放后仍可能包含原值,这种情况叫迷途指针,因此可以在释放后,将已释放的指针赋值为NULL指针

③、重复释放是指两次释放同一块内存。像释放同名指针或者指向统一内存的指针都会发生运行异常。

④、堆一般利用操作系统的功能来管理内存。堆的大小一般不会改变,就算调用free函数也不一定将分配的内存归还系统,开辟的内存空间可以理解成被应用程序重复使用。
⑤、程序结束前释放内存的优劣(是否要在程序终止前释放内存取决于具体的应用程序):
(1)、内存损坏可能导致程序停止运行,这种情况也没必要在程序结束前释放内存。
(2)、如果不释放,内存会被占用,程序一多内存就会越来越少,可能导致申请不到内存或者系统变慢。
(3)、释放内存可能会很耗时间。
??…
、内存泄漏:
程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。

2.4 迷途指针

①、如果内存已经释放,而指针还在引用原始内存,这样的指针就称为迷途指针。

??2.4.1 迷途指针示例

②、在free释放内存后,再次对该位置赋值,系统不会阻止该行为,但结果将不可预期。

	int *p=(int *)malloc(sizeof(int));  
	*p=5;
	free(p);
	*p=10;   //已经释放了就不能用这块内存了,p变成迷途指针

③、多个引用同一内存的指针,现在free释放掉其中一个指针,则继续操作其他指针也将变成迷途指针。

	int *p1=(int *)malloc(sizeof(int));  
	int *p2;
	*p1=5;
	p2=p1
	free(p1);
	*p2=10; //该指针成为迷途指针

④、块语句中声明的变量会在块语句结束时出栈,如果该变量赋值给一个指针变量,那该指针也会变成迷途指针。

int *p;
...
{
	int i=5;
	p=&i;
}
//大部分编译器把语句块当成一个栈帧,里面的变量出了语句块就会一起出栈

⑤、处理迷途指针:
(1)释放后将指针置为NULL指针
(2)有些系统会覆写已释放的内存。
(3)第三方工具。
(4)写新函数代替free。

2.5 动态内存分配技术

①、释放的内存称为垃圾,但是C语言不像java和C++一样有垃圾回收技术,需要我们手动收集。也可以借助Boehm GC来自动释放内存(用GC_MALLOC取代malloc函数分配内存,不用编程者写free释放内存)。

②、资源获取即初始化(RAII)RAII_VARIABLE

/***
@desc:声明一个变量,然后给变量关联如下属性
@param:
	类型
	创建变量时执行的函数
	变量超出作用域时执行的函数
	***/
	/* 分配内存然后一旦变量超出作用域会自动触发释放 */
RAII_VARIABLE(char*,name,(char*)malloc(32),free);

③、使用异常处理函数

int *p=NULL;
__try{
	p=(int *)malloc(sizeof(int));  
	*p=5;
}
__finally{
	free(p);   //不管try中有没有异常都会执行finally,就是说一定会释放
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-21 21:19:47  更:2022-07-21 21:20:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/11 0:19:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码