【问题标题】:Is it legal to cast a pointer to array reference using static_cast in C++?在 C++ 中使用 static_cast 转换指向数组引用的指针是否合法?
【发布时间】:2014-01-23 19:03:32
【问题描述】:

我有一个指针T * pValues,我想将其视为T (&values)[N]

在这个 SO 答案 https://stackoverflow.com/a/2634994/239916 中,建议的做法是

T (&values)[N] = *static_cast<T(*)[N]>(static_cast<void*>(pValues));

我对此的担忧是。在他的示例中,pValues 是通过以下方式初始化的

T theValues[N];
T * pValues = theValues;

我的问题是,如果pValues 来自以下任何构造,则强制转换构造是否合法:

1:

T theValues[N + M]; // M > 0
T * pValues = theValues;

2:

T * pValues = new T[N + M]; // M >= 0

【问题讨论】:

  • *static_cast&lt;T(*)[N]&gt;(static_cast&lt;void*&gt;(pValues)) 似乎在滥用语言的功能,而不是您真正想要使用的东西,而是一种丑陋的解决方法......
  • 您在 1 和 2 中的内存都是动态的 - 也就是说,指针是指向内存块起点的实际指针,而不是已衰减为指针的数组。好像是UB。

标签: c++ arrays casting type-safety static-cast


【解决方案1】:

简短的回答:你是对的。仅当 pValues 的类型为 T[N] 并且您提到的两种情况(不同大小,动态分配的数组)很可能导致未定义的行为时,强制转换才是安全的。


static_cast 的好处是在编译时进行了一些额外的检查,所以如果你做错了什么,编译器会抱怨它(与丑陋的 C 风格转换相比,它允许你做几乎任何东西),例如:

struct A { int i; };
struct C { double d; };

int main() {
    A a;
    // C* c = (C*) &a; // possible to compile, but leads to undefined behavior
    C* c = static_cast<C*>(&a);
}

会给你:invalid static_cast from type ‘A*’ to type ‘C*’

在这种情况下,您转换为void*,从编译时可以进行的检查的角度来看,这对几乎任何东西都是合法的,反之亦然:void* 也可以转换回几乎任何东西,这首先使用 static_cast 完全没用,因为这些检查变得无用。

对于前面的例子:

C* c = static_cast<C*>(static_cast<void*>(&a));

不比:

C* c = (C*) &a;

并且很可能会导致此指针的错误使用和未定义的行为


换句话说:

A arr[N];
A (&ref)[N] = *static_cast<A(*)[N]>(&arr);

是安全的,很好。但是一旦你开始滥用static_cast&lt;void*&gt;,就根本无法保证实际会发生什么,因为即使是这样的东西:

C *pC = new C;
A (&ref2)[N] = *static_cast<A(*)[N]>(static_cast<void*>(&pC));

成为可能。

【讨论】:

  • 好的,但这并不能完全回答我的问题,因为您要介绍两种类型(A 和 C),而我问的是有一种类型(非指针非引用)的情况) 类型 T 参与其中。
  • 恕我直言,静态强制转换为 void* 按照标准应该是非法的。
  • A (&amp;ref)[N] = *static_cast&lt;A(*)[N]&gt;(&amp;arr);:static_cast 没有做任何事情。 &amp;arr 已有该类型。
【解决方案2】:

由于 C++17 至少显示的表达式是不安全的,即使 pValues 是指向数组第一个元素的指针并且数组的类型完全匹配(包括 excat 大小),无论是从变量声明或对new 的调用。 (如果不满足这些条件,无论以下情况都是UB。)

数组和它们的第一个元素不是pointer-interconvertible,因此reinterpret_cast(相当于两个static_castsvoid*)不能将一个指针值转换为指针值其他的。

因此static_cast&lt;T(*)[N]&gt;(static_cast&lt;void*&gt;(pValues)) 仍将指向数组的第一个元素,而不是数组对象本身。

由于类型/值不匹配,因此引用此指针是未定义的行为。

这可以通过std::launder 进行潜在补救,这可能会更改reinterpret_cast 不能更改的指针值。具体而言,以下可能是明确定义的:

T (&values)[N] = *std::launder(static_cast<T(*)[N]>(static_cast<void*>(pValues)));

或等效

T (&values)[N] = *std::launder(reinterpret_cast<T(*)[N]>(pValues));

但前提是 std::launder 返回的指针不能用于访问无法通过原始 pValues 指针访问的任何字节。如果数组是一个完整的对象,这是满足的,但是例如如果数组是二维数组的子数组,则不满足。

有关确切的可达性条件,请参阅https://en.cppreference.com/w/cpp/utility/launder

【讨论】:

    猜你喜欢
    • 2017-11-30
    • 2021-07-03
    • 1970-01-01
    • 1970-01-01
    • 2020-05-04
    • 1970-01-01
    • 2017-01-19
    • 2021-02-14
    • 1970-01-01
    相关资源
    最近更新 更多