工作笔记-堆 和 栈总结

“堆”(Heap)和“栈”(Stack)是计算机内存管理的两种不同方式,它们有不同的用途、特性和管理方式。下面是对这两者的详细解释和比较:

1. 栈(Stack)

栈是一种 后进先出(LIFO, Last In, First Out) 的数据结构。它是由操作系统自动管理的,主要用于存储局部变量和函数调用的上下文(如返回地址、函数参数、局部变量等)。栈内存的管理非常高效,但它有以下特点:

  • 内存分配:栈是由操作系统在程序运行时自动分配的。每当一个函数被调用时,栈会为其分配空间,函数执行完毕后,空间会被自动释放。

  • 大小有限:栈的大小通常比堆小得多,且一般是有限制的。如果栈的使用超过了其限制,会引发 栈溢出(Stack Overflow) 错误。

  • 分配速度快:栈内存的分配和释放速度非常快,因为只需要调整栈顶指针。

  • 内存管理:内存由系统自动管理,程序员无需手动释放内存。

  • 局部变量:栈上分配的内存通常用于存储局部变量和函数调用的上下文。每个线程有自己的栈空间

栈的示例:
void myFunction() {
    int localVar = 10;  // 局部变量存储在栈中
    printf("%d\n", localVar);
}

在上面的代码中,localVar 是局部变量,存储在栈上,函数执行完后,localVar 会自动释放。


2. 堆(Heap)

堆是一块 动态分配 的内存区域,用于存储程序运行时动态分配的对象和数据结构。与栈不同,堆内存是由程序员手动分配和释放的,具有以下特点:

  • 内存分配:堆内存由程序员通过显式的内存分配函数(如 mallocnew)来申请。与栈不同,堆内存不是自动释放的,程序员必须手动释放(如 freedelete)。

  • 大小灵活:堆的大小通常没有限制(只受可用系统内存的限制)。堆内存可以动态扩展,适合存储生命周期较长的数据(如大型数据结构、动态数组等)。

  • 分配速度较慢:堆内存的分配和释放比栈慢,因为堆内存的管理需要更复杂的算法来跟踪已分配和未分配的内存。

  • 内存管理:堆内存需要程序员手动管理,忘记释放内存会导致 内存泄漏

  • 全局访问:堆内存通常是共享的,多个函数和线程可以访问同一块堆内存

堆的示例:
void myFunction() {
    int localVar = 10;  // 局部变量存储在栈中
    printf("%d\n", localVar);
}


堆和栈的对比

特性栈(Stack)堆(Heap)
内存分配方式自动分配(函数调用时由操作系统分配,函数返回时自动释放)手动分配(由程序员通过 mallocnew 等分配,需要 freedelete 释放)
内存大小较小,通常有限制(操作系统指定大小)较大,通常受限于可用内存,程序可以动态分配和释放
分配速度非常快,调整栈顶指针即可较慢,涉及内存查找和管理
释放方式自动释放,函数退出时栈空间自动释放需要手动释放,程序员必须调用 freedelete 释放内存
生命周期仅在函数调用期间有效,函数返回后即被销毁动态分配,程序员决定何时释放
访问范围每个线程有独立的栈空间,线程内的局部变量只能在当前线程中访问全局共享,多个线程和函数可以访问
存储内容存储局部变量、函数参数、返回地址等存储动态分配的对象和数据结构
内存管理复杂度简单,由操作系统自动管理复杂,需要程序员手动管理
错误栈溢出(Stack Overflow)内存泄漏(Memory Leak)、野指针(Dangling Pointer)

栈和堆的优缺点

栈的优点
  • 自动管理:不需要程序员手动管理内存,减少内存泄漏的风险。
  • 分配和释放非常快。
  • 适合存储短期的数据,特别是局部变量。
栈的缺点
  • 空间有限:栈的大小通常较小,一旦超出限制会导致栈溢出。
  • 数据生命周期较短:数据只在函数执行期间有效,无法存储跨函数的数据。
堆的优点
  • 空间大:堆内存的大小通常受限于系统内存,可以存储大量数据。
  • 灵活:可以动态分配内存,适合存储需要跨函数调用的数据。
堆的缺点
  • 内存管理复杂:需要手动分配和释放内存,容易发生内存泄漏和悬空指针问题。
  • 分配和释放内存的速度较慢,可能会导致性能开销。

