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_cast
到 char
或 unsigned char
我们永远不能投从一个char
或unsigned 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>());
}
假设我有这样的代码
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_cast
到 char
或 unsigned char
我们永远不能投从一个char
或unsigned 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>());
}