内存泄漏

背景


对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题。已经有许多技术被研究出来以应对这个问题,比如 Smart Pointer,Garbage Collection等。Smart Pointer技术比较成熟,STL中已经包含支持Smart Pointer的class,但是它的使用似乎并不广泛,而且它也不能解决所有的问题;Garbage Collection技术在Java 中已经比较成熟,但是在c/c++领域的发展并不顺畅,虽然很早就有人思考在C++中也加入GC的支持。

内存泄露的定义


广义的内存泄露

​ 广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内 存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。

查看更多

c++ 符号的编解码

c++ 符号的编解码

C++ 语言在编译的时候,符号会被编译器修改,转换成 C++ ABI 标识符,正向的过程称为 mangle,反向的过程称为为 demangle。

一、ABI 说明

ABI 全称为:Application Binary Interface 。 C/C++发展的过程中,二进制兼容一直是个问题。不同编译器厂商编译的二进制代码之间兼容性不好,甚至同一个编译器的不同版本之间兼容性也不好。之后 C 语言首先拥有了统一的 ABI,而 C++ 由于其特性的复杂性和 ABI 标准推进缓慢,一直没有自己的 ABI。

这就造成的不同的编译器或者不同的编译器版本,编译后的名称可能有所不同。

每个编译器都有一套自己内部的符号编译规则,比如对于 Linux 下 G++ 而言,如下的简单的规则:

每个方法都是以 _Z 开头,对于嵌套的名字(比如命名空间中的名字或者类中间的名字,比如 Class::Func)后面紧跟 N。然后是各个命名空间和类的名字,每个名字前都是各自字符的长度,再以 E 结尾。(如果不是嵌套名字则不需要以 E 结尾)。

查看更多

编译 openssl

1
2
3
4
5
6
7
../Configure --cross-compile-prefix=aarch64-linux-gnu-  no-asm no-zlib no-comp no-engine no-dso no-ssl2 no-ssl3 no-hw linux-aarch64 --prefix=/home/system/ExtDisk/home/system/App/toolchain/src/out_aarch64_zy/openssl-1.1.1o --openssldir=/home/system/ExtDisk/home/system/App/toolchain/src/out_aarch64_zy/openssl-1.1.1o -fno-sanitize=address -DOPENSSL_NO_ASM -DOPENSSL_NO_INLINE_ASM -DOPENSSL_NO_INLINE -DOPENSSL_NO_AUTOLOAD_CONFIG -DOPENSSL_NO_DEPRECATED -DOPENSSL_NO_COMP -DOPENSSL_NO_ENGINE -DOPENSSL_NO_STATIC_ENGINE -DOPENSSL_NO_HW -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_TLS1 -DOPENSSL_NO_TLS1_1 -DOPENSSL_NO_WEAK_SSL_CIPHERS 

make depend
make -j8
make install # 才可以安装到设置的目录中

-fno-sanitize=address 选项来禁用 ASan 的内存分配函数重载
1
2
3
在使用 ASAN 时,由于 ASAN 会对内存分配函数进行重载,为了避免对 OpenSSL 的影响,需要禁用 ASAN 的内存分配函数重载。您可以在编译 OpenSSL 库时指定编译选项 -DOPENSSL_NO_ASM、-DOPENSSL_NO_INLINE、-DOPENSSL_NO_AUTOLOAD_CONFIG、-DOPENSSL_NO_DEPRECATED、-DOPENSSL_NO_COMP、-DOPENSSL_NO_ENGINE、-DOPENSSL_NO_STATIC_ENGINE、-DOPENSSL_NO_HW、-DOPENSSL_NO_SSL2、-DOPENSSL_NO_SSL3、-DOPENSSL_NO_TLS1、-DOPENSSL_NO_TLS1_1 和 -DOPENSSL_NO_WEAK_SSL_CIPHERS 来禁用 ASAN 的内存分配函数重载。这些选项会关闭一些不必要的功能和优化,从而确保在使用 ASAN 时不会对 OpenSSL 的内存分配函数造成干扰。

../config no-asm no-zlib no-ssl2 no-ssl3 no-comp no-hw no-engine -DOPENSSL_NO_ASM -DOPENSSL_NO_INLINE -DOPENSSL_NO_AUTOLOAD_CONFIG -DOPENSSL_NO_DEPRECATED -DOPENSSL_NO_COMP -DOPENSSL_NO_ENGINE -DOPENSSL_NO_STATIC_ENGINE -DOPENSSL_NO_HW -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_TLS1 -DOPENSSL_NO_TLS1_1 -DOPENSSL_NO_WEAK_SSL_CIPHERS

查看更多

一、hook 文件 IO 函数问题

hook IO 函数遇到的问题,在进程退出时,exit 类函数,exit 这个函数会通过 close/fclose 来关闭打开的文件描述符,但是这个时候,业务对象可能已经被销毁,如果此时又去使用业务对象,就会导致未定义的问题。产生 core 、或者非预期的问题。

进程退出时的过程:

数据结构

三、底层实现

1. 内存回收

采用引用计数技术实现内存回收机制

2. 对象共享

如果多个键的对象值都相同,对于相同的对象值,这些键就会同时指向这个对象。节省空间。

举例:Redis 在初始化服务器时,创建一万个字符串对象,这些对象包含了从 0 到 9999 的所有整数值,当服务器需要用到值为 0 到 9999 的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。

1
2
// 查看键 a 所对应的值对象的引用计数
object refcount a

查看更多

动态字符串 SDS

一、C 语言 char* 字符串的不足

  • C 语言的 char* 会在 \0 处表示字符串结束,如果数据在本身就有 \0,那么数据会被截断。不符合 redis 保存任意二进制数据的需求。
  • 使用 \0 作为字符串的结束字符。也会操作字符串的函数的复杂度增加。比如 strcat 字符串追加函数,需要遍历源字符串才能完成追加。操作函数的复杂度一旦增加,就会影响字符串的操作效率。

二、SDS 结构设计

1
2
3
4
5
6
7
8
9
10
struct __attribute__ ((__packed__)) sdshdr64 {
// 字符数组现有长度
uint64_t len;
// 字符数组的分配空间长度
uint64_t alloc;
// SDS 类型
unsigned char flags;
// 字符数组
char buf[];
};

查看更多