Joh*_*nck 7 c++ pointer-arithmetic undefined-behavior language-lawyer
我有一个结构数组,我有一个指向其中一个结构的成员的指针.我想知道数组的哪个元素包含该成员.这有两种方法:
#include <array>
#include <string>
struct xyz
{
float x, y;
std::string name;
};
typedef std::array<xyz, 3> triangle;
// return which vertex the given coordinate is part of
int vertex_a(const triangle& tri, const float* coord)
{
return reinterpret_cast<const xyz*>(coord) - tri.data();
}
int vertex_b(const triangle& tri, const float* coord)
{
std::ptrdiff_t offset = reinterpret_cast<const char*>(coord) - reinterpret_cast<const char*>(tri.data());
return offset / sizeof(xyz);
}
Run Code Online (Sandbox Code Playgroud)
这是一个测试驱动程序:
#include <iostream>
int main()
{
triangle tri{{{12.3, 45.6}, {7.89, 0.12}, {34.5, 6.78}}};
for (const xyz& coord : tri) {
std::cout
<< vertex_a(tri, &coord.x) << ' '
<< vertex_b(tri, &coord.x) << ' '
<< vertex_a(tri, &coord.y) << ' '
<< vertex_b(tri, &coord.y) << '\n';
}
}
Run Code Online (Sandbox Code Playgroud)
两种方法都产生了预期的结果:
0 0 0 0
1 1 1 1
2 2 2 2
Run Code Online (Sandbox Code Playgroud)
但它们是有效的代码吗?
特别是我想知道是否vertex_a()可能通过强制转换float* y来调用未定义的行为,xyz*因为结果实际上并没有指向a struct xyz.这种担忧促使我写作vertex_b(),我认为这是安全的(是吗?).
这是GCC 6.3使用-O3生成的代码:
vertex_a(std::array<xyz, 3ul> const&, float const*):
movq %rsi, %rax
movabsq $-3689348814741910323, %rsi ; 0xCCC...CD
subq %rdi, %rax
sarq $3, %rax
imulq %rsi, %rax
vertex_b(std::array<xyz, 3ul> const&, float const*):
subq %rdi, %rsi
movabsq $-3689348814741910323, %rdx ; 0xCCC...CD
movq %rsi, %rax
mulq %rdx
movq %rdx, %rax
shrq $5, %rax
Run Code Online (Sandbox Code Playgroud)
两者都不符合标准.
在vertex_a,你被允许将指针转换为xyz::x指针,xyz因为它们是指针可互换的:
如果一个是标准布局类对象而另一个是该对象的第一个非静态数据成员,则两个对象a和b是指针可互换的 [...]
如果两个对象是指针可互换的,那么它们具有相同的地址,并且可以通过a获得指向另一个指针的指针
reinterpret_cast.
但是你不能从指向指针xyz::y的指针进行转换xyz.该操作未定义.
在vertex_b,你要减去两个指针const char.该操作在[expr.add]中定义为:
如果表达式
P和Q指向,分别,元件x[i]和x[j]同一阵列对象的x,表达P - Q具有值i ? j; 否则,行为未定义
您的表达式不指向数组的元素char,因此行为未定义.