在 boost::serialization 中导出 class 偏移量计算。有效吗?
Derived class offset calculation in boost::serialization. Is it valid?
boost::serialization
包含 this code:
reinterpret_cast<std::ptrdiff_t>(
static_cast<Derived *>(
reinterpret_cast<Base *>(1 << 20)
)
) - (1 << 20)
它的目的是计算基数和派生数之间的偏移量类。此代码是否没有未定义的行为?
我问的原因是ASAN+UBSAN抱怨。例如,这段代码
#include <iostream>
class Foo { public: virtual void foo() {} };
class Base { public: virtual void base() {} };
class Derived: public Foo, public Base {};
int main()
{
std::cout <<
(reinterpret_cast<std::ptrdiff_t>(
static_cast<Derived *>(
reinterpret_cast<Base *>(1 << 20)
)
) - (1 << 20));
}
编译为(gcc 版本 9.2.1)
g++ -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -g main.cpp
产生这个输出
AddressSanitizer:DEADLYSIGNAL
=================================================================
==72613==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000ffff8 (pc 0x0000004012d9 bp 0x7ffd5b3eecf0 sp 0x7ffd5b3eece0 T0)
==72613==The signal is caused by a READ memory access.
#0 0x4012d8 in main main.cpp:13
#1 0x7f74a90d5f42 in __libc_start_main (/lib64/libc.so.6+0x23f42)
#2 0x40112d in _start (/home/.../a.out+0x40112d)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV main.cpp:13 in main
是误报还是这段代码真的有问题?
2019 年 12 月 9 日更新: 根据 Filipp 的提议和我的实验,此代码似乎有效并且不会产生任何警告:
std::aligned_storage<sizeof(Derived)>::type data;
reinterpret_cast<char*>(&data)
- reinterpret_cast<char*>(
static_cast<Base*>(
reinterpret_cast<Derived*>(&data)));
有人发现此代码段有任何问题吗?如果没有,我会建议给 boost
.
2019 年 12 月 16 日更新: 将 was merged 修复到 boost::serialization
develop
分支。
reinterpret_cast<Base *>(1 << 20)
这不是一个有效的指针。
static_cast
需要对其进行评估,它具有未定义的行为。
这是一个有趣的 "trick",但它似乎没有明确定义,-fsanitize
选项的结果证实了这一点。
如另一个答案所示,问题是 (1 << 20)
不是任何对象的地址。使用原则上可以存储 Derived
的 char[]
似乎可以解决该问题:
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
class Foo { public: virtual void foo() {} };
class Base { public: virtual void base() {} };
class Derived: public Foo, public Base {};
int main() {
alignas (Derived) char const buffer[sizeof(Derived)] = {};
Derived const* const derived = reinterpret_cast<Derived const*>(buffer);
Base const* const base = derived;
ptrdiff_t const delta =
reinterpret_cast<char const*>(derived) -
reinterpret_cast<char const*>(base);
::printf("%td\n", delta);
return 0;
}
Is it a false-positive or does this code really have problems?
根据对标准的严格阅读,该代码确实表现出 UB,因此从这个意义上讲,它不是 false 肯定。实际上,boost 作者和编译器作者都同意这只是指针数学运算,所以无论如何它都应该做正确的事情。
编辑:除非涉及的碱基之一是virtual
。然后演员将尝试从 vtable 中读取偏移量。
编辑 2:使用 nullptr
产生 0。更改为使用本地对齐缓冲区。
boost::serialization
包含 this code:
reinterpret_cast<std::ptrdiff_t>(
static_cast<Derived *>(
reinterpret_cast<Base *>(1 << 20)
)
) - (1 << 20)
它的目的是计算基数和派生数之间的偏移量类。此代码是否没有未定义的行为?
我问的原因是ASAN+UBSAN抱怨。例如,这段代码
#include <iostream>
class Foo { public: virtual void foo() {} };
class Base { public: virtual void base() {} };
class Derived: public Foo, public Base {};
int main()
{
std::cout <<
(reinterpret_cast<std::ptrdiff_t>(
static_cast<Derived *>(
reinterpret_cast<Base *>(1 << 20)
)
) - (1 << 20));
}
编译为(gcc 版本 9.2.1)
g++ -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -g main.cpp
产生这个输出
AddressSanitizer:DEADLYSIGNAL
=================================================================
==72613==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000ffff8 (pc 0x0000004012d9 bp 0x7ffd5b3eecf0 sp 0x7ffd5b3eece0 T0)
==72613==The signal is caused by a READ memory access.
#0 0x4012d8 in main main.cpp:13
#1 0x7f74a90d5f42 in __libc_start_main (/lib64/libc.so.6+0x23f42)
#2 0x40112d in _start (/home/.../a.out+0x40112d)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV main.cpp:13 in main
是误报还是这段代码真的有问题?
2019 年 12 月 9 日更新: 根据 Filipp 的提议和我的实验,此代码似乎有效并且不会产生任何警告:
std::aligned_storage<sizeof(Derived)>::type data;
reinterpret_cast<char*>(&data)
- reinterpret_cast<char*>(
static_cast<Base*>(
reinterpret_cast<Derived*>(&data)));
有人发现此代码段有任何问题吗?如果没有,我会建议给 boost
.
2019 年 12 月 16 日更新: 将 was merged 修复到 boost::serialization
develop
分支。
reinterpret_cast<Base *>(1 << 20)
这不是一个有效的指针。
static_cast
需要对其进行评估,它具有未定义的行为。
这是一个有趣的 "trick",但它似乎没有明确定义,-fsanitize
选项的结果证实了这一点。
如另一个答案所示,问题是 (1 << 20)
不是任何对象的地址。使用原则上可以存储 Derived
的 char[]
似乎可以解决该问题:
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
class Foo { public: virtual void foo() {} };
class Base { public: virtual void base() {} };
class Derived: public Foo, public Base {};
int main() {
alignas (Derived) char const buffer[sizeof(Derived)] = {};
Derived const* const derived = reinterpret_cast<Derived const*>(buffer);
Base const* const base = derived;
ptrdiff_t const delta =
reinterpret_cast<char const*>(derived) -
reinterpret_cast<char const*>(base);
::printf("%td\n", delta);
return 0;
}
Is it a false-positive or does this code really have problems?
根据对标准的严格阅读,该代码确实表现出 UB,因此从这个意义上讲,它不是 false 肯定。实际上,boost 作者和编译器作者都同意这只是指针数学运算,所以无论如何它都应该做正确的事情。
编辑:除非涉及的碱基之一是virtual
。然后演员将尝试从 vtable 中读取偏移量。
编辑 2:使用 nullptr
产生 0。更改为使用本地对齐缓冲区。