前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C/C++】——小白初步了解——内存管理

【C/C++】——小白初步了解——内存管理

作者头像
小李很执着
发布2024-06-15 10:20:14
950
发布2024-06-15 10:20:14
举报
文章被收录于专栏:学习笔记学习笔记

1. C/C++内存分布

一个典型的C/C++程序在内存中的布局如下:

  1. 代码区(Code Segment):
    • 存储程序的机器指令,由编译器生成。
    • 该区域通常是只读的,以防止程序在运行时修改自身的指令。
    • 代码区在程序加载时被操作系统加载到内存中。
  2. 数据区(Data Segment):
    • 存储全局变量和静态变量,包括已初始化和未初始化的变量。
    • 数据区又分为两部分:
      • 已初始化数据区(Initialized Data Segment): 存储程序中已初始化的全局变量和静态变量。
      • 未初始化数据区(Uninitialized Data Segment or BSS): 存储未初始化的全局变量和静态变量,程序启动时这些变量会被初始化为0。
  3. 堆区(Heap):
    • 用于动态内存分配,大小不固定,可以在程序运行时动态地增长或缩小。
    • 由程序员手动管理内存的分配和释放。常用的函数有 malloc()free()
    • 堆区的内存分配效率较低,但灵活性高。
  4. 栈区(Stack):
    • 用于函数调用时的临时存储,包括函数的局部变量、参数和返回地址。
    • 栈区的内存由编译器自动分配和释放,具有后进先出的特点。
    • 栈区内存分配效率高,但大小有限,通常由操作系统决定。
  5. 常量区(Constant Segment):
    • 存储常量数据,如字符串字面量和常量变量。
    • 通常也是只读的,以保护常量数据不被修改。

2. C语言中动态内存管理方式

在C语言中,动态内存管理主要通过以下几个函数实现:

1.malloc(size_t size):

功能:分配指定大小的字节,并返回一个指向这块内存的指针。

特点:分配的内存未初始化,内容是随机的。

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = (int *)malloc(10 * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    for (int i = 0; i < 10; i++) {
        arr[i] = i;
    }
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    free(arr);
    return 0;
}

2.calloc(size_t nmemb, size_t size):

功能:分配nmemb个元素,每个元素size字节,并初始化所有分配的字节为0。

特点:分配的内存被初始化为0,适合分配需要清零的数组。

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = (int *)calloc(10, sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    free(arr);
    return 0;
}

3.*realloc(void ptr, size_t size):

功能:调整之前分配的内存块的大小。

特点:如果新大小大于原大小,新分配的内存区域中的内容是不确定的;如果新大小小于原大小,超出的内容将被丢弃。

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = (int *)malloc(5 * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    arr = (int *)realloc(arr, 10 * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    for (int i = 5; i < 10; i++) {
        arr[i] = i;
    }
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    free(arr);
    return 0;
}

4.*free(void ptr):

功能:释放之前分配的内存块,使其可以重新分配。

特点:释放后,指针ptr不再指向有效的内存区域,应该将ptr置为NULL以防止野指针错误。

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = (int *)malloc(10 * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    free(arr);
    arr = NULL;
    return 0;
}

3. C++中动态内存管理

在C++中,动态内存管理不仅可以使用C语言的函数(如malloc、calloc等),还提供了更高级的 newdelete 运算符:

1.new:

功能:分配指定类型的内存,并调用该类型的构造函数。

示例代码:

代码语言:javascript
复制
#include <iostream>

int main() {
    int *arr = new int[10];
    for (int i = 0; i < 10; i++) {
        arr[i] = i;
    }
    for (int i = 0; i < 10; i++) {
        std::cout << arr[i] << " ";
    }
    delete[] arr;
    return 0;
}

2.delete:

功能:释放用new分配的内存,并调用该类型的析构函数。

代码语言:javascript
复制
#include <iostream>

int main() {
    int *arr = new int[10];
    delete[] arr;
    return 0;
}

在C++中,使用 newdelete 操作符进行内存管理比使用C语言中的函数更方便,因为它们不仅分配和释放内存,还自动调用构造函数和析构函数,确保对象在创建和销毁时执行必要的初始化和清理工作。

4. operator new与operator delete函数

C++中,operator newoperator delete 是为对象分配和释放内存的函数。它们类似于 mallocfree,但有一些重要区别:

operator new:

功能:分配指定大小的内存,但不调用构造函数。

通常在类的new运算符中隐式调用。

代码语言:javascript
复制
#include <iostream>

void* operator new(size_t size) {
    std::cout << "Custom new for size " << size << std::endl;
    return malloc(size);
}

void operator delete(void* ptr) noexcept {
    std::cout << "Custom delete" << std::endl;
    free(ptr);
}

int main() {
    int *p = new int(10);
    delete p;
    return 0;
}

operator delete:

  • 功能:释放用 operator new 分配的内存,但不调用析构函数。
  • 通常在类的delete运算符中隐式调用。

可以重载这两个函数以定制内存分配行为。例如,在需要跟踪内存分配和释放的场景中,可以重载 operator newoperator delete 以记录每次内存操作的日志。

5. new和delete的实现原理

newdelete 的实现可以分为两个步骤:

new:

  • 调用 operator new 分配内存。
  • 在分配的内存上调用构造函数初始化对象。

delete:

  • 在内存上调用析构函数销毁对象。
  • 调用 operator delete 释放内存。

示例代码展示了new和delete的工作机制:

代码语言:javascript
复制
#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
};

