在C语言中管理堆

57 阅读4分钟

在C语言中管理堆

内存管理是非常重要的。在谷歌浏览器中,70%的安全漏洞是内存问题。修复内存管理会使程序更加可靠和安全。对此,有两种可能的解决方案。你可以使用一种具有更好更容易的内存管理的语言,或者只是学习如何正确使用内存。我们今天将讨论后者。

堆栈

有两个可以放置内存的地方:堆栈和堆。

在大多数静态类型的编程语言中,堆栈是原始变量的默认位置。堆栈位于内存的前端。它不仅包含变量,而且还包含函数参数和返回地址。将一个变量分配给堆栈很容易。只要创建一个变量。

int stack_num = 5; // this variable is stored in the stack

使用堆栈的好处是,它的速度相对较快。不像cpu寄存器那么快,但仍然很快。使用它只需要CPU的几个周期。但在有些时候,它还是没有用的......

堆(The Heap

什么是 "堆"?

堆在内存的后面,它由操作系统管理。使用这一点是比较麻烦的。你需要使用malloc 函数来告诉操作系统你需要多少内存。

// malloc returns a pointer to the memory address that the OS has allocated to your program
int* heap_num = malloc(sizeof(int)); // use sizeof() to determine the size of a value
*heap_num = 5;

不过现在我们有一个问题。通常情况下,一个变量在它所声明的函数结束时退出范围。但是,除非我们告诉它,否则操作系统永远不会放开这些内存。这台电脑上还有其他程序需要运行,如果我们把这些内存都占为己有,那就太自私了。我们需要使用free 功能来释放这些内存。

free(heap_num);

有些程序忘记这样做,这就造成了所谓的 "内存泄漏"。操作系统仍然认为你需要这些内存,直到程序结束时才将其清除。

如果你现在试图访问heap_num ,你会得到可怕的 "分段故障"。你正试图访问不属于你的内存。发生这种情况有非常好的安全理由。

堆的速度比堆栈慢得多。计算机需要跟踪哪些进程被允许使用哪些内存点。访问内存并不像分配那样是个大问题。malloc 函数必须在内存中找到一个空位,将该空间标记为已使用,然后告诉你的程序它在哪里。

为什么要使用堆?

堆使你能够控制内存被释放的时间。考虑一下下面这个函数。

int* mut_num(int value) {
    int number = value;
    return &number;
}

你可能期望它能给你一个指向你所输入的任何值的指针,但它并没有。如果你很幸运,它可能真的给你你要找的东西。但更有可能的是,你只是得到一个随机的数字。为什么呢?因为number ,在函数结束时被删除。你不能在这个函数之外使用它,因为一旦这个函数结束,它就不再存在了。相反,你需要把它改写成下面的样子。

int* mut_num(int value) {
    int* number = malloc(sizeof(int));
    *number = 5;
    return number;
}

这样一来,当你离开这个函数时,这个值仍然存在。如果这看起来是一个矫揉造作的例子,你是对的。一个更好的例子是,如果你需要在函数之间传递一个可变结构。

struct Account {
    int id;
    char* name;
    int amount;
};

struct Account* new_account(char* name) {
    static int current_id = 0;
    current_id ++;

    struct Account* account = malloc(sizeof(Account));
    account -> name = name;
    account -> id = current_id;
    account -> amount = 0;

    return account;
}

这样,你就可以修改账户,并在函数之间传递它。在Java中,所有的对象都存储在堆中,就是这个原因。这使得对一个对象有多个引用变得更容易。

其他问题

当然,在使用堆时还会出现其他问题。这里有几个问题。

解除引用空值

一些函数没有返回指针,而是返回NULL 。这表明有什么地方出了问题。通常情况下,这将导致一个分段故障。这是因为NULL 并不是一个你有权限使用的真实地址。

使用未初始化的内存

这并不是严格意义上的堆问题,但假设你试图这样做。

int* pointer = malloc(sizeof(int));
printf("%d", *pointer); // undefined behavior, most likely a random number

你从来没有初始化过pointer 的值。这个值将是之前刚好在那里的任何东西。这几乎是完全随机的。

双重自由

你不希望尝试释放已经释放的东西。在某些情况下,它实际上会引起内存管理器的问题,并导致未来的malloc或freees失败。

free(pointer);
free(pointer); // undefined behavior

总结

现在你知道了许多关于内存管理的问题。你应该能够防止这些错误在你自己的C代码中出现了。