这个问题是关于使用带有结构偏移的指针算法派生的指针。
考虑以下计划:
#include <cstddef>
#include <iostream>
#include <new>
struct A {
float a;
double b;
int c;
};
static constexpr auto off_c = offsetof(A, c);
int main() {
A * a = new A{0.0f, 0.0, 5};
char * a_storage = reinterpret_cast<char *>(a);
int * c = reinterpret_cast<int *>(a_storage + off_c));
std::cout << *c << std::endl;
delete a;
}
该程序似乎可以工作,并使用默认设置和C ++ 11标准在我测试的编译器上给出预期结果。
(一个密切相关的计划,我们使用void *
代替char *
和static_cast
代替reinterpret_cast
,
并未被普遍接受。 gcc 5.4
发出有关使用void指针的指针算法的警告,clang 6.0
表示该指针
使用void *
进行算术是一个错误。)
根据C ++标准,该程序是否具有明确定义的行为?
答案取决于实施是否已放宽或严格指针安全([basic.stc.dynamic.safety]
)?
答案 0 :(得分:8)
您的代码中没有根本错误。
如果A
不是普通旧数据,则上面是UB(在C ++ 17之前)并且有条件地支持(在C ++ 17之后)。
您可能希望将char*
和int*
替换为auto*
,但这是一种风格。
请注意,指向成员的指针以类型安全的方式完成相同的操作。大多数编译器实现了一个指向成员的指针...作为类型中成员的偏移量。然而,它们确实在任何地方工作,甚至在非吊舱结构上也是如此。
除此之外:我没有看到标准中offsetof
为constexpr
的保证。 ;)
无论如何,请替换:
static constexpr auto off_c = offsetof(A, c);
与
static constexpr auto off_c = &A::c;
和
auto* a_storage = static_cast<char *>(a);
auto* c = reinterpret_cast<int *>(a_storage + off_c));
与
auto* c = &(a->*off_c);
用C ++方式做。
答案 1 :(得分:4)
在您的具体示例中这是安全的,但仅因为您的结构是标准布局,您可以使用std::is_standard_layout<>
仔细检查。
尝试将其应用于结构,例如:
struct A {
float a;
double b;
int c;
std::string str;
};
即使字符串超出了相关结构的一部分,也是非法的。
修改
下面是关于abt的内容:在3.7.4.3 [basic.stc.dynamic.safety]中它表示只有在(条件)时才能安全地导出指针,并且如果我们有严格的指针安全性,那么指针无效,如果它不是来自这样的地方。在5.7指针算术中,它说我可以在数组中进行通常的算术,但我没有看到任何告诉我结构偏移算术是可以的。我试图弄清楚这是否与我认为的方式无关,或者假设的“严格”推算中结构偏移算术是否正常,或者我是否读错了5.7(n4296)
当您执行指针算术时,您在char
的数组上执行它,其大小至少为sizeof(A)
,所以没关系。
然后,当你回到第二个成员时,你将被(2.4)所涵盖:
- 安全派生指针值的明确定义的指针转换(4.10,5.4)的结果;
答案 2 :(得分:1)
你应该检查你的假设。
假设#1)offsetof以字节为单位给出正确的偏移量。只有当类被认为是“标准布局”时才有保证,它有许多限制,例如没有虚拟方法,避免多重继承等。在这种情况下,应该没问题,但一般情况下你不能一定不会。
假设#2)char与字节大小相同。在C中,这是定义,所以你是安全的。
假设#3)offsetof给出了指向类的指针的正确偏移,而不是从数据的开头。这与#1基本相同,但vtable肯定是个问题。同样,只适用于标准布局。