使用强制转换为"wrong"类型的指针算术
Pointer arithmetic using cast to "wrong" type
我有一个结构数组,我有一个指向其中一个结构的成员的指针。 我想知道数组的哪个元素包含该成员。 以下是两种方法:
#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);
}
下面是一个测试驱动程序:
#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';
}
}
这两种方法都能产生预期的结果:
0 0 0 0
1 1 1 1
2 2 2 2
但它们是有效的代码吗?
特别是我想知道vertex_a()
是否可能通过将float* y
转换为xyz*
来调用未定义的行为,因为结果实际上并不指向struct xyz
。 这种担忧促使我写了vertex_b()
,我认为这是安全的(是吗?
以下是使用 -O3 的 GCC 6.3 生成的代码:
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
根据标准,两者都无效。
在vertex_a
中,您可以将指针转换为指向xyz::x
指向xyz
的指针,因为它们是指针可相互转换的:
两个对象a和b是指针可相互转换的,如果 [...] 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员 [...]
如果两个对象是指针可相互转换的,则它们具有相同的地址,并且可以通过
reinterpret_cast
从指向另一个对象的指针获取指向一个对象的指针。
但是您不能从指针到xyz::y
到指针到xyz
进行强制转换。该操作未定义。
在vertex_b
中,您减去两个指向const char
的指针。该操作在 [expr.add] 中定义为:
如果表达式
P
和Q
分别指向同一数组对象的元素x[i]
和x[j]
x
,则表达式P - Q
的值为i − j
;否则,行为未定义
您的表达式不指向char
数组的元素,因此行为是未定义的。
vertex_a
确实违反了严格的混叠规则(您的float
都不是有效的xyz
s,并且在您的示例中,50% 的示例中,即使没有填充,它们甚至不在xyz
的开头)。
vertex_b
依赖于,可以说,对标准的创造性解释。虽然你对const char*
的投射是合理的,但在数组的其余部分使用它执行算术会更狡猾一些。从历史上看,我得出的结论是,这种东西有未定义的行为,因为在这种情况下,"对象"是xyz
,而不是数组。但是,我现在倾向于其他人的解释,即这总是有效的,并且在实践中不会期望其他任何事情。
vertex_b
完全没问题。您可能只需要优化return offset / sizeof(xyz);
,因为您将std::ptrdiff_t
与std::size_t
除法并将结果隐式转换为int
。 根据书本,此行为是实现定义的。std::ptrdiff_t
是有符号的,std::size_t
无符号的,除法的结果可能大于在某些平台/编译器上具有巨大数组大小的INT_MAX
(非常不可能)。
为了消除你的烦恼,你可以放assert()
和/或#error
来检查PTRDIFF_MIN
、PTRDIFF_MAX
、SIZE_MAX
、INT_MIN
和INT_MAX
,但我个人不会打扰这么多。
也许更健壮的方法涉及将类型签名更改为xyz::T*
(T
是一个模板参数,因此您可以根据需要采用xyz::x
或xyz::y
)而不是float*
然后,您可以使用offsetof(struct xyz,T)
自信地计算结构开始的位置,以便对其定义的未来更改更具弹性。
然后其余的就像你目前所做的那样:一旦你有一个指向结构开头的指针,在数组中找到它的偏移量是一个有效的指针减法。
这涉及到一些指针的肮脏。但这是一种使用的方法。例如,请参阅 Linux 内核中的 container_of() 宏。 https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/067/6717/6717s2.html
- 非类型指针和引用模板参数,以及在编译时如何/为什么解析它们.c++
- 如何访问在 c++ 中在类内声明的结构类型指针变量?
- (C )找到基本类型指针的儿童类型
- 表达式必须具有指向对象的指针类型(指针向量)
- 将子项复制构造到父类型指针中
- C 返回类型指针声明
- 调用虚拟函数而不通过类类型指针创建任何对象
- C++ 如何使用类类型指针制作向量
- 为什么基类型指针不能获取派生类对象的地址值?
- 如何在 C++ 中获取映射类型指针,映射
- 如何检查该类型的类型指针是正确对齐的
- 正在转换为短类型指针
- 指向任意类方法的模板非类型指针
- 如何通过强制转换类型指针将字符数组转换为uint16_t
- 指向shared_ptr的不透明类型 C 指针
- C++:从值类型指针强制转换为包含迭代器
- C/C++:访问给定类型指针的位置与访问另一类型指针的相同位置不同
- 比较类型指针
- 数据类型指针使用*(Datatype *)
- 如何打印c++中char类型指针的所有值