内容简介:Base是Derived的基类,前者拥有成员变量_v_b,后者拥有前者的_v_b和自己定义的_v_d。我们分别构建一个Base和Derived对象数组我们再提供一个方法,用于遍历数组中对象的_v_b(Base基类定义的成员变量)。
class Base {
public:
Base() = default;
void set_v_b(int v_b) {
_v_b = v_b;
}
int get_v_b() const {
return _v_b;
}
private:
int _v_b;
};
class Derived :
public Base
{
public:
Derived() = default;
void set_v_d(int v_d) {
_v_d = v_d;
}
int get_v_d() const {
return _v_d;
}
private:
int _v_d;
};
Base是Derived的基类,前者拥有成员变量_v_b,后者拥有前者的_v_b和自己定义的_v_d。
我们分别构建一个Base和Derived对象数组
Base * build_base_list(size_t count) {
Base *b_list = new (std::nothrow) Base[count];
if (!b_list) {
return nullptr;
}
for (size_t i = 0; i < count; i++) {
b_list[i].set_v_b(static_cast<int>(i));
}
return b_list;
}
Derived * build_derived_list(size_t count) {
Derived *d_list = new (std::nothrow) Derived[count];
if (!d_list) {
return nullptr;
}
for (size_t i = 0; i < count; i++) {
d_list[i].set_v_b(static_cast<int>(i));
d_list[i].set_v_d(static_cast<int>(i));
}
return d_list;
}
我们再提供一个方法,用于遍历数组中对象的_v_b(Base基类定义的成员变量)。
void print_v_b(Base *b_list, size_t b_list_count) {
if (!b_list) {
return;
}
for (size_t i = 0; i < b_list_count; i++) {
std::cout << "_v_b(" << i << "):" << b_list[i].get_v_b() << std::endl;
}
}
然后我们对构建的两个数组分别调用print_v_b,以期望打印出各自的v_b。
const size_t count = 8;
std::unique_ptr <Base, std::function<void(Base*)>> base_list(
build_base_list(count),
[](Base* p) {
delete [] p;
}
);
std::cout << "base_list:" << std::endl;
print_v_b(base_list.get(), count);
std::unique_ptr <Derived, std::function<void(Derived*)>> derived_list(
build_derived_list(count),
[](Derived* p) {
delete [] p;
}
);
std::cout << "derived_list:" << std::endl;
print_v_b(derived_list.get(), count);
理论上,我们将看到两组相同的结果。因为base_list和derived_list中每个元素的_v_b是其在数组中的下标。然而结果是
base_list: _v_b(0):0 _v_b(1):1 _v_b(2):2 _v_b(3):3 _v_b(4):4 _v_b(5):5 _v_b(6):6 _v_b(7):7 derived_list: _v_b(0):0 _v_b(1):0 _v_b(2):1 _v_b(3):1 _v_b(4):2 _v_b(5):2 _v_b(6):3 _v_b(7):3
很明显,derived_list数组输出的元素信息不正确。
derived_list数组中的每个元素都是Base子类Derived的对象。理论上,对Derived对象,通过基类Base的方法访问,是可以获得正确数据的。那问题出在哪里?我们还要回到print_v_b方法中
void print_v_b(Base *b_list, size_t b_list_count) {
if (!b_list) {
return;
}
for (size_t i = 0; i < b_list_count; i++) {
std::cout << "_v_b(" << i << "):" << b_list[i].get_v_b() << std::endl;
}
}
我们看到第7行是通过数组下标的形式获取每个元素的。在 C语言 中,如果一个数组通过下标[]访问元素,其获取的元素实际地址是Head+index*sizeof(struct)。
我们分别看一个int型和long long型数组通过下标获取元素的取址值
const size_t count = 8;
int integer_list[count];
std::cout << "Head:" << integer_list << " sizeof(int):" << sizeof(int) << std::endl;
for (size_t i = 0; i < count; i++) {
std::cout << "integer_list[" << i << "] address:" << &integer_list[i] << std::endl;
}
long long longlong_list[count];
std::cout << "Head:" << integer_list << " sizeof(int):" << sizeof(long long) << std::endl;
for (size_t i = 0; i < count; i++) {
std::cout << "longlong_list[" << i << "] address:" << &longlong_list[i] << std::endl;
}
可以看到,虽然每次下标只是自增1,但是地址实际增加了每个元素的大小。
Head:0x7fffffffe900 sizeof(int):4 integer_list[0] address:0x7fffffffe900 integer_list[1] address:0x7fffffffe904 integer_list[2] address:0x7fffffffe908 integer_list[3] address:0x7fffffffe90c integer_list[4] address:0x7fffffffe910 integer_list[5] address:0x7fffffffe914 integer_list[6] address:0x7fffffffe918 integer_list[7] address:0x7fffffffe91c Head:0x7fffffffe900 sizeof(int):8 longlong_list[0] address:0x7fffffffe9a0 longlong_list[1] address:0x7fffffffe9a8 longlong_list[2] address:0x7fffffffe9b0 longlong_list[3] address:0x7fffffffe9b8 longlong_list[4] address:0x7fffffffe9c0 longlong_list[5] address:0x7fffffffe9c8 longlong_list[6] address:0x7fffffffe9d0 longlong_list[7] address:0x7fffffffe9d8
在print_v_b数组中,它默认认为数组中每个元素大小是Base对象的大小。然而derived_list数组中每个元素的是Derived对象大小。Derived类比Base类多一个元素_v_d,从而大小从Base对象的4字节变成了8字节。这样第7行中,每次下标移动实际只是移动了4字节,于是每个奇数次移动均移动到Derived对象的_v_d前,每个偶数次移动均移动到Derived对象的_v_b前。这就出现了上面的数据错乱的问题。
数组是C的遗产。为了兼容C,C++保留了很多C语言的印记,于是导致自身呈现出一些不清晰的表达。比如下面如下三种写法
- void print_t(T *t)
- void print_t(T t[])
- void print_t(T & t)
第3种写法,我们可以知道t是个对象。
第2种写法,我们可以知道t表达了一个数组。
第1中写法,则可以表达出t可以是一个数组,可以是一个对象。那么到底它是个组数还是对象?我们没法从语法上得知。
像本例中,使用者很有可能会把print_v_b的第一元素当成一个对象指针(当然第二个参数透露出其应该是一个数组,但是假如没有第二个参数呢?),那么他怎么也不会想到,对derived_list调用print_v_b会出错。
这从一个侧面可以说明,对于可以灵活表达的C++语言,我们需要采用一些易于理解的方式去设计API。
以上所述就是小编给大家介绍的《bug诞生记——隐蔽的指针偏移计算导致的数据错乱》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ICMP隐蔽隧道从入门到精通
- 浅谈http隐蔽隧道下的RDP暴力破解
- SSHazam:如何利用SSH隧道实现隐蔽C2通信
- SSHazam:如何利用SSH隧道实现隐蔽C2通信
- 记一次Access偏移注入
- ios – reloadRowsAtIndexPaths时保持偏移量
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Data Mining
Bing Liu / Springer / 2011-6-26 / CAD 61.50
Web mining aims to discover useful information and knowledge from Web hyperlinks, page contents, and usage data. Although Web mining uses many conventional data mining techniques, it is not purely an ......一起来看看 《Web Data Mining》 这本书的介绍吧!