内存:栈和堆(C / Swift)

2018-03-23  本文已影响55人  TomatosX

栈(C 语言描述)


什么是栈(Stack)?这是计算机内存中的一个特殊区域,它存储由每个函数创建的临时变量(包括 main() 函数)。栈是一个“LIFO”(后进先出)的数据结构。它是被 CPU 管理和优化的。每次函数声明一个新变量时,它都被“压入”栈中。然后每次函数退出时,所有由该函数压入栈的变量都被释放(也就是说,它们被删除)。一旦释放栈变量,该区域的内存就可用于其他栈变量。

使用栈来存储变量的优点是内存是自动为你管理的。你无需手动分配内存,或者在你不再需要时释放内存。更重要的是,由于 CPU 如此高效地组织栈内存,读取和写入栈变量的速度非常快。

理解栈的关键是这样一个概念:当一个函数退出时,它的所有变量都从栈中弹出(因此永远丢失)。因此栈变量本质上是本地的。这与我们之前看到的一个概念(称为变量范围)或局部变量或全局变量有关。 C 编程中的一个常见错误是尝试访问某个函数内栈上创建的变量,该函数在该函数之外的某个地方(即该函数退出后)从该程序中创建。

要记住栈的另一个特性是,可以存储在栈上的变量的大小有一个限制(随OS变化)。对于在堆中分配的变量,情况并非如此。

栈的总结:

堆(C 语言描述)


堆是计算机内存中的一栈个区域,不会自动为您进行管理,并且不受 CPU 的严格管理。它是一个更自由浮动的内存区域(并且更大)。要在堆上分配内存,必须使用 malloc()calloc() ,它们是内置的 C 函数。一旦你在堆上分配了内存,当你不再需要它的时候,你就有责任使用 free() 来释放内存。如果你没有做到这一点,你的程序将会有所谓的内存泄漏。也就是说,堆上的内存仍然会被留出(并且不会被其他进程使用)。

与栈不同,堆没有栈那样大小的限制(除了计算机明显的物理限制外)。
由于必须使用指针访问堆上的内存,因此堆内存的读取和写入速度稍慢。
与栈不同,在堆中创建的变量可以通过程序中任何位置的任何函数访问。
堆变量实质上是全局的。

栈与堆的优点和缺点


例子🌰


下面是一个简短的程序,它在上创建它的变量。

#include <stdio.h>

double multiplyByTwo (double input) {
    double twice = input * 2.0;
    return twice;
}

int main (int argc, char *argv[])
{
    int age = 30;
    double salary = 12345.67;
    double myList[3] = {1.2, 2.3, 3.4};
    
    printf("double your salary is %.3f\n", multiplyByTwo(salary));
    
    return 0;
}

// double your salary is 24691.340

在第 10, 11 和 12 行,我们声明了变量:一个 int ,一个 double 和一个由三个双精度组成的数组。只要 main() 函数执行,这三个变量就会被压入栈。当 main() 函数退出(并且程序停止)时,这些变量将从栈中弹出。类似地,在函数 multiplyByTwo() 中,一旦 multiplyByTwo() 函数执行, double 变量,即 double 将被压入栈。一旦 multiplyByTwo() 函数退出,两个变量就从栈中弹出,并且永远消失。

另一点说明,有一种方法可以告诉 C 保存栈变量,即使在其创建者函数退出,就是在声明变量时使用 static 关键字。用 static 关键字声明的变量因此变成类似于全局变量的变量,但是仅在创建它的函数内部可见。这是一种奇怪的结构,除非在特定的情况下,否则你可能不需要这种结构。

下面是该程序的另一个版本,它将它的所有变量分配给而不是

#include <stdio.h>
#include <stdlib.h>

double *multiplyByTwo (double *input) {
    double *twice = malloc(sizeof(double));
    *twice = *input * 2.0;
    return twice;
}

int main (int argc, char *argv[])
{
    int *age = malloc(sizeof(int));
    *age = 30;
    double *salary = malloc(sizeof(double));
    *salary = 12345.67;
    double *myList = malloc(3 * sizeof(double));
    myList[0] = 1.2;
    myList[1] = 2.3;
    myList[2] = 3.4;
    
    double *twiceSalary = multiplyByTwo(salary);
    
    printf("double your salary is %.3f\n", *twiceSalary);
    
    free(age);
    free(salary);
    free(myList);
    free(twiceSalary);
    
    return 0;
}

正如你所看到的,使用 malloc() 在堆上分配内存,然后使用 free() 来释放它,没有什么大不了的,但有点麻烦。另外要注意的是,现在到处都有一堆星号(*)。那些是什么?答案是,他们是指针。 malloc()calloc()free() )函数处理的是指针而不是实际值。指针是 C 中的一种特殊数据类型,它将地址存储在内存中,而不是存储实际值。因此,在上面的第 5 行中,两次变量不是 double,而是一个指向 double 的指针。在内存地址中,这是一个 double 类型的数据。

何时使用堆?何时使用栈?


什么时候应该使用堆,什么时候应该使用栈?

数据在内存中的存储(C 语言描述 )


这里有一条黄金法则:

结构体在内存中的存储(Swift 语言描述)


与 C 语言不同,苹果对 Swift 中的结构体有着优化,使其能够存储在栈上,进而加快访问速度与增加操作的安全性。

如果你的结构体只由其他结构体组成,那编译器可以确保不可变性。同样地,当使用结构体时,编译器也可以生成非常快的代码。举个例子,对一个只含有结构体的数组进行操作的效率,通常要比对一个含有对象的数组进行操作的效率高得多。这是因为结构体通常要更直接:值是直接存储在数组的内存中的。而对象的数组中包含的只是对象的引用。最后,在很多情况下,编译器可以将结构体放到栈上,而不用放在堆里。

func uniqueIntegerProvider() -> AnyIterator<Int> {
    var i = 0
    return AnyIterator {
        i += 1
        return i
    }
}

Swift 的结构体一般被存储在栈上,而非堆上。不过这其实是一种优化:默认情况下结构体是存储在堆上的,但是在绝大多数时候,这个优化会生效,并将结构体存储到栈上。当结构体变量被一个函数闭合的时候,优化将不再生效,此时这个结构体将存储在堆上。因为变量 i 被函数闭合了,所以结构体将存在于堆上。这样一来,就算 uniqueIntegerProvider 退出了作用域,i 也将继续存在。与此相似,如果结构体太大,它也会被存储在堆上

上一篇下一篇

猜你喜欢

热点阅读