跳转至

检查 std::vector 的结构

这个类的声明看起来像清单 5-4。

template<typename T>
class vector {
    T* begin;
    T* end;
    T* reservationEnd;
};

清单 5-4:一个抽象的 std::vector 对象

这个模板增加了一层抽象,所以我将用声明为 DWORD 类型的 std::vector 继续描述。下面是一款游戏可能如何声明该向量:

std::vector<DWORD> _vec;

现在,让我们剖析 DWORD 对象的 std::vector 在内存中会是什么样子。如果你有 _vec 的地址,且共享相同内存空间,你可以重新创建类的底层结构并按清单 5-5 所示访问 _vec。

class vector {
    DWORD* begin;
    DWORD* end;
    DWORD* tail;
};
// 指向内存中的向量
vector* _vec = (vector*)vectorAddress;

清单 5-5:一个 DWORD std::vector 对象

你可以把成员 begin 当作原始数组,因为它指向 std::vector 对象中的第一个元素。不过没有数组长度成员,所以你必须根据 begin 和 end 计算向量的长度,end 是数组中最后一个对象之后的空对象。长度计算代码看起来像这样:

int length() {
    return ((DWORD)_vec->end - (DWORD)_vec->begin) 
/ sizeof(DWORD);
}

这个函数只是用 end 中存储的地址减去 begin 中存储的地址,找出它们之间的字节数。然后,要计算对象数量,它用每个对象的字节数除以字节数。

使用 begin 和这个 length() 函数,你可以安全地访问 _vec 中的元素。那段代码看起来像这样:

DWORD at(int index) {
    if (index >= _vec->length())
        throw new std::out_of_range();
    return _vec->begin[index];
}

给定索引,这段代码会从向量中取一个项目。但如果索引大于向量的长度,会抛出 std::out_of_range 异常。如果类不能保留或重用内存,向 std::vector 添加值会非常昂贵。为了解决这个问题,类实现了名为 reserve() 的函数,告诉向量要为多少对象留出空间。

std::vector 的绝对大小(它的容量)通过一个额外的指针确定,在我们重新创建的 vector 类中叫做 tail。容量计算与长度计算类似:

int capacity() {
    return ((DWORD)_vec->tail - 
(DWORD)_vec->begin) / sizeof(DWORD);
}

要找到 std::vector 的容量,不是像计算长度那样用 begin 地址减去 end 地址,这个函数用 tail 减去 begin 地址。此外,你可以第三次使用这个计算,用 tail 和 end 确定向量中的空闲元素数量:

int freeSpace() {
    return ((DWORD)_vec->tail - (DWORD)_vec->end) 
/ sizeof(DWORD);
}

有了正确的内存读写函数,你可以使用清单 5-4 中的声明和后面的计算来访问和操纵游戏内存中的向量。第 6 章会详细讨论读取内存,但现在,让我们看看如何确定你感兴趣的数据是否存储在 std::vector 中。