RTTI 是 C++ 提供的运行时机制用于确定对象的类型。借助 RTTI,程序可以在运行时识别对象信息,这在处理多态对象时十分有用。但另一方面,RTTI 会在包体积和安全性上可能有负面影响。

RTTI,即 Run-Time Type Information。

RTTI 的作用

C++ 主要提供了两个操作符来使用 RTTI 特性:

  • typeid 操作符

用于获取对象的类型信息,返回一个 std::type_info 对象,这个对象包含了类型的名称等关键信息。

  • dynamic_cast 操作符

用于在继承层次结构中进行安全的类型转换(从基类指针或引用转换为派生类指针或引用)。如果转换失败,对于指针类型,dynamic_cast 会返回 nullptr;对于引用类型,dynamic_cast 会抛出 std::bad_cast 异常。

使用 AI 生成的一个简单示例展示 typeid 和 dynamic_cast 的用法:

#include <iostream>

class Base {
public:
    virtual ~Base() {}
};

class Derived : public Base {};

int main() {
    Base* basePtr = new Derived();

    // 使用 typeid 获取类型信息
    std::cout << "Type of basePtr: " << typeid(*basePtr).name() << std::endl;

    // 使用 dynamic_cast 进行向下转换
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        std::cout << "Downcast successful!" << std::endl;
    } else {
        std::cout << "Downcast failed!" << std::endl;
    }

    delete basePtr;
    return 0;
}

RTTI 负面影响

RTTI 的实现依赖于编译器在编译时生成额外的类型信息,并将这些信息在最终的二进制文件中保留。这些类型信息包含类的层次结构、类型名称等,程序在运行时可以根据这些信息来确定对象的实际类型。因此,RTTI 在增加动态性的同时,也不可避免地引入了一些负面作用:

  • 会使编译后的二进制文件增大

项目中 C++ 代码比较多的话,影响比较显著。在 kanchuan 测试的项目中,启用 RTTI 后,二进制包体积增大了 5M。

  • 二进制安全性

启用 RTTI 后,可能会给攻击者暴露一些关键的类信息。

  • 性能影响

相比前两个影响,在现有 CPU 算力的情况下对性能的影响相对没那么重要。但在对性能要求极高的场景下,关闭 RTTI 可以提高程序的运行效率(避免了运行时计算)。

如何关闭 RTTI

代码中不能包含 typeid 和 dynamic_cast 的使用,同时可以增加 -fno-rtti 编译参数禁用 RTTI。以 Protocol Buffers 为例,其 C++ 版本(至少 从 V21.12 版本开始)提供了禁用 RTTI 的支持:

"OTHER_CPLUSPLUSFLAGS" => "-fno-rtti"

即可编译 “NO RTTI“ 的版本。

如何确定关闭 RTTI 是否生效

__ZTI 是 GCC 和 Clang 编译器为 type_info 类型生成的符号的前缀,以 iOS Mach-O 二进制文件为例,可以通过以下命令查找与 RTTI 相关的符号:

nm {binary_file} | grep "__ZTI"

需要注意的是,即使在工程中配置 -fno-rtti,也可能还是找到一些 "__ZTI" 符号,比如:

0000000000004600 S __ZTISt11logic_error
0000000000004618 S __ZTISt12length_error
0000000000004630 S __ZTISt12out_of_range
00000000000045f0 S __ZTISt9exception
00000000000038a0 S __ZTISt11logic_error
00000000000038b8 S __ZTISt12length_error
0000000000003890 S __ZTISt9exception
0000000000001ff0 S __ZTISt11logic_error
0000000000002008 S __ZTISt12length_error
0000000000001fe0 S __ZTISt9exception
000000000000d350 S __ZTISt11logic_error
000000000000d368 S __ZTISt12length_error
000000000000d340 S __ZTISt9exception
000000000000c0c0 S __ZTISt11logic_error
000000000000c0d8 S __ZTISt12length_error
000000000000c0b0 S __ZTISt9exception

这些是依赖的标准库或其他三方库中可能存在的 RTTI,只要确保没有我们自己业务代码的 __ZTI 符号即可。