Self Driven

兴趣是1,坚持是剩下的99

0%

《深入理解C指针》 Chap1&2

Chap 1 认识指针

1.1 指针与内存

C使用内存的方式有三种:

  1. 静态/全局内存
    变量在程序开始时分配,知道程序终止时被清除. 全局变量均可访问,静态变量则只在函数定义域内可以.
  2. 自动内存
    在函数内部声明,且在函数被调用时才创建.仅限函数调用期间可使用和生存.
  3. 动态内存
    分配在堆上的内存.可以根据需要来分配和释放.

如何阅读一个指针变量声明?
const int* pi 一个指向整型常量的指针变量
试着从右往左去理解.

1.1.8 关于NULL

一个自定义的宏

1
#define NULL ((void*)0) 

可以使用这种方式来决定循环是否退出:

1
2
3
4
int* pi...
if (pi) {
} else {
}

void指针: 通用指针.用来存放任何类型的指针引用.
两个有趣的性质(?):

  1. void指针具有与char指针相同的形式和内存对齐方式
  2. void指针和别的指针永远不会相等,除非都是为NULL的void指针.

任何指针都可以被赋给void指针,它也可以被转换回原来的指针类型.
void指针只能做数据指针,而不能用做函数指针.

1.2.2 size_t

size_t是C中任何对象所能达到的最大长度,它是一个无符号整数

1
typedef unsigned int size_t

要特别注意循环中的情况:

1
2
3
for (size_t i = 10; i > 0; i--) {
...
}

这个循环永远不会退出. 一般不会这么用错,但是要注意 ** strlen() sizeof() 返回的都是size_t**

1.3.1 指针算数运算

指针的加法: 内存地址偏移 = 对应整数 * 指针数据类型对应字节数
减法同理.
不过要注意void指针的情况–不建议对其进行算数运算(不过地址也可能偏移就是了,偏移量会是4字节(32位机器))

1.4.1 多层间接引用

1
2
3
char *title[] = {"111", "222", "333"};
//大致上跟多维数组类似. 只是如果是多维数组会略麻烦,后面那维是必须给出-这跟C的数组表示形式有关-c多维数组在内存空间上是‘一维’的
char title[][3] = {...};

1.4.2 常量和指针

1
2
3
4
5
6
7
8
9
int num = 10;
const int limit = 5;
//只是限制不可以通过pi修改对应的值;pi可以指向另外一个变量
const int* pi = &limit; // 指向常量的整型指针---实际指向的可以不是常量;←_←他觉得自己就是指向常量的(虽然实际上它可以不是)
int const* pi = &limit; // 跟上面等价
//指向常量会报错,因为这里可以赋值.
int* const pi; //指向非常量的常量指针. pi必须指向非常量,pi不可以指向其他变量
const int* const pi; // 指向常量的常量指针,似乎没多大意义
const int* const *pi; // 指向"指向常量的常量指针"的指针= =

Chap2 C的动态内存管理

2.1 动态内存管理

  • C的动态内存管理: 管理的对象即从堆上分配的内存.通常是手动分配和释放的过程.(c上也存在gc的实现,只是非标准)

动态分配内存的步骤:

  1. 使用malloc类的函数分配内存,返回void指针
  2. 使用返回的内存
  3. 使用free释放对应的内存

内存泄露

  • 丢失内存地址–分配了内存,但是无法使用. 常见于在函数中分配内存,但是未正确返回创建的地址.
    在这个例子中,name做完循环之后就指向了原本的尾巴.前面’test’所占的内存无法利用,也无法释放.(这种情况,建议放到函数中来做)

    1
    2
    3
    4
    5
    6
    char* name=(char*)malloc(sizeof("test")+1);
    strcpy(name, "test");
    while (*name != 0) {
    printf("%c", name);
    name++;
    }
  • 未调用frre–也就是忘记释放该释放的内存.无法使用对应的内存,堆可用空间越来越少.

2.2 动态内存分配函数

分配的内存会根据指针的数据类型对齐. 如4字节的整数会被分配在能被4整除的边界上.

  1. malloc(size_t) 在堆上直接分一块内存.返回void指针,如果分配失败返回NULL(以下同理). 这个函数不会修改内存的数据,可以认为分配的内存中包含脏数据.
    Warning:全局和静态变量不可以在初始化的时候调用malloc分配内存,该操作需要放在函数中执行
  2. calloc(size_t numEle, size_t eleSize) 分配的同时清空内存.
    1
    2
    3
    4
    int *pi = (int*) calloc(5, sizeof(int));
    //等价 不过一般情况下calloc可能会比malloc慢
    int *pi = (int*) malloc(sizeof(int) * 5);
    memset(pi, 0, sizeof(pi));
  3. realloc(void* ptr, size_t)重新分配内存.
  • 如果分配的大小小于原本则裁剪
    
  • 如果分配的大小大于原本,则先在本地试着增加,否则就另找一块足够
    
  • 大的空间来进行分配,并复制到新区域.
    
  • 如果为0, 则释放原本的内存块(类似于free)
    

2.3 关于free

重复调用free会导致错误(将一块已经归还系统的内存再归还是不可能的~)
将已释放的指针赋值为NULL,以防止迷途指针.

2.4 迷途指针

如果内存已经释放,而指针仍然还在引用原本的内存-也就是野指针,迷途指针(过早释放).
可能造成的问题:

  1. 如果通过它访问内存,则行为不可预期
  2. 如果内存不可访问,则是段错误
  3. 潜在的安全隐患

常见的错误:

  1. 释放指针后仍然对指定地址进行修改;
  2. 指针别名–其中一指针释放了内存,但是另外一别名仍然继续访问.

Ku: 跟Java不同的一种内存泄露现象.考虑下面的代码:

1
2
3
int *pi;
{ int tmp = 5; pi = &tmp; }
foo();

在外面声明一个变量,然后在另一代码块中赋值,在Java中似乎可以接受.但在这里, pi指向的是tmp这一变量,而tmp是只在该栈帧中存在的,在大括号结束之后,tmp所在栈帧就出栈了.调用foo()导致入栈,那么pi指向的就是foo()中的某块不知名内存. 也就成为了野指针.