SQLite剖析之动态内存分配

SQLite的动态内存分配具有健壮性,处理分配失败时释放缓存并重新尝试,确保无内存泄漏。它允许设置内存使用限制,提供零分配选项和自定义分配器。SQLite通过内存分配器的测试、统计和限制机制,保证了在各种场景下的稳定性和可靠性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SQLite剖析之动态内存分配

    SQLite通过动态内存分配来获取各种对象(例如数据库连接和SQL预处理语句)所需内存、建立数据库文件的内存Cache、保存查询结果。

1、特性
    SQLite内核和它的内存分配子系统提供以下特性:
    (1)对内存分配失败的健壮处理。如果一个内存分配请求失败(即malloc()或realloc()返回NULL),SQLite将释放未关联的缓存页,然后重新进行分配请求。如果失败,SQLite返回SQLITE_NOMEM给应用程序。
    (2)无内存泄漏。应用程序负责销毁已分配的任何对象。例如应用程序必须使用sqlite3_finalize()结束每个预处理SQL语句,使用sqlite3_close关闭每个数据库连接。只要应用程序配合,即使在内存分配失败或系统出错的情况下SQLite也绝不会泄漏内存。
    (3)内存使用限制。sqlite3_soft_heap_limit64()机制可以让应用程序设置SQLite的内存使用限制。SQLite会从缓存中重用内存,而不是分配新的内存,以满足设置的限制。
    (4)零分配选项。应用程序可以在启动时给SQLite提供几个大块内存的缓冲区,SQLite将用这些缓冲区作为它所有内存分配的需要,不再调用系统的malloc()和free()。
    (5)应用程序提供内存分配器。应用程序在启动时可以给SQLite提供可选的内存分配器,以代替系统的malloc()和free()。
    (6)对防止内存分配失败或堆内存出现碎片提供形式化的保证。SQLite能配置成保证不会出现内存分配失败或内存碎片。这个特性对长期运行、高可靠性的嵌入式系统至关重要,在这样的系统上一个内存分配错误可能会导致整个系统失效。
    (7)内存使用统计。应用程序可以统计SQLite使用了多少内存,可以检测内存使用是否接近或超过设计限制。
    (8)尽量少调用分配器。系统的malloc()和free()实现在很多系统上是低效的。SQLite通过尽量少地使用malloc()和free()来减少整个处理时间。
    (9)开放式存取。可插拨的SQLite扩展模块或应用程序自己可以通过sqlite3_malloc()、sqlite3_realloc()和sqlite3_free()接口来访问SQLite使用的底层内存分配器。

2、测试
    测试基础设施通过使用一个特殊的检测内存分配器来验证SQLite没有错误地使用动态分配的内存。检测内存分配器通过编译时使用SQLITE_MEMDEBUG选项来激活,它比缺省的内存分配器更慢,因此不建议在产品中使用它。但是在测试时激活它,可以做以下检查:
    (1)边界检查。检测内存分配器在每个内存分配的末尾放置哨兵值,以验证SQLite没有任何例程写出了分配的边界。
    (2)释放后的内存使用。当每块内存被释放时,每个字节会填充一些无用的位模式。这可以确保内存在释放后绝不会留下曾经使用过的痕迹。
    (3)从非malloc获取的内存的释放。每个来自于检测内存分配器的内存分配都含有一个哨兵值,以验证每个分配的释放来自于先前的malloc。
    (4)未初始化的内存。检测内存分配器用一些无用的位模式初始化每块内存的分配,以确保用户不能对所分配内存的内容做任何假设。
    无论是否使用检测内存分配器,SQLite都会跟踪当前已取出多少内存。有数百个测试脚本用于测试SQLite。在每个脚本的末尾,所有的对象被销毁,并有一段测试保证所有内存已释放。这就是检测内存泄漏的方法。注意内存泄漏的检测在任何时候都是大批量的进行,包括测试构建和产品构建过程中。每当一个开发者运行任意单个测试脚本,内存泄漏检测就会被激活。因此开发过程中引入的内存泄漏能够迅速地被检测到并修复。
    SQLite使用一个特殊的、能模拟内存失败的内存分配器覆盖层来测试内存不足(OOM)的错误。覆盖层被插入到内存分配器和SQLite内核之间,它直接传递内存分配请求到底层的分配器,并把结果传回到请求者。覆盖层可设置让第N次内存分配失败。为了运行OOM测试,覆盖层首先设置第一次分配失败,并运行一些测试脚本以验证分配被正确捕捉和处理。然后覆盖层设置第二次分配失败并重复运行测试。失败点继续一次通知一个分配,直到整个测试过程完成并且没有出现内存分配错误。这样的完整测试流程运行两遍,第一遍覆盖层只设置第N次分配失败,第二遍覆盖层设置第N次和后续的分配失败。注意即使遇到OOM覆盖层,内存泄漏检测逻辑也继续工作,以验证SQLite即使遇到内存分配错误也不会泄漏内存。OOM覆盖层能与任何底层内存分配器一起工作,包括检测内存分配器(这时可验证OOM错误不会引入其他的内存使用错误)。
    检测内存分配器和内存泄漏检测逻辑工作在整个SQLite测试套件上。其中TCL测试套件提供99%的语句测试覆盖,TH3测试提供100%的分支测试覆盖以保证无内存泄漏。因此SQLite的动态内存分配器可以在各种场合下正确地工作。

3、配置
    3.1 可选的底层内存分配器

    SQLite源代码包含几种不同的内存分配器模块,可以在编译时选择,或在启动时作为一个有限的扩展。
    (1)缺省内存分配器
    缺省情况下,SQLite使用C标准库中的malloc()、realloc()和free()例程来分配内存。实现中还对这些例程做一层薄的包装以提供一个memsize()函数返回一个现存分配的大小。memsize()也能精确地跟踪未归还内存的字节数,它能确定当一个分配被释放时有多少字节从未归还的内存中移除。缺省内存分配器中的memsize()实现是在每个malloc()请求上多分配额外8个字节作为头部,并把分配的大小保存到这个8字节的头部。
    在大多数应用程序中我们都建议使用缺省内存分配器,如果没有使用可选内存分配器的强制性需求,使用缺省的即可。
    (2)调试内存分配器
    如果SQLite使用SQLITE_MEMDEBUG选项编译时,会使用一个不同的对系统malloc()、realloc()和free()进行重型包装的内存分配器。重型包装器对每个分配请求多分配100字节的额外空间,用来在分配的末尾放置哨兵值。当一个分配被释放时,检查这些哨兵值以确保SQLite内核没有超出缓冲区的两端。当系统库来自GLIBC时,重型包装器也会使用GNU backtrace()函数来检查栈,并记录malloc()调用的祖先函数。当运行测试套件时,重型包装器还会记录当前测试用例的名称。这两个特性对跟踪内存泄漏是非常有用的。
    重型包装器只用于SQLite的测试、分析和调试。它有显著的性能和内存开销,一般不用在最终产品中。
    (3)零

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值