提问人:Lukas Lang 提问时间:5/25/2023 最后编辑:Lukas Lang 更新时间:5/26/2023 访问量:127
通过指向第一个成员的指针访问未知大小的类成员数组
Access class member array of unknown size through pointer to first member
问:
我希望能够处理指向具有未知大小的数组成员的对象的指针,并通过指向其公共第一个成员的类型擦除指针访问该数组。我目前的尝试如下:
#include <cstddef>
struct node_base
{
node_base* next;
int size;
};
template <int n>
struct node
{
node_base base;
char data[n];
node() : base{nullptr, n} {
static_assert(offsetof(node, data) == sizeof(node_base));
}
};
void process_queue(node_base* head)
{
while (head)
{
for (int i = 0; i < head->size; ++i)
{
*(reinterpret_cast<char*>(reinterpret_cast<char*>(head) + sizeof(node_base)) + i) = i;
}
head = head->next;
}
}
int main()
{
node<3> a{};
node<4> b{};
node<2> c{};
c.base.next = &b.base;
a.base.next = &c.base;
process_queue(&a.base);
return a.data[2] + c.data[1];
}
此代码构建了一个类似队列的结构(节点,并以“a -> c -> b”的形式相互指向),并将指向第一个元素的指针传递给 。然后,该函数将遍历队列并访问直接存储在成员之后的数组,并写入值 ...到其条目中。a
b
c
process_queue
node<n>::data
node_base
0
n-1
挑战在于节点具有不同的类型,因此队列的指针指向实际节点的成员,我需要一些东西才能从那里获取实际数据。next
node_base
尽管这似乎在成功返回期望值 3 的意义上起作用 (Godbolt),但我不确定这是否被允许。
问题
假设我通过某种方法知道指针指向具有大小数组的对象的第一个成员,通过上面的代码访问数组的元素是否合法?如果不是,它可以在不扩大规模的情况下合法化吗?cur
cur->size
node<n>::data
sizeof(node<n>)
答:
严格按照当前标准,它已经是未定义的行为,因为这里的指针算术:
reinterpret_cast<char*>(head) + sizeof(node_base)
未定义。 是指向对象的指针,该对象不能与该位置的任何对象进行指针互换。因此,也将是指向同一对象的指针。因此,指针算术是未定义的,因为表达式 () 的指向类型与指向对象 () 的实际类型不相似。head
node_base
char
reinterpret_cast<char*>(head)
node_base
char
node_base
但是,您在此处强制转换的目的是更改指针值。您打算获取指向对象的对象表示形式的指针。强制转换为通常用于访问对象表示形式,但该标准实际上并未提供此功能。char*
node<n>
char*
提案 P1839 试图将这种预期行为纳入标准。由于多种原因,由于以下几个原因,它目前在修订P1839R5中的措辞仍然无法使您的程序得到很好的定义:
首先,因为只有才能获得指向对象表示的指针,如提案的限制部分所述。reinterpret_cast<unsigned char*>
即使有 ,该提案下仍然存在问题:unsigned char
您的类恰好是标准布局。这是实现这一目标的必要条件。如果它们不是标准布局,那么通常没有任何方法可以从一个成员的指针转到另一个成员的指针。
但是,标准布局可以保证对象与其第一个成员子对象的指针可互换。因此,根据该建议,是否将生成指向成员或对象的对象表示的第一个元素的指针是悬而未决的。这在提案中被指出为一个悬而未决的问题。node<n>
reinterpret_cast<unsigned char*>(head)
node_base
node<n>
但是,假设它确实生成了指向对象的对象表示的指针,那么下一个问题是是否也指向数组成员的对象表示。我不确定该提案的意图是什么。node<n>
reinterpret_cast<unsigned char*>(head) + sizeof(node_base)) + i
char
node<n>
但即使这不是问题,该提案也只定义了如何从对象表示中读取。写信给它超出了范围,根据提案仍然是UB。
因此,至少需要保留外部并将其包装在调用中,以便获取指向对象本身的指针(而不是其对象表示或对象的对象表示)。reinterpret_cast<char*>
std::launder
char
node<n>
评论
std::aligned_storage
node_base
data
+sizeof(node_base)
data
node_base
node<n>
std::launder
new (addr) ...
addr
std::byte
unsigned char
std::launder
new
std::launder
std::aligned_storage
unsigned char
std::byte
alignas
std::aligned_storage
std::launder
const
评论
static_cast<derived<5>*>(p)->c[2]
n
n
std::vector
std::array
base* p = &d;
是相当没有意义的。对于任何操作,您始终需要向下转换为,但访问 .但是你不能问自己它是否是一个(没有虚拟),所以你需要将这些信息存储在其他地方。总而言之,你正在做的事情并不比.p
derived
p[0]
p
derived
void* b = d;