【问题标题】:reinterpreting array of doubles as array of std::complex<double>将双精度数组重新解释为 std::complex<double> 数组
【发布时间】:2021-12-04 01:13:20
【问题描述】:

虽然 C++11 标准将 std::complex&lt;double&gt; 重新解释为 doubles:

对于指向名为pcomplex&lt;T&gt; 数组元素的任何指针和 任何有效的数组索引ireinterpret_cast&lt;T*&gt;(p)[2*i] 都是实数部分 复数 p[i]reinterpret_cast&lt;T*&gt;(p)[2*i + 1] 是 复数的虚部p[i]

此要求的目的是保持二进制兼容性 C++ 库复数类型和 C 语言之间的关系 复数类型(及其数组),它们具有相同的 对象表示要求。

向后重新解释是真的吗?我的意思是执行这样的操作是否安全:std::complex&lt;double&gt; *cppComplexArray = reinterpret_cast&lt;std::complex&lt;double&gt; *&gt;(cDoublesArray) 其中cDoublesArray 的类型为double *,甚至长度为2 * n?如果它的长度是奇数 (2 * n + 1),有什么潜在的陷阱?

【问题讨论】:

  • “显然”,向后重新解释也应该起作用 - 如果双精度数组中有偶数个元素。但是您是否正在使用 C++ 标准中的某些措辞来寻找证据?如果有,请添加“language-lawyer”标签。
  • 很确定是否向前有效,向后也有效。
  • 如果std::complex 本身就是一个行为端正的类型……也许吧。否则,C++ 在其对象模型中有一些黑暗的角落(随着时间的推移而变化)。它可能是格式错误的 C++17 和有效的 C++20。不过,我不想尝试正式证明这一点。
  • 向后重新解释是真的吗?不,在指针运算中使用cppComplexArray会违反[expr.add]/6
  • C++11 标准这样说我打开了 C++11 草案timsong-cpp.github.io/cppwp/n3337/complex.numbers 并在那里找不到单词«intent»。如果您引用 cppreference,请不要说它是标准,因为它不是。

标签: c++ arrays language-lawyer complex-numbers


【解决方案1】:

向后重新解释是真的吗?我的意思是执行这样的操作是否安全:std::complex&lt;double&gt; *cppComplexArray = reinterpret_cast&lt;std::complex&lt;double&gt; *&gt;(cDoublesArray)

强制转换/初始化本身是安全的,使用结果好像指向 std::complex&lt;double&gt; 数组的元素不是。

cDoublesArray(或应用于它的数组到指针的转换,如果cDoublesArray表示doubles的数组)points到doubles数组的第一个元素, reinterpret_cast&lt;std::complex&lt;double&gt;*&gt;(cDoublesArray) 的作用相同(具有相同的值)。

在指针算术(例如cppComplexArray + 0)中使用std::complex&lt;double&gt;*类型的表达式,其值«指向double类型对象的指针»(如reinterpret_cast&lt;std::complex&lt;double&gt;*&gt;(cDoublesArray)cppComplexArray)将违反[expr.add]/6

对于加法或减法,如果表达式PQ 的类型为“pointer to cv T”,其中T 和数组元素类型不是similar ,行为未定义。

(Tstd::complex&lt;double&gt;,这里数组元素类型是double,它们不是similar)

