C 语言学习笔记 08 - 指针基础

简介

指针实际上也可以是一种类型。在先前我们曾使用过变量的地址,而变量地址的类型就是指针:

int *p = &a;

指针储存着该变量的地址,用于指向此变量。在原类型名前加上 *,即为相应的指针类型。

但星号符置于变量之前却有不同表现。如下:

int *p = &a;

*p // 实际上就是 a, 这里 * 作为间接引用符

由于指针的本质也是一个值,因此我们还可以多重取指针,例如 int **pp = &p

只读指针与只读变量指针

我们定义以下指针:

int a;
int *const cp = &a; // 只读指针, 变量可修改
int const *const = &a; // 只读变量指针, 二者都不可修改
int const * cp2 = &a; // 普通常量,修饰前面的变量类型

一般来说,建议由后向前阅读类型,这样便于我们识别一个指针的类型。

例如 cp 即为 const 的指针指向了一个整型变量,ccp为一个 const 的指针指向了 const 的整型。

因此,不能通过 *ccp = ...; 直接修改 ccp 的变量,也不能通过 ccp = &...; 修改指针。 同样的,也不能使用 cp = &..; 修改其指针。

特殊指针

由于指针的值实际上为内存地址,因此我们也可以直接给一个指针类型的变量赋值类型。这样做 IDE 并不会显式报错,而编译器也仅会给出一个类型不匹配的警告,我们可以通过强转类型来绕过这个警告。

int *p = (int *)100; // 绕过编译器的警告

但这样操作完也并不代表使用这个指针就安全了,如果我们尝试打印上述指针,会得到一个报错。

倘若我们从上次 Debug 中手动获取一个地址并填入,这样的指针行吗?显然,在运行时中内存地址也不是唯一的,因此也会得到报错。但是先前描述的是在 MSVC 上的行为,MinGW 行为却不一样。因为 GCC 的内存空间非常稳定,进行上述修改并不会得到报错,这与 GCC 的特性有关。我们只在特殊情况下写死内存地址,一般不推荐硬编码内存地址。

C 中也存在一个常用的 “硬编码” 的指针,NULL。使用 CLion 的 lookup 功能,我们可以发现 NULL 实际上就是一个 0 的地址,如下所示。

#define NULL ((void *)0)

0 是一个特殊的地址,任何人都可以访问,用于声明这个指针不指向任何值,它并不指向一个特定的内存,可以用于给未初始化的指针赋值。

野指针

顾名思义,这个指针比较野。它指向的值不可控,可能产生危险指针。

int *large_pointer;

void DangerousPointer() {
    int a = 2;
    large_pointer = &a;
}

我们定义了一个全局指针,但在这个方法执行完过后,a 就无效了,最终导致 large_pointer 指向一个不存在的内存地址。

这时候我们使用该内存地址会导致不可控的结果,因此,我们应及时处理失效的指针并设为 NULL。如下所示:

int *large_pointer;

void DangerousPointer() {
    int a = 2;
    large_pointer = &a;

    // do something....

    large_pointer = NULL; // 处理野指针
}

总结

  1. 不要为一个指针赋值硬编码,除非你知道你在干什么
  2. 善用空指针 NULL,用于判断指针是否可用
  3. 避免产生野指针引发不可控的问题
comments powered by Disqus