- 一、认识指针
- 1.1指针和内存
- 1.2指针的长度和类型
- 1.2.1函数指针&指针函数
- 1.2.2数组指针&指针数组
- 1.3指针操作符
- 1.4指针的常见用法
- 1.4.1常量&指针
- 二、C的动态内存管理
- 2.1 动态内存分配
- 2.2 动态内存分配函数
- 2.2.1`malloc`函数
- 2.2.2`calloc`函数
- 2.2.3`realloc`函数
- 2.2.4`alloca`函数和变长数组
- 2.3 用free函数释放内存
- 2.4 迷途指针
- 2.4.1 迷途指针示例
- 2.5 动态内存分配技术
解读《深入理解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*。
⑩、倒着读更好理解指针:
①、声明诸如字符数或者数组索引这样的长度变量时用size_t是好的做法。typedef unsigned int size_t;
②、sizeof操作符可以用来判断指针长度。sizeof(char*)
(16位指针变量都是2字节,32位都是4字节,64位都是8字节,同系统数据指针的长度一样,理解成地址即可。但是指针变量+1,指针的值递增所指向类型的大小(单位字节),例如int就是地址+4)。
③、函数指针的长度是可变的。
详解会在解读(二)指针&函数、数组、结构体、字符串中
int f();
int main(){
int (*p)(); /
int *p(x,y); //返回值是int*类型
1.2.2数组指针&指针数组
详解会在解读(二)指针&函数、数组、结构体、字符串中
int* p[3]; //p是数组,内容是三个指向int类型的指针 int (*p)[3]; //因为写法规定,因此比较容易混淆,其实就是一个指向一个3个int类型数据的数组的指针变量。 int arr[3]; p = &arr;
注:指针数组的内容可以是字符串,因为字符串是一个以首字母地址开头,‘ ’地址结束的char类型数组,因此字符串可等同于指针使用。
1.3指针操作符①、指针加上整数:实际=指针+该整数*指针指向类型字节数。减去同理
int* p = 100; //100是地址 p += 2; //p=100+2*4 ,int是4字节大小
②、一个指针减去另一个指针会得到两个地址的差值。这个差值通常没什么用,但可以判断数组中的元素顺序。
③、指针可以用标准的比较操作符来比较。通常,比较指针没什么用。然而,当把指针和数组元素相比时,比较结果可以用来判断数组元素的相对顺序。例如指针变量p0>p1,为真为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; //将分配的内存赋值 free(p);
每次调用malloc(或类似函数),程序结束时必须有对应的free函数调用,以防止内存泄漏。最好总是把被释放的指针赋值为NULL以防万一。
②、内存泄漏:内存泄漏的一个问题是无法回收内存并重复利用,堆管理器可用的内存将变少。
导致内存泄漏的可能:
(1) 丢失地址:
如上图的例子,这里之所以会丢失地址,是因为当指针最后指向’ '时,NUL字符没有真实地址,从而丢失了起始地址。
(2)隐式内存泄漏:如果程序应该释放内存而实际却没有释放,也会发生内存泄漏。在释放用struct关键字创建的结构体时也可能发生内存泄漏。如果结构体包含指向动态分配的内存的指针,那么可能需要在释放结构体之前先释放这些指针。
①、stdlib.h头文件中一般有以下几种操作动态内存的函数:
动态内存从堆上分配,分配的内存会根据指针的数据类型对齐,比如说,4字节的整数会分配在能被4整除的地址边界上。堆管理器返回的地址是最低字节的地址。
②、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
同时根据下面实验可以验证,字符串表达式使用上相当于地址。
一个字符串常量生成的时候就会先申请常量区内存,然后在末尾加上’ ’,最后返回地址,所以直接赋值时相当于赋值了返回的地址。
int *p;
*p =5;
printf("p:%d",*p);
int *p;
p=(int*)malloc(4);
*p =5;
printf("p:%d",*p);
char *p;
strcpy(p,"012");
printf("*p:%s",p);
char *p;
p=(char*)malloc(4);
strcpy(p,"012");
printf("*p:%s",p);
char *p;
p="012";
printf("*p:%s",p);
2.2.2calloc函数
③、calloc函数:会在分配的同时清空内存,将其内容置为二进制0。根据numElements和elementSize两个参数的乘积来分配内存,并返回一个指向内存的第一个字节的指针。如果不能分配内存,则会返回NULL。此函数最初用来辅助分配数组内存。
(void *calloc(size_t numElements,size_t elementSize);)
不用calloc的话,用malloc函数和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相关参数行为:
这里着重说一下第二个参数比原内存块小的情况,如果没有释放内存,堆管理器可以重用原始的内存块,且不会修改其内容。
参考如下示例:重新分配的内存范围更小,但是也能正常输出,但正常使用的时候应该避免这种情况。
char *s1;
char *s2;
s1 = (char *)malloc(5);
strcpy(s1,"0123");
s2 = realloc(s1,3);
printf("s1_val:%s,ts1_addr:%pnr",s1,s1);
printf("s1_val:%s,ts1_addr:%pnr",s2,s2);
2.2.4alloca函数和变长数组
⑤、alloca函数:(微软为malloca)在函数的栈帧上分配内存(堆上内存手动分配,栈上内存自动分配)。函数返回后会自动释放内存。若底层的运行时系统不基于栈,alloca函数会很难实现,所以这个函数是不标准的,如果应用程序需要可移植就尽量避免使用它。
C99引入了变长数组(VLA),允许函数内部声明和创建其长度由变量决定的数组。
VLA的长度不能改变,一经分配其长度就固定了。如果你需要一个长度能够实际变化的数组,那么需要使用类似realloc的函数。参考2.2.3。
①、free函数可以将不再使用的内存释放返还给系统(void free(void *ptr))
指针参数指向由malloc类函数分配的内存地址,将该地址返还给堆。
像以下示例是不被允许的,因为传入指针所指向的内存不是由malloc类的函数分配的。
int num; int *p = # free(p); //错误的释放行为
注:应该在同一区块进行内存的分配和释放。
②、free函数释放后仍可能包含原值,这种情况叫迷途指针,因此可以在释放后,将已释放的指针赋值为NULL指针。
③、重复释放是指两次释放同一块内存。像释放同名指针或者指向统一内存的指针都会发生运行异常。
④、堆一般利用操作系统的功能来管理内存。堆的大小一般不会改变,就算调用free函数也不一定将分配的内存归还系统,开辟的内存空间可以理解成被应用程序重复使用。
⑤、程序结束前释放内存的优劣(是否要在程序终止前释放内存取决于具体的应用程序):
(1)、内存损坏可能导致程序停止运行,这种情况也没必要在程序结束前释放内存。
(2)、如果不释放,内存会被占用,程序一多内存就会越来越少,可能导致申请不到内存或者系统变慢。
(3)、释放内存可能会很耗时间。
…
注、内存泄漏:
程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。
①、如果内存已经释放,而指针还在引用原始内存,这样的指针就称为迷途指针。
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。
①、释放的内存称为垃圾,但是C语言不像java和C++一样有垃圾回收技术,需要我们手动收集。也可以借助Boehm GC来自动释放内存(用GC_MALLOC取代malloc函数分配内存,不用编程者写free释放内存)。
②、资源获取即初始化(RAII)RAII_VARIABLE
RAII_VARIABLE(char*,name,(char*)malloc(32),free);
③、使用异常处理函数
int *p=NULL;
__try{
p=(int *)malloc(sizeof(int));
*p=5;
}
__finally{
free(p); //不管try中有没有异常都会执行finally,就是说一定会释放
}



