12.2 - LRU缓存 && C语言内存布局

目录

1.LRU缓存

a.核心思想

b.思路

c.步骤

d.注意

[2.C 语言程序占用的内存分为哪几个部分](#2.C 语言程序占用的内存分为哪几个部分)

1.代码区

2.静态/全局数据区

3.栈区

4.堆区

5.命令行参数和环境变量区

内存示意图

注意事项


1.LRU缓存

146. LRU 缓存 - 力扣(LeetCode)

cpp 复制代码
class LRUCache {
public:
    LRUCache(int capacity) : _capacity(capacity){
        
    }
    
    int get(int key) {
        // 先判断_mmap是否已经存在该key值
        if(_mmap.find(key) != _mmap.end())
        {
            // 1. 通过_mmap获取该key值在链表的位置
            auto iter = _mmap[key].second;
            // 2. 更新该元素的位置, 并返回value值
            _list.splice(_list.begin(), _list, iter);
            return _mmap[key].first;
        }
        return -1;
    }
    
    void put(int key, int value) {
        // 先判断_mmap是否已经存在该key值
        if(_mmap.find(key) != _mmap.end())
        {
            // 若存在
            // 1. 直接在链表中更新该元素的位置
            auto iter = _mmap[key].second;
            _list.splice(_list.begin(), _list, iter);
            // 2. 再更新对应的value值
            _mmap[key].first = value;
        }
        else
        {
            // 若不存在
            // 1. 先构建pair对象
            auto iter = _list.insert(_list.begin(), key);
            std::pair<int, std::list<int>::iterator> pa(value, iter);
            // 2. 插入到_mmap中
            _mmap.emplace(key, pa);
            // 3. 判断是否超出容量
            if(_list.size() > _capacity)
            {
                // 若超出, 就删除最久没使用的对象
                _mmap.erase(*_list.rbegin());
                _list.pop_back();
            }
        }
    }

private:
    int _capacity;
    std::unordered_map<int, pair<int, std::list<int>::iterator>> _mmap;
    std::list<int> _list;
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

a.核心思想

当缓存容量达到上限时,优先淘汰最久未被使用的数据。

b.思路

**① 快速查找:**通过哈希表实现,用于快速判断某个键是否存在以及获取对应的值。

**② 维护访问顺序:**通过双向链表实现,最近访问的节点移动到链表头部,最久未访问的节点在链表尾部。

**③ 容量控制:**当缓存超过容量时,删除链表尾部的节点,并在哈希表中移除对应的键。

c.步骤

① 数据结构选择:

A. 使用 unordered_map 存储键值对,键为缓存的键(传入的key值),值为缓存的值和指向双向链表节点的迭代器(传入的value值)。

B. 使用双向链表(list)维护访问顺序,链表头部是最近访问的节点,尾部是最久未访问的节点。

② 核心操作:

A. get(key)

  • 如果键存在,将对应的节点移动到链表头部,并返回值。

  • 如果键不存在,返回 -1

B. put(key,value)

  • 如果键存在,更新值,并将节点移动到链表头部。

  • 如果键不存在,在链表头部插入新节点,并在哈希表中记录。

  • 如果插入后超过容量,删除链表尾部的节点,并在哈希表中移除对应的键。

d.注意

cpp 复制代码
    int get(int key) {
        if(_mmap.find(key) != _mmap.end())
        {
            // _list.erase(_mmap[key].second);
            // _list.remove(key);
            // _list.emplace_front(key);
            auto iter = _mmap[key].second;
            _list.splice(_list.begin(), _list, iter);
            return _mmap[key].first;
        }
        return -1;
    }
    
    void put(int key, int value) {
        if(_mmap.find(key) != _mmap.end())
        {
            // _list.remove(key);
            // _list.emplace_front(key);
            auto iter = _mmap[key].second;
            _list.splice(_list.begin(), _list, iter);
            _mmap[key].first = value;
        }
        else
        {
            auto iter = _list.insert(_list.begin(), key);
            std::pair<int, std::list<int>::iterator> pa(value, iter);
            _mmap.emplace(key, pa);
            if(_list.size() > _capacity)
            {
                _mmap.erase(*_list.rbegin());
                _list.pop_back();
            }
        }
    }

这里一开始在编写代码时,犯了一个低级的错误,就是注释代码中的逻辑!

对于更新最新访问的节点,我的想法是直接删除这个元素,接着头插到链表就好了,但是这里存在一个致命的问题,那就是_mmap并不知道我删除并重新插入该元素,所以_mmap里保存的iterator仍然是被删除的元素的迭代器,此时该迭代器失效,导致后续再使用该迭代器时会报错。(要不就一起改,要不就使用splice

另外即使一起改了,也大概率编不过,因为效率太低了。

2.C 语言程序占用的 内存 分为哪几个部分

a.代码区

存储内容:编译后的机器指令(即程序的二进制代码)

特点:只读、固定大小、共享性

cpp 复制代码
int main() {
    printf("Hello, World!\n");  // 这条指令存储在代码区
    return 0;
}

b.静态/全局数据区

① 初始化数据区:

存储内容:已初始化的全局变量和静态变量(包括全局常量)。

特点:程序启动时分配,程序结束时释放。生命周期贯穿整个程序运行期。

② 未初始化数据区:

存储内容:未初始化的全局变量和静态变量(默认初始化为0NULL)。

特点:程序启动时由系统自动清零。大小在编译时确定。

cpp 复制代码
int global_var = 10;          // 初始化全局变量
static int static_var = 20;    // 初始化静态变量
const int const_var = 30;      // 全局常量(可能存储在只读数据区)

int uninit_global;             // 未初始化全局变量(存储在 BSS 段)
static int uninit_static;      // 未初始化静态变量(存储在 BSS 段)

c.栈区

存储内容:局部变量(非静态)、函数参数和返回值、函数调用时的上下文(如返回地址、寄存器值)。

特点:自动管理、高效但有限、后进先出(LIFO

cpp 复制代码
void foo() {
    int local_var = 5;  // 局部变量存储在栈区
    char buffer[1024];   // 栈空间可能不足导致溢出
}

d.堆区

存储内容:动态分配的内存(通过 malloccallocreallocnew 分配)。

特点:手动管理、大小灵活、访问速度较慢。

cpp 复制代码
int *arr = malloc(10 * sizeof(int));  // 动态分配数组
free(arr);                           // 必须手动释放

e.命令行参数和环境变量区

存储内容:程序启动时的命令行参数(argcargv),环境变量(如 PATHHOME 等)。

特点:由操作系统在程序启动时设置、通常通过 main 函数的参数访问

cpp 复制代码
int main(int argc, char *argv[]) {
    // argv[0] 是程序名,argv[1...] 是参数
}

内存示意图

注意事项

栈溢出 递归过深或局部变量过大可能导致栈溢出(如 buffer[1000000])。

② 内存泄漏: 忘记释放堆内存(如 malloc 后未 free)。

**③ 野指针:**访问已释放的堆内存或未初始化的指针。

④ 段错误: 试图修改代码区或只读数据区(如修改字符串常量 char *s = "abc"; s[0] = 'x';)。

3.参考

**考察范围:**C语言内存体系的理解。

回答思路:代码区 + 静态区 + 堆区 + 栈区 来进行回答。

答案:

  • 代码区(Text Segment):存放程序指令(如函数代码),只读且共享。

  • 静态区(Data Segment):包含初始化的全局/静态变量、未初始化的全局/静态变量(BSS),生命周期贯穿整个程序。

  • 堆区(Heap):动态分配内存的区域(如 malloc / free),由程序员手动管理,空间最大但分配效率较低。

  • 栈区(Stack):由编译器自动管理,存放函数调用信息、局部变量和参数,分配高效但空间有限。

希望这些内容对大家有所帮助!

感谢大家的三连支持!

相关推荐
一只乔哇噻39 分钟前
java后端工程师+AI大模型进修ing(研一版‖day59)
java·开发语言·算法·语言模型
报错小能手39 分钟前
C++流类库 概述及流的格式化输入/输出控制
开发语言·c++
2301_7890156241 分钟前
C++:list(带头双向链表)增删查改模拟实现
c语言·开发语言·c++·list
暗然而日章1 小时前
C++基础:Stanford CS106L学习笔记 5 内存与指针
c++·笔记·学习
学习路上_write1 小时前
FREERTOS_定时器——创建和基本使用
c语言·开发语言·c++·stm32·嵌入式硬件
_OP_CHEN1 小时前
【算法基础篇】(二十三)数据结构之并查集基础:从原理到实战,一篇吃透!
数据结构·算法·蓝桥杯·并查集·算法竞赛·acm/icpc·双亲表示法
学技术的大胜嗷1 小时前
如何在 VSCode 中高效开发和调试 C++ 程序:面向用过 Visual Studio 的小白
c++·vscode·visual studio
liu****1 小时前
10.指针详解(六)
c语言·开发语言·数据结构·c++·算法