Chap 1 认识指针
1.1 指针与内存
C使用内存的方式有三种:
- 静态/全局内存
变量在程序开始时分配,知道程序终止时被清除. 全局变量均可访问,静态变量则只在函数定义域内可以. - 自动内存
在函数内部声明,且在函数被调用时才创建.仅限函数调用期间可使用和生存. - 动态内存
分配在堆上的内存.可以根据需要来分配和释放.
如何阅读一个指针变量声明?
const int* pi 一个指向整型常量的指针变量
试着从右往左去理解.
1.1.8 关于NULL
一个自定义的宏
1 |
可以使用这种方式来决定循环是否退出:
1 | int* pi... |
void指针: 通用指针.用来存放任何类型的指针引用.
两个有趣的性质(?):
- void指针具有与char指针相同的形式和内存对齐方式
- void指针和别的指针永远不会相等,除非都是为NULL的void指针.
任何指针都可以被赋给void指针,它也可以被转换回原来的指针类型.
void指针只能做数据指针,而不能用做函数指针.
1.2.2 size_t
size_t是C中任何对象所能达到的最大长度,它是一个无符号整数
1 | typedef unsigned int size_t |
要特别注意循环中的情况:
1 | for (size_t i = 10; i > 0; i--) { |
这个循环永远不会退出. 一般不会这么用错,但是要注意 **
strlen()
和sizeof()
返回的都是size_t**
1.3.1 指针算数运算
指针的加法: 内存地址偏移 = 对应整数 * 指针数据类型对应字节数
减法同理.
不过要注意void指针的情况–不建议对其进行算数运算(不过地址也可能偏移就是了,偏移量会是4字节(32位机器))
1.4.1 多层间接引用
1 | char *title[] = {"111", "222", "333"}; |
1.4.2 常量和指针
1 | int num = 10; |
Chap2 C的动态内存管理
2.1 动态内存管理
- C的动态内存管理: 管理的对象即从堆上分配的内存.通常是手动分配和释放的过程.(c上也存在gc的实现,只是非标准)
动态分配内存的步骤:
- 使用malloc类的函数分配内存,返回void指针
- 使用返回的内存
- 使用free释放对应的内存
内存泄露
丢失内存地址–分配了内存,但是无法使用. 常见于在函数中分配内存,但是未正确返回创建的地址.
在这个例子中,name做完循环之后就指向了原本的尾巴.前面’test’所占的内存无法利用,也无法释放.(这种情况,建议放到函数中来做)1
2
3
4
5
6char* name=(char*)malloc(sizeof("test")+1);
strcpy(name, "test");
while (*name != 0) {
printf("%c", name);
name++;
}未调用frre–也就是忘记释放该释放的内存.无法使用对应的内存,堆可用空间越来越少.
2.2 动态内存分配函数
分配的内存会根据指针的数据类型对齐. 如4字节的整数会被分配在能被4整除的边界上.
malloc(size_t)
在堆上直接分一块内存.返回void指针,如果分配失败返回NULL(以下同理). 这个函数不会修改内存的数据,可以认为分配的内存中包含脏数据.
Warning:全局和静态变量不可以在初始化的时候调用malloc分配内存,该操作需要放在函数中执行calloc(size_t numEle, size_t eleSize)
分配的同时清空内存.1
2
3
4int *pi = (int*) calloc(5, sizeof(int));
//等价 不过一般情况下calloc可能会比malloc慢
int *pi = (int*) malloc(sizeof(int) * 5);
memset(pi, 0, sizeof(pi));realloc(void* ptr, size_t)
重新分配内存.
如果分配的大小小于原本则裁剪
如果分配的大小大于原本,则先在本地试着增加,否则就另找一块足够
大的空间来进行分配,并复制到新区域.
如果为0, 则释放原本的内存块(类似于free)
2.3 关于free
重复调用free会导致错误(将一块已经归还系统的内存再归还是不可能的~)
将已释放的指针赋值为NULL,以防止迷途指针.
2.4 迷途指针
如果内存已经释放,而指针仍然还在引用原本的内存-也就是野指针,迷途指针(过早释放).
可能造成的问题:
- 如果通过它访问内存,则行为不可预期
- 如果内存不可访问,则是段错误
- 潜在的安全隐患
常见的错误:
- 释放指针后仍然对指定地址进行修改;
- 指针别名–其中一指针释放了内存,但是另外一别名仍然继续访问.
Ku: 跟Java不同的一种内存泄露现象.考虑下面的代码:
1
2
3 int *pi;
{ int tmp = 5; pi = &tmp; }
foo();在外面声明一个变量,然后在另一代码块中赋值,在Java中似乎可以接受.但在这里, pi指向的是tmp这一变量,而tmp是只在该栈帧中存在的,在大括号结束之后,tmp所在栈帧就出栈了.调用foo()导致入栈,那么pi指向的就是foo()中的某块不知名内存. 也就成为了野指针.