【在主画面加入捷径】
       
【选择语系】
繁中 简中

[C 语言] 程序设计教学:指针 (Pointer) 和内存管理 (Memory Management)

【赞助商连结】

    指针 (pointer) 是 C (或 C++) 中用来管理内存的语法特性,指针内储存的值 (value) 是内存的虚拟位置 (virtual address)。一般学 C (或 C++) 时产生的阴影一部分就是来自于指针,但指针其实并不是什么洪水猛兽,使用一阵子就会自然而然地习惯其使用方式。

    前言

    由于指针有许多的使用情境,我们将其拆开在多篇文章中:

    有需要的读者可以前往观看。

    C 语言的内存管理模式

    了解 C 语言中内存管理的模式,也会对使用指针有帮助。在 C 语言中,内存管理有三种层次:

    • 静态内存管理 (static memory management)
    • 自动内存管理 (stack memory management)
    • 手动内存管理 (heap memory management)

    静态配置内存适用于程序代码本身、静态变量 (static variable)、全局变量 (global variable) 等。虽然静态内存管理不需人为介入,但只能用于固定值的变量,而且有大小的限制,故无法适用于所有的情境。此外,在程序中滥用全局变量也是不良的习惯。

    自动配置内存的值会随着函式的生命周期结束而消失,在主函式以外的函式中使用局部变量 (local variable) 时皆需考虑这项特性。虽然自动配置内存不需人为介入,但局部变量无法跨函式传递,而且局部变量同样有大小的限制,故不适用于所有的情境。

    手动配置内存的大小约略等于系统的内存大小,也就是说,可以充分利用系统资源。此外,使用手动配置的局部变量可跨函式传递,在实际应用上会方便地多。但手动配置的内存之后需要手动释放掉,这就是我们要学习的地方。

    声明和使用指针

    以下的例子使用到 stack memory,所以不需手动释放记体体:

    #include <assert.h>
    #include <stdlib.h>
    
    int main(void)
    {
        // An automatic variable using stack memory.
        int i = 3;
        
        // Declare a pointer to int.
        int *i_p = NULL;
        
        // Point to the address of i.
        i_p = &i;
        assert(*i_p == 3);
        
        // Change the value of `i` through `i_p`
        *i_p = 5;
        assert(i = 5);
        
        return 0;
    }

    首先,我们声明并指派变量 i,程序已经配置好一块 stack memory。

    接着我们声明一个指向整数的指针 i_p,声明的方式是在变量旁加上星号 *;以本例来说,*i_p 是一个指向整数的指针。如果声明指针时没有要将该指针指向某个位址,最好先将其值设为 NULL

    在本例中,我们将 i_p 的位址指向 i 所在的位址,对变量 i 前加上 & 可以取得其位址。

    我们透过指针 i_p 间接修改 i 的值,并用断言确认 i 有改到。

    细心的读者可以发现,本例没有引入 stdlib.h 但程序可正常运作。可知指针和内存配置函式不一定要挂勾。如果我们用 Valgrind 检查此程序,可发现此程序没有从 heap 分配内存,也没有造成内存泄露。

    像以下的例子有配置 heap memory,就需手动释放:

    #include <stdlib.h>
    
    int main()
    {
        // Allocate a chunk of heap memory.
        int *i_p = malloc(sizeof(int));
        if (!i_p)
            return 1;
        
        // Free i_p.
        free(i_p);
        
        return 0;
    }

    配置内存的函式为 malloc,配置时需传入记体体的大小,通常是搭配 sizeof 来取得某个类型的大小。平常练习时 malloc 大多都会成功,但其实 malloc 有可能会失败,最好还是加入错误检查的程序代码。在本例中,当 malloc 失败时就中止程序,并回传程序失败的状态码。释放内存的函式为 free,将指针传入即可释放内存。处理内存的函式位于 stdlib.h函数库中。

    mallocfree 应当成对出现,每当有用 malloc (或其他同质函式) 配置 heap memory,就要于后续的程序中搭配 free 来释放内存。

    如果我们用 Valgrind 检查此程序,可发现此程序有从 heap 配置内存,但已释放了,没有造成内存泄露。

    小整理

    C 语言中指针相关的运算符有两种:

    • &
    • *

    & 的意义皆为取址 (address of value)。

    在声明变量时加上 * 代表声明该变量为指针,在其他地方使用 * 则是对该指针变量取值 (value of address)。

    void 指针

    void 指针 (void *) 可指向任意指针类型,像是 free函数的参数即为 void 指针。利用 void 指针可在 C 语言中仿真泛型程序的特性。

    延伸阅读

    如果想要深入学习指针,可以阅读 Understanding and Using C Pointers, O’Reilly (2013)

    【赞助商连结】