【问题标题】:is the pointerdiff between members of different objects in containers the same?容器中不同对象的成员之间的指针差异是否相同?
【发布时间】:2021-01-12 23:01:30
【问题描述】:

c++ 宏 offsetof 只是在标准布局类型上使用时定义的行为。据我了解,这是因为编译器可以根据其运行的代码的上下文更改数据的内存布局。 (例如从不使用变量时)

但是,我想知道存储在一个范围中的所有元素是否共享相同的布局。或者,换句话说,如果以下代码定义明确:

template<typename T>
concept has_member_int = requires(const T& x)
{
  { x.member } -> std::same_as<int>;
};

template <std::ranges::Range Range_t, has_member_int T>
void setEveryMemberTo20(Range_t<T> range)
{
  if (range.size() > 0)
  {
    auto& firstElement = *(range.begin());
    
    auto ptrdiffToMember = &(firstElement.member) - &firstElement;
    
    for (auto& element : range)
    {
      *(reinterpret_cast<int*>(&element + ptrdiffToMember)) = 20;
    }
  }
}

【问题讨论】:

  • element.member = 20; 有什么问题?您是否对类型可能被擦除或成员变量的名称未知,并且由于某种原因指向成员的指针不可行的情况感兴趣?
  • @RemyLebeau 是的,但数组是一种特殊的容器,将所有数据连续存储在内存中。
  • @alterigel 显然我发布的代码是一个最小的可重现示例,说明了我的问题
  • @user3520616 是否在一个连续的数组中,这不会改变我所说的。
  • 编译器不会删除未使用的结构成员。在很多实际情况下,这样的事情往往会中断——例如,如果链接了一个新的对象文件,而该文件现在使用了数据结构中以前未使用的成员。消除未使用的变量往往会发生,因为有关变量使用的所有信息都在(例如)单个函数中可见。对于跨多个编译单元使用的数据结构,情况并非如此。

标签: c++ layout c++20 offsetof


【解决方案1】:

我想知道存储在一个范围中的所有元素是否共享相同的布局

当然可以,否则遍历多个相同类型的元素并访问每个元素的相同成员是不可能的。给定成员的偏移量是相对于元素类型的。对于该类型的 all 实例,该偏移量是相同的。因此,一个类型中所有成员的组合构成了该类型的布局,并且该布局在该类型的所有用途中保持一致。

但是,您对成员偏移量的处理都是错误的。您通过从 int* 指针中减去 T* 指针来计算偏移量,

  • 甚至不应该编译,因为你不能减去不同类型的指针。
  • 即使编译成功,也不会在T 内为您提供member 的正确字节 偏移量。

然后您将该偏移量应用于T* 指针,这将使指针前进那么多T 实例,而不是那么多字节。 IOW,如果memberT 中的偏移量为4,则您将T* 指针前进sizeof(T) * 4 字节,而不仅仅是4 个字节。

我认为您需要重新了解指针算法的实际工作原理。

试试类似的方法:

auto& firstElement = *(range.begin());
// or: T& firstElement = ...
    
auto ptrdiffToMember = reinterpret_cast<uintptr_t>(&firstElement.member) - reinterpret_cast<uintptr_t>(&firstElement);
// or: auto ptrdiffToMember = offsetof(T, member);
    
for (auto& element : range)
{
    *(reinterpret_cast<int*>(reinterpret_cast<uintptr_t>(&element) + ptrdiffToMember)) = 20;
}

但是,正如 @alterigel 在 cmets 中所说,只需使用 element.member = 20; 代替。您根本不需要处理指针操作:

template <std::ranges::Range Range_t, has_member_int T>
void setEveryMemberTo20(Range_t<T> range)
{
  for (auto& element : range)
  {
    element.member = 20;
  }
}

【讨论】:

  • 数组是一种特殊的容器,将所有数据连续存储在内存中。例如,您的解释不适用于std::map。最后一段:显然我发布的代码是一个最小的可重现示例,说明了我的问题
  • "例如,您的解释不适合 std::map" - 是的,确实如此。无论使用哪种类型的容器,给定类型中给定成员的字节偏移量在该类型的所有实例中始终相同。
  • 好的,但你也说过我可以使用 "offsetof" 。但是在标准中它说 offsetof 仅对标准布局类型有效。难道我的例子仍然存在,因为它是在严格的背景下吗?成员的偏移量在这个范围内是否有效?
  • @user3520616 你没有错,从技术上讲,将offsetof 与非标准布局类型一起使用在 C++17 之前具有未定义的行为。那么,在实践中,你可能会得到错误的答案,或者更糟。这不是因为不同实例的成员偏移量可能不同(它们不能);这只是因为在这些情况下,该标准不需要实现来提供明确定义的offsetof。一位委员会成员猜测,这将是一项严格的要求,实施起来太难了。
猜你喜欢
  • 2019-06-28
  • 2014-12-29
  • 2012-11-14
  • 2011-10-14
  • 1970-01-01
  • 2015-11-01
  • 2021-08-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多