目录
第一章 认识指针
1.1指针和内存
- 静态/全局内存
静态声明的变量分配在这里,全局变量也使用这部分内存。这些变量在程序开始时分配,知道程序终止才消失,所有函数都能访问全局变量,静态变量的作用域局限在定义他们的函数内部 - 自动内存
这些变量在函数内部声明,并且在函数被调用时才创建,作用域局限在函数内部,并且声明周期限制在函数的执行时间内 - 动态内存
内存分配在堆上,可以根据需要释放,而且直到释放才消失。指针引用分配的内存,作用域局限于引用内存的指针。
总结
作用域 | 神明周期 | |
---|---|---|
全局内存 | 整个文件 | 应用程序的生命周期 |
静态内存 | 声明它的函数内部 | 应用程序的生命周期 |
自动内存 | 申明它的函数内部 | 限制在函数执行时间内 |
动态内存 | 由引用该内存的指针决定 | 知道内存释放 |
1.1.1为什么要精通指针
指针就是变量,对象或函数的地址
对象就是内存分配函数分配的内存
指针可以写出快速高效的代码是因为更接近硬件,编译器可以更容易把操作翻译成机器码
指针开销不想其他操作符那样大
c的动态内存分配实际上就是通过使用指针来实现。可以malloc与free函数实现分配和释放内存
C语言由于更相信程序员,所以给了程序员更多的权限,所以在使用指针过程中可能会发生很多问题
- 越界
- 自动变量消失后被引用
- 对上分配的内存释放后引用
- 内存分配前解引指针
1.1.2 声明指针
通过在数据类型后面跟星号,再加上指针变量的名字可以声明
星号两边的空白符无关紧要(下列几个都是合规的)
int *c;
int* c;
int*c;
int * c;
指向未初始化内存的指针可能会产生问题,如果将这种指针解引用,指针的内容可能并不是一个合法的地址,就算地址合法,这个地址里面也没有合法的数据。
记住几点
- int*p,p的内容最终应该赋值为一个整数变量的地址
- 这些变量没有被初始化,所以包含的是垃圾值
- 指针也有类型,如果没有正确使用,编译器会警告
- 指针的实现没有内部信息表明自己指向的是什么类型的数据或内容是否合规
1.1.3如何阅读声明
1.1.4地址操作符
定制操作符&会返回操作数的地址
int *pi;
num = 0;
pi = #
//pi是指向num的地址
内存赋值错误案例
//error
int num;
int *pi = #
1.1.5打印指针的值
略
其中的虚拟内存和指针更多需要看操作系统和组成原理
1.1.6用间接引用操作符解引指针
*(间接引用操作符),一般成为解引用指针
int num = 4;
int *p = #
printf("%d\n",*p);
1.1.7 指向函数的指针
指针可以声明为指为函数
void(*foo)();
1.1.8null的概念
这部分有点碎有点乱
- null概念
- null指针常量
- NULL 宏
- ASCII 字符NULL
- null 字符串
- null语句
NULL 被赋值给指针就意味着指针不知想任何东西。null概念是指指针包含了一个特殊的值,和别的指针不一样,他没有指向任何内存区域。
//NULL 宏是强制类型转换为void 指针的整数常量0
#define NULL
//这种定义一般再很多头文件中可以找到,包括stddef.h stdlib.h stdio.h
if(pi){
//pi is not NULL
}
else{
//pi is NULL
}
用不用NULL
使用指针时哪一种形式更好,NULL 还是0?
无论那种形式都是没有问题的,选择只是个人爱好
0的含义随着上下文的变化而变化,有时候可能是整数0,有时候可能是null指针
void指针
void指针是通用指针,用来存放任何数据类型的引用。
两个性质
- void指针具有与char指针相同的形式和内存对齐方式
- void指针和别的指针永远不会相等,不过两个赋值为NULL 的void指针相等。
void指针只有用作数据指针,不能用作函数指针
全局和静态指针
偷懒了
1.2指针的长度和类型
大多数平台,数据指针的长度通常是一样的,与指针类型无关,char指针和结构体指针长度相同。
1.2.1内存模型
64为机器的出现导致为不同的数据类型分配的内存在长度上差异变得明显。
1.2.2指针相关的预定义类型
使用指针时经常用到一下四种预定义类型
size_t 用于安全的表示长度
ptrdiff_t 用于处理指针算数运算
intptr_t和uintptr_t 用于存贮指针地址
- 理解size_t
表示c中任何对象所能达到的最大长度。它的目的是提供一种可以指的方法来声明与系统中可寻址的内存区域一致的长度
size_t是正整数其定义
- 对指针使用sizeof操作符
- 使用intptr_t和uintptr_t
1.3指针操作符
1.3.1指针算数运算
数据指针可以执行以下集中运算
-
给指针加上整数
要注意超出数组的范围的内存很危险啊
-
从指针减去整数
-
两个指针相减
-
比较指针
1.4 指针的常见用法
1.4.1 多层间接引用
略
1.4.2常量与指针
-
指向常量的指针
可以将指针定义为指向常量,这意味着不能通过指针修改它所引用的值 -
指向非常量的指针
-
指向常量的常量指针
-
指向“指向常量的常量指针”的指针
主要意思就是多层套娃,关于const位置的关系,什么时候能够修改什么时候不能修改
第二章 c的动态内存管理
2.1 动态内存分配
在c中动态分配内存的基本步骤有:
- 用malloc类的函数分配内存
- 用这些内存支持应用程序
- 用free函数释放掉内存
int *pi = (int *)malloc(sizeof(int));
*pi = 5;
printf("*p = %d",*pi);
free(pi);
下列图片是内存的内配
关于malloc的后面大小用sizeof更好,这样更有移植性,可能不同的机器是不同的位数,数据的长度会发生变化,这样不管哪里程序都能返回正常长度。
malloc用过之后要用free掉
分配内存时候,对管理器维护的数据结构中会保留额外的信息。这些信息包括块大小和其他的一些东西,通常放在紧挨着的分配快的位置。如果应用程序的写入操作超出了这块内存,数据结构会被破坏。
char *p = (char*)malloc(sizeof(int));
for(int i = 0;i<8;i++){
*p[i]=0;
}
这里把分配6个字节内存的额外内存给赋值为零,这样就改变了原来的内存值。
内存泄漏
如果已经不在使用的内存没有释放掉过多就会导致内存泄漏。如果分配的内存不断给与,但不释放那么就可能会导致内存空间被用完,这样极端情况下会导致系统崩溃
- 丢失内存地址
- 应该调用free函数却没有调用
- 丢失地址
int *p = (int*)malloc(sizeof(int));
*p = 5;
......
pi =(int *)malloc(sizeof(int));
这里是pi被赋予给一个新地址时候,旧地址没有被释放就丢失
char *name = (char *)malloc(sizeof("Susan")+1);
strcpy(name,"susan");
while(*name!= 0){
printf("%c",*name);
name++;
- 隐式内存泄漏
如果程序应该释放内存而实际上没有释放掉,也会发生内存泄漏。如果我们不在需要某个对象但它依然保存在堆上,就会发生隐式内存泄漏,一般这是程序员忽视所致的。最差的情况是,对管理器可能都无法按需分配内存,程序不得不终止。最好的情况下是我们持有了不必要的内存。
在释放使用struct关键字的时候创建的结构体也有可能发生内存泄漏。如果结构体包含指向动态分配的内存指针,那么坑你需要在释放结构体之前先释放掉这些指针。
2.2动态内存分配函数
stdlib.h头文件的函数
- malloc
- realloc
- calloc
- free
动态内存从堆上分配,至于一连串内存分配调用,系统不保证内存的顺序和所分配的内存的连续性。不过,分配的内存会根据指针的数据类型对齐。
2.2.1使用malloc函数
------------------------待续