C++ 和 reinterpret_cast 中的越界数组访问

Out of bounds array accesses in C++ and reinterpret_cast

假设我有这样的代码

struct A {
  int header;
  unsigned char payload[1];
};

A* a = reinterpret_cast<A*>(new unsigned char[sizeof(A)+100]);

a->payload[50] = 42;

这是未定义的行为吗?创建指向 payload 外部的指针应该是未定义的 AFAIK,但我不确定在我在数组之后分配内存的情况下是否也是如此。

标准说 p[n]*(p+ n) 和 "if the expression P poinst to the i-th element of an array object, the expressions (P)+N point to the i+n-th elements of the array" 相同。在示例中,payload 指向数组中分配了 new 的元素,所以这可能没问题。

如果可能的话,如果您的答案包含对 C++ 标准的引用就更好了。

所以 reinterpret_cast 未定义的行为,我们可以 reinterpret_castcharunsigned char 我们永远不能投一个charunsigned char,如果我们这样做:

Accessing the object through the new pointer or reference invokes undefined behavior. This is known as the strict aliasing rule.

所以是的,这违反了严格的别名规则。

考虑代码:

struct {char x[4]; char a; } foo;

int work_with_foo(int i)
{
  foo.a = 1;
  foo.x[i]++;
  return foo.a;
}

尽管程序会 "own" 存储在 foo.x+4,但事实上 通过数组类型的访问仅针对前四个元素定义 允许编译器将上面的代码替换为 以下之一:

int work_with_foo(int i)  { foo.a = 1; foo.x[i]++; return 1; }

int work_with_foo(int i)  { foo.x[i]++; foo.a = 1; return 1; }

上述替换在标准下是明确允许的。这是 不太清楚写增量的替代方法会强制 编译器的行为就像它重新加载 foo.a。例如,我认为 当 i 等于 foo.a 的偏移量,我认为同样应该如此 *(i+(char*)&foo.x)+=1; 但我不确定 *(i+foo.x)+=1; 或者 *(i+(char*)foo.x)+=1;.

在 C++ 中永远不需要这种老式的 C hack。

考虑:

#include <cstdint>
#include <utility>
#include <memory>

template<std::size_t Size>
struct A {
  int header;
  unsigned char payload[Size];
};

struct polyheader
{
  struct concept
  {
    virtual int& header() = 0;
    virtual unsigned char* payload() = 0;
    virtual std::size_t size() const = 0;
    virtual ~concept() = default;  // not strictly necessary, but a reasonable precaution
  };

  template<std::size_t Size>
  struct model : concept
  {
    using a_type = A<Size>;
    model(a_type a) : _a(std::move(a)) {}
    int& header() override {
      return _a.header;
    }

    unsigned char* payload() override {
      return _a.payload;
    }

    std::size_t size() const override {
      return Size;
    }

    A<Size> _a;
  };

  int& header() { return _impl->header(); }
  unsigned char* payload() { return _impl->payload(); }
  std::size_t size() const { return _impl->size(); }

  template<std::size_t Size>
  polyheader(A<Size> a) 
    : _impl(std::make_unique<model<Size>>(std::move(a)))
    {}

  std::unique_ptr<concept> _impl;
};


int main()
{
  auto p1 = polyheader(A<40>());
  auto p2 = polyheader(A<80>());

}