什么时候使用栈和堆

  • :适用于存储生命周期短、大小固定的数据,如局部变量和函数参数。栈非常高效,但空间有限。

  • :适用于存储生命周期长、大小可变的数据,如动态数组、链表、树等数据结构。堆内存更加灵活,但需要手动管理。

在编写程序时,通常会结合使用堆和栈,根据数据的生命周期、大小和访问方式来决定将数据存储在哪里。

 使用示例

1. 栈(Stack)示例

栈内存用于存储函数的局部变量、函数调用的返回地址等。栈内存是由操作系统管理的,自动分配和释放。每个线程都有自己的栈。

栈的特点:

  • 自动管理,函数调用时分配内存,函数结束时自动释放。
  • 生命周期短,局部变量只在函数执行期间有效。
  • 空间有限,栈的空间是有限的。
示例:局部变量
#include <stdio.h>

void func() {
    int x = 5;  // 局部变量x存储在栈上
    printf("Value of x: %d\n", x);
}

int main() {
    func();  // 调用func
    return 0;
}

说明:

  • xfunc 函数的局部变量,它存储在栈上。
  • func 被调用时,栈为 x 分配空间,并在 func 执行完后自动释放这块内存。
  • x 只在 func 函数内有效,当 func 执行结束,x 的内存就被释放。

栈的内存管理是由操作系统自动完成的,因此不需要手动分配或释放。


2. 堆(Heap)示例

堆内存用于动态分配内存,程序员需要手动分配和释放。堆内存的大小受限于系统的物理内存,堆内存适合存储较大或生命周期较长的数据。

堆的特点:

  • 手动管理,程序员使用 mallocnew 分配内存,使用 freedelete 释放内存。
  • 生命周期灵活,可以跨函数或跨程序运行期间使用。
  • 空间较大,堆的内存一般较栈大,且可以动态扩展。
示例:动态内存分配
#include <stdio.h>
#include <stdlib.h>

void func() {
    int *ptr = (int *)malloc(sizeof(int));  // 在堆上分配内存
    if (ptr == NULL) {
        printf("Memory allocation failed!\n");
        return;
    }
    *ptr = 10;  // 设置堆上内存的值
    printf("Value stored in heap: %d\n", *ptr);
    free(ptr);  // 释放堆内存
}

int main() {
    func();  // 调用func
    return 0;
}

说明:

  • ptr 是一个指针,它指向堆上的内存。在 malloc 调用时,堆上为 ptr 分配了一个 int 类型的内存区域。
  • 程序员手动管理堆内存的分配和释放,使用 malloc 分配内存,使用 free 释放内存。
  • func 函数执行完毕后,ptr 指向的堆内存不会自动释放,程序员需要使用 free(ptr) 来释放这块内存,避免内存泄漏。

堆的内存管理需要程序员显式地调用 mallocfree,否则可能导致内存泄漏。


栈与堆的对比举例-堆内存在多个线程和函数之间共享

示例:多个线程访问堆内存
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct {
    int value;
} SharedData;

void *thread_function(void *arg) {
    SharedData *data = (SharedData *)arg;
    data->value += 10;
    printf("Thread: Updated value = %d\n", data->value);
    return NULL;
}

int main() {
    // 在堆上分配内存,多个线程可以共享
    SharedData *data = (SharedData *)malloc(sizeof(SharedData));
    if (data == NULL) {
        printf("Memory allocation failed!\n");
        return -1;
    }

    data->value = 5;  // 初始化值

    pthread_t thread1, thread2;

    // 创建线程1和线程2
    pthread_create(&thread1, NULL, thread_function, (void *)data);
    pthread_create(&thread2, NULL, thread_function, (void *)data);

    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    printf("Main: Final value = %d\n", data->value);

    free(data);  // 释放堆内存
    return 0;
}

运行输出(可能不同,因为线程执行顺序不确定):
Thread: Updated value = 15
Thread: Updated value = 25
Main: Final value = 25