【讨论】:

  • 有趣。指针运算的一般规则确实非常严格(即相同类型的指针和相同数组的范围内)。然而,这条规则不是没有回避有效重新解释转换的要求和“保持 C++ 库复数类型和 C 语言复数类型(及其数组)之间的二进制兼容性”的意图吗? (例如参见this analysis 第 4 节)
  • @Christophe 通常的 [expr.add] 规则是 ofc。回避,但仅适用于reinterpret_cast&lt;T*&gt;(p)[2*i]reinterpret_cast&lt;T*&gt;(p)[2*i + 1] 形式的表达式,其中p 是指向std::complex&lt;T&gt; 类型数组元素的指针,i 是整数类型的表达式。
  • 重新解释指针的一般规则不允许在其他表达式中使用获得的指针值吗?在复杂的特定情况下,这些原则的例外会导致不一致吗?
  • @Christophe 重新解释指针的一般规则不是允许在其他表达式中使用获得的指针值吗? 使用 reinterpret_cast&lt;T*&gt;(p) 任何你喜欢的东西,除了它可以有UB 在某些情况下。 在复杂的特定情况下这些原则的例外会导致不一致吗?与什么不一致?
  • 好的,似乎使用直接向后重新解释不是一种可移植且定义明确的方式。但是如果直接在重新解释为double * 源数组std::complex&lt;double&gt; x* 上进行操作(请参阅我对克里斯托夫的回答的评论)?似乎标准说直接操作应该没问题,因为打算在 c 和 c++ 中支持复杂数据类型的相同对象表示,并且在从 c99(我使用 c11)开始的 C 中,复杂值存储为两个相邻的子值.我尝试从 C 函数修改 double * 数组,原始 C++ 复杂数据已更改
【解决方案2】:

在实践中,鉴于前向重新解释强加的强约束,向后重新解释可能大部分时间都有效(参见 cppreference,std::complex,实施说明)。

但是,我不完全确定这种向后重新解释在理论上是否总是有效:

  • 让我们想象一个复杂库的荒谬和假设实现,它将维护活动复杂对象的地址列表(例如,用于调试目的)。这个(可能是静态的)列表将由复杂的构造函数和析构函数维护。
  • 此库中的每个复杂操作都会验证其操作数是否在列表中。
  • 虽然前向重新解释可以工作(复杂对象构造良好,并且它的部分可以用作双精度),但向后重新解释将不起作用(例如,尽管布局兼容,但您会将一对双精度重新解释为复杂,如果您'会对它们执行任何复杂的操作,它会失败,因为复杂的构造不正确,即它的地址不在列表中)。

如前所述,这个复杂的库可能是一个愚蠢的想法。但是这样的库可以实现并符合标准规范。这足以证明理论上不存在反向保证。

假设我们将有一个反向重新解释起作用的实现,那么您问题的最后一点更容易回答。缺少最后一列将导致访问超出范围的部分。因此,这将导致 UB。

其他阅读材料:标准委员会的 working paper 要求提供一个通用的可转换功能,该功能将概括 reinterpret_cast 对其他类型的 complex 的行为。它解释了第 4 节中的复杂情况,以及如果 complex 本身不是由数组实现的,编译器需要进行特殊处理才能使其工作。

【讨论】:

  • 好的,我认为,最好将给定的doubles 逐个元素复制到手动创建的复杂值数组中。
  • 顺便说一句,如果我要创建复杂数组的“双重表示”,例如。 G。 double *dblArr = reinterpret_cast&lt;double *&gt;(cmplxArr) 然后将此指针传递给外部函数,例如将结果存储在其中的 C 函数 - 是否会修改原始复杂存储中的数据?我的意思是,如果原始 cmplxArr 包含 3 个复数 double 值(1.0+1.0i、-2.5+4.0i、1.2-0.1i)并且 C 函数只是将给定的 dblArr 乘以 2,那么 cmplxArr 的内容会是像那样(2.0+2.0i,-5.0+8.0i,2.4-0.2i)?
  • 刚刚测试过,它运行良好,但是,我不确定它是否是可移植的和明确定义的方式。 UPD:重新阅读标准的报价,似乎任何 valid 索引和指针的行为都应该是明确定义的
  • @DrobotViktor 是的,如果您将指针传递给双精度数组并且您的函数修改了指向对象的内容,则原始数据将被修改。这没关系:这是重新解释保证的结果,所以你可以完全依赖它。
  • @LanguageLawyer *严格来说,C 对象 ≠ C++ 对象,C 指针 ≠ C++ 指针等。 * C 和 C++ 代码的混合是并且将永远是 C/C++ 世界的一个特征。对于函数,有extern "C",通常,采用结构参数的 C 函数也定义结构。如果 C++ 代码现在实例化这样的结构并将指向 C 函数的指针作为参数传递,这也是 100% 可以的。你的措辞没有反映这一点。
猜你喜欢
  • 2011-07-07
  • 1970-01-01
  • 1970-01-01
  • 2015-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多