跳转至

检查 std::string 的结构

当你剥离 std::string 类的成员函数和其他非数据组件时,剩下的结构是这样的:

class string {
    union {
        char* dataP;
        char dataA[16];
    };
    int length;
// 指向内存中的字符串
string* _str = (string*)stringAddress;

这个类保留了 16 个字符,大概用于就地存储字符串。但它也声明前 4 个字节可以是指向字符的指针。这看起来奇怪,但它是优化的结果。在某个时刻,这个类的开发者决定 15 个字符(加上null 终止符)是许多字符串合适的长度,他们选择提前保留 16 字节内存来节省内存分配和释放。为了容纳更长的字符串,他们允许这块保留内存的前 4 个字节用作指向这些更长字符串字符的指针。

 如果代码编译为 64 位,那么实际上会是前 8 个(而不是 4 个)字节指向字符。不过,在整个示例中,你可以假设 32 位地址,且 int 是地址的大小。

这样访问字符串数据会有一些开销。定位正确缓冲区的函数看起来像这样:

const char* c_str() {
    if (_str->length <= 15)
        return (const char*)&_str->dataA[0];
    else
        return (const char*)_str->dataP;
}

std::string 既可以是完整字符串,也可以是指向更长字符串的指针,这一事实使这个特定结构从游戏破解的角度看相当棘手。有些游戏可能用 std::string 存储很少超过 15 个字符的字符串。这种情况下,你可能会实现依赖这些字符串的机器人,却永远不知道底层结构实际上比简单字符串复杂得多。