C++中从对象指针计算缓冲区已放置对象偏移量的合法性与正确方法
问题分析与解答
核心结论:代码中(b)行存在未定义行为(UB)
你对标准规则的理解方向是正确的:根据C++标准[expr.add]p5的要求,两个指针相减的合法前提是——它们必须指向同一个数组的元素(或数组末尾后一位),且指针类型与数组元素类型匹配。
这里buf是std::byte数组衰减得到的std::byte*,指向数组的第0个元素;而bp是通过reinterpret_cast将S*转换而来的std::byte*:虽然地址数值相同,但bp实际指向的是一个S对象的首字节,而非std::byte数组的元素。标准并不认为"落在数组内存范围内的其他类型对象的首字节"等同于"数组的元素",因此bp不属于buf这个std::byte数组的合法指针序列,二者相减的操作违反标准要求,属于未定义行为。
gcc的-fsanitize=undefined未发出诊断,是因为UB的检测覆盖并不完全,很多场景下 sanitizer 无法识别出所有UB,不能以此判定代码合法。
正确的无UB计算方法
有两种可靠的实现方式:
方法1:通过整数类型转换计算偏移(兼容性最好)
将指针转换为std::uintptr_t(标准定义的无符号整数类型,能够完整保存指针的地址值),通过整数相减得到偏移量:
#include <cstddef> #include <cstdint> #include <cstdio> #include <new> struct alignas(8) S { int x; }; int main() { alignas(8) std::byte buf[80]; S* const sp{new (buf + 24) S{42}}; // ... const std::uintptr_t buf_addr = reinterpret_cast<std::uintptr_t>(buf); const std::uintptr_t sp_addr = reinterpret_cast<std::uintptr_t>(sp); const ptrdiff_t off = static_cast<ptrdiff_t>(sp_addr - buf_addr); printf("%td\n", off); }
方法2:使用std::launder确保指针合法性(C++17及以上)
因为S是在std::byte数组的内存上构造的,而std::byte是平凡类型,原有数组元素的生命周期结束后,其内存仍可被视为std::byte类型的存储。通过std::launder可以获取指向对应std::byte数组元素的合法指针,再进行相减:
#include <cstddef> #include <cstdio> #include <new> #include <memory> // 包含std::launder struct alignas(8) S { int x; }; int main() { alignas(8) std::byte buf[80]; S* const sp{new (buf + 24) S{42}}; // ... std::byte* const bp = std::launder(reinterpret_cast<std::byte*>(sp)); const ptrdiff_t off = bp - buf; printf("%td\n", off); }
内容的提问来源于stack exchange,提问作者Cole A
相关产品推荐
相关产品推荐