int main() {
    MyClass *obj = new MyClass();
    delete obj;
    return 0;
}

在上面的代码中,当我们使用 new MyClass() 创建对象时,首先调用 operator new 分配内存,然后在分配的内存上调用 MyClass 的构造函数。当我们使用 delete obj 删除对象时,首先调用 MyClass 的析构函数,然后调用 operator delete 释放内存。

6. 定位new表达式(placement-new)

placement new 是C++中的一个高级特性,用于在已分配的内存上构造对象。它不会分配新的内存,只是调用对象的构造函数。

代码语言:javascript
复制
#include <iostream>

int main() {
    char buffer[sizeof(int)];
    int *p = new (buffer) int(5); // 在buffer中构造int
    std::cout << *p << std::endl;
    p->~int(); // 手动调用析构函数
    return 0;
}

在上面的代码中,我们在预先分配的内存 buffer 中使用 placement new 构造了一个 int 对象。这种技术通常用于自定义内存池或优化程序性能。

7. 常见面试题

1.解释C++中new和malloc的区别

  • new:
    • 分配内存并调用构造函数初始化对象。
    • 返回对象的指针。
    • 可以重载。
    • 用于分配类对象。
  • malloc:
    • 仅分配内存,不调用构造函数。
    • 返回 void* 类型的指针,需要类型转换。
    • 不能重载。
    • 用于分配任意类型的内存。

2.什么是内存泄漏?如何避免?

  • 内存泄漏: 是指程序在分配内存后,未能正确释放已分配的内存,导致内存无法被重新利用。
  • 避免方法:
    • 使用智能指针(如 std::unique_ptrstd::shared_ptr)来自动管理内存。
    • 确保每个 malloc 对应一个 free,每个 new 对应一个 delete
    • 使用工具如 Valgrind 进行内存泄漏检测。

3.解释C++中的RAII(Resource Acquisition Is Initialization)

RAII: 是一种编程习惯,即资源的获取和释放通过对象的构造函数和析构函数来管理。

示例:

代码语言:javascript
复制
#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired" << std::endl; }
    ~Resource() { std::cout << "Resource released" << std::endl; }
};

void useResource() {
    std::unique_ptr<Resource> res(new Resource());
    // 使用资源
}

int main() {
    useResource();
    return 0;
}

4.解释栈区和堆区的区别

  • 栈区:
    • 用于存储函数调用的局部变量。
    • 内存由编译器自动分配和释放。
    • 具有后进先出的特点。
    • 内存分配效率高,但大小有限。
  • 堆区:
    • 用于动态内存分配。
    • 内存由程序员手动分配和释放。
    • 大小不固定,可以动态增长或缩小。
    • 内存分配效率较低,但灵活性高。

5.如何实现自己的内存池?

内存池是一种预分配大块内存以减少多次分配开销的方法。可以通过链表管理内存块,分配时从链表中取出一块内存,释放时将内存块重新挂回链表。

代码语言:javascript
复制
#include <iostream>
#include <vector>

class MemoryPool {
    std::vector<void*> pool;
public:
    MemoryPool(size_t size, size_t count) {
        for (size_t i = 0; i < count; ++i) {
            pool.push_back(malloc(size));
        }
    }

    void* allocate() {
        if (pool.empty()) return malloc(1); // 返回新分配内存
        void* ptr = pool.back();
        pool.pop_back();
        return ptr;
    }

    void deallocate(void* ptr) {
        pool.push_back(ptr);
    }

    ~MemoryPool() {
        for (auto ptr : pool) {
            free(ptr);
        }
    }
};

int main() {
    MemoryPool pool(256, 10);
    void* p1 = pool.allocate();
    pool.deallocate(p1);
    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. C/C++内存分布
  • 2. C语言中动态内存管理方式
    • 1.malloc(size_t size):
      • 2.calloc(size_t nmemb, size_t size):
        • 3.*realloc(void ptr, size_t size):
          • 4.*free(void ptr):
          • 3. C++中动态内存管理
            • 1.new:
              • 2.delete:
              • 4. operator new与operator delete函数
              • 5. new和delete的实现原理
              • 6. 定位new表达式(placement-new)
              • 7. 常见面试题
                • 1.解释C++中new和malloc的区别
                  • 2.什么是内存泄漏?如何避免?
                    • 3.解释C++中的RAII(Resource Acquisition Is Initialization)
                      • 4.解释栈区和堆区的区别
                        • 5.如何实现自己的内存池?
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档