说明
  • SharedData 是一个结构体,包含一个整型变量 value,它在堆上分配,data 是一个指向堆内存的指针。
  • pthread_create 创建了两个线程 thread1thread2,它们都使用同一个 data 指针,意味着它们可以访问和修改同一块堆内存。
  • 每个线程对 data->value 进行修改,最终打印的值会受到线程执行顺序的影响。
  • 注意:在这个示例中,没有对共享数据加锁,因此可能会出现竞争条件,导致结果不可预期。在实际生产环境中,通常需要使用 互斥锁(mutex) 来保护对共享堆内存的访问。
例子:多个函数共享堆内存的独特性与局部变量的区别 (多个函数之间共享堆内存,局部变量无法跨函数共享)
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int value;
} SharedData;

// 使用堆内存的函数,数据可以在多个函数之间共享
void modify_data_heap(SharedData *data) {
    data->value += 5;
    printf("In modify_data_heap: value = %d\n", data->value);
}

void print_data_heap(SharedData *data) {
    printf("In print_data_heap: value = %d\n", data->value);
}

// 使用局部变量的函数,数据不能跨函数共享
void modify_data_stack() {
    int value = 10;  // 栈上的局部变量
    value += 5;
    printf("In modify_data_stack: value = %d\n", value);
}

void print_data_stack() {
    int value = 10;  // 栈上的局部变量
    printf("In print_data_stack: value = %d\n", value);
}

int main() {
    // 堆内存:多个函数共享同一块数据
    SharedData *data = (SharedData *)malloc(sizeof(SharedData));
    if (data == NULL) {
        printf("Memory allocation failed!\n");
        return -1;
    }
    data->value = 10;  // 初始化堆内存中的数据

    modify_data_heap(data);   // 第一个函数修改堆内存数据
    print_data_heap(data);    // 第二个函数访问堆内存数据

    free(data);  // 释放堆内存

    // 栈内存:每个函数都有独立的局部变量
    modify_data_stack();   // 修改局部变量
    print_data_stack();    // 打印局部变量,注意它们是独立的

    return 0;
}

运行输出
In modify_data_heap: value = 15
In print_data_heap: value = 15
In modify_data_stack: value = 15
In print_data_stack: value = 10

分析与解释
  1. 堆内存的共享性

    • main 函数中,我们通过 mallocSharedData 类型的数据分配了堆内存。
    • modify_data_heapprint_data_heap 两个函数都接受堆内存的指针,因此它们 共享同一块堆内存。修改后的值 (15) 能够在多个函数间传递和访问。
    • 通过堆内存,多个函数可以访问和修改同一块数据,无论这些函数是在栈上还是其他地方,只要它们获得了正确的指针。
  2. 局部变量的作用范围

    • modify_data_stackprint_data_stack 函数中,value 是局部变量,存储在栈上。每个函数都有自己独立的 value 变量。
    • modify_data_stack 中的 value 被修改为 15,但是这个 value 是局部的,在 print_data_stack 中,新的 value 变量从头开始初始化为 10,它与 modify_data_stack 中的 value 没有任何关系。
    • 由于局部变量只在它们各自的函数作用域内有效,所以它们不能跨函数共享数据。每次调用 modify_data_stackprint_data_stack 时,都会重新创建独立的局部变量。

栈与堆的内存使用对比

特性栈(Stack)堆(Heap)
分配方式自动分配,函数调用时分配,函数返回时自动释放手动分配,使用 mallocnew 分配,使用 freedelete 释放
内存管理操作系统自动管理程序员手动管理
大小限制较小,通常有限制(如 1MB)较大,受限于系统可用内存
生命周期局部变量只在函数内部有效,函数返回后自动销毁动态分配,程序员决定何时销毁
效率内存分配和回收速度非常快分配和回收较慢,可能涉及复杂的内存管理操作
内存溢出栈溢出(Stack Overflow):栈空间不足时发生内存泄漏:忘记释放堆内存时发生
使用场景局部变量、函数调用上下文动态内存分配、大数据结构、生命周期较长的数据

总结

  1. :存储局部变量、函数参数、返回地址等。由操作系统自动管理,效率高,空间较小,但生命周期短,适合短期数据存储。

  2. :用于动态分配内存,可以存储生命周期较长的数据,空间较大,但需要程序员手动管理,效率较低,容易出现内存泄漏问题。

栈和堆有各自的优缺点,程序员需要根据不同的需求来选择使用栈或堆来管理内存。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dlz0836

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值