【问题标题】:Is it undefined behavior to access members through a pointer to the first member of the class [duplicate]通过指向类的第一个成员的指针访问成员是否是未定义的行为[重复]
【发布时间】:2019-03-18 15:48:17
【问题描述】:

我正在玩弄一个类,我想用operator[] 对其进行索引,同时还能够访问这些字段。

我在我正在尝试做的事情下方附加了一个 MCVE,它能够通过变量本身访问成员变量,但也可以使用一些指针偏移量(例如:如果有一个 ab,然后我可以通过名称访问b,或者如果它们是相同的类型并且按顺序定位而没有填充,则可以通过&a + 1访问它。

我担心我会遇到未定义的行为并且不会知道。最初我试图做一个“与1)浮动成员和2)浮动数组的联合”,但我发现它是undefined behavior。如果我要在下面做的是未定义的行为但无法找到它,我尝试在标准中查找(这显然并不意味着它不存在,我很容易错过它)。

由于我也使用 CRTP 来执行此操作,因此我认为,因为我正在向自己强制转换,只要继承不提供任何成员就应该没问题。

为了确保这在 C++ 中可能是合法的,我添加了一堆静态断言:

  • 确保它是标准布局,因此我可以将 offsetof 用于其他静态断言 static_assert(std::is_standard_layout_v<Color>);
  • 确保它是微不足道的static_assert(std::is_trivial_v<Color>);
  • 确保偏移是连续的static_assert(offsetof(Color, r) == 0);static_assert(offsetof(Color, g) == sizeof(float));static_assert(offsetof(Color, b) == 2 * sizeof(float));
  • 确保没有从继承static_assert(sizeof(Color) == 3 * sizeof(float)); 向类添加任何内容

代码:

#include <iostream>

using namespace std;

template <typename T>
class ColorCRTP {
    T& getInstance() {
        return *static_cast<T*>(this);
    }

public:
    // Is it UB to do this when we set values from the
    // fields themselves in the actual class?
    float& operator[](size_t index) {
        // Assume the inheriting class *always* is only a
        // series of sequential members of the exact same
        // type.
        return *(&getInstance().r + index);
    }
};

struct Color : ColorCRTP<Color> {
    float r;
    float g;
    float b;

    Color() = default;
    Color(float r, float g, float b) : r(r), g(g), b(b) { }
};

// Do these help guarantee that I am not performing UB?
static_assert(std::is_standard_layout_v<Color>);
static_assert(std::is_trivial_v<Color>);
static_assert(offsetof(Color, r) == 0);
static_assert(offsetof(Color, g) == sizeof(float));
static_assert(offsetof(Color, b) == 2 * sizeof(float));
static_assert(sizeof(Color) == 3 * sizeof(float));

int main() {
    Color c{0.5f, 0.75f, 1.0f};

    c.g = 0.123f;        
    cout << c[1] << " = " << c.g << endl;

    c[1] = 0.321f; // This is legal or UB?
    cout << c[1] << " = " << c.g << endl;
}

我是否违反了标准并通过执行上述操作来调用未定义的行为?当然,假设没有提供超出范围的索引。

由于r 是第一个成员,我不知道6.7.2 part 4.3 是否让我更加放心,因为我以安全的方式引用了第一个成员。

【问题讨论】:

  • "// 这些是否有助于保证我没有执行 UB?" 不会。保证成员在您期望的位置并不能消除您拥有的事实未定义的行为。未定义的行为可能源于内存布局以外的事物,特别是优化。如果编译器可以确定某个分支包含未定义的行为,那么观察到的行为可能不是您所期望的。这只是一种情况。未定义的行为可以出于任何原因做任何事情。试图以可移植的方式限制其结果是不切实际的。

标签: c++ c++17 undefined-behavior


【解决方案1】:

你的程序的行为是未定义的。

指针算法只在数组中有效。并且rgb 不构成数组。

最好的办法是使用包含 3 个标签的 switch 块重新编码 float&amp; operator[](size_t)

【讨论】:

  • 爆炸,把我的锤子打14秒;)
  • @NathanOliver:磁盘很便宜。这个是给float的,“骗子”对我来说不够准确。很高兴有一个针对问题进行微调的答案。
  • @Bathsheba:将问题标记为重复不是为了节省磁盘空间。这是关于不分裂信息。如果答案存在于其他地方(它确实存在。floatdouble 没有明显的不同),那么这就是这个问题应该指出的地方。
  • @NicolBolas:当我在图书馆研究一个主题时,我喜欢看不止一本书。我们不会仅仅因为我们有马太福音就从圣经中删除马可、路加和约翰。是的,如果您研究链接页面,您就会非常清楚答案是什么,但同样我们可以创建一个规范的 C++ 标准问题并将所有问题链接到该问题。显然,这个限制是荒谬的,但你在哪里划清界限?同样,磁盘很便宜——在我看来,将页面合并为副本的想法是徒劳的,并且在许多方面损害了网站的运作。
  • 我喜欢这个答案,因为它为我提供了有效的定义行为和其他相关帖子所没有的问题的简单解决方案。同样对于上面的讨论:我总是在发布之前寻找重复并在这个地方搜索我的问题,并且相关的帖子从未出现在任何搜索中。在发布之前,我花了大量时间搜索互联网/SO,而相关的帖子从未出现过。如果有一种方法可以用问题标记元数据,那就太好了,因为这样可以节省我很多时间来写我的帖子。我很幸运能得到满意的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-21
  • 2020-03-13
  • 2018-01-03
  • 2018-03-24
  • 2011-11-10
  • 1970-01-01
  • 2011-05-17
相关资源
最近更新 更多