漏洞?MATLAB MEX 更改默认逻辑的类型

Nun*_*uno 7 matlab fortran mex gfortran intel-fortran

当通过 MEX 将一段 Fortran 2003(或更高版本)代码与 MATLAB 连接时,我惊讶地发现MEX 更改了默认逻辑. 这是致命的,因为一段完美编译的 Fortran 代码可能会因为类型不匹配而无法被 mexified,这种情况在我的项目中确实发生过。

\n

这是一个最小的工作示例。

\n

将以下代码命名为“test_kind.F”,在MATLAB中编译mex test_kind.F,然后test_kind在MATLAB中运行。这将生成一个名为 fort.99 的纯文本文件,其中包含两个数字“4”和“8”作为 WRITE 指令的结果。

\n
! test_kind.F\n! Tested by MATLAB 9.8.0.1323502 (R2020a) with GNU Fortran (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0\n\n#include "fintrf.h"\n\n      subroutine mexFunction(nlhs, plhs, nrhs, prhs)\n\n      use ieee_arithmetic, only : ieee_is_nan\n      implicit none\n      mwPointer plhs(*), prhs(*)\n      integer nlhs, nrhs\n\n      write(99, *) kind(ieee_is_nan(1.0))  ! This prints a number in fort.99\n      write(99, *) kind(.false.)  ! A benchmark, which should print the same number in fort.99\n      close(99)\n\n      end subroutine mexFunction\n
Run Code Online (Sandbox Code Playgroud)\n

我认为这两个打印的数字应该总是彼此相等,尽管具体值取决于编译器并且不需要是 4 或 8。(正如 Fortran 博士 @SteveLionel 所强调的,Fortran 标准在这些数字之间没有强加任何关系以及用于表示数据的字节数。请参阅 Steve 的博客Doctor Fortran,位于 \xe2\x80\x9cIt Takes All KINDs\xe2\x80\x9d,以获取有关此主题的详细阐述。)

\n

[更新尽管Fortran 2018 标准是这样建议的,但上述推测结果是错误的。kind(ieee_is_nan(1.0)=kind(.false.)使用某些编译器选项,kind(ieee_is_nan(1.0)kind(.false.)实际上可能彼此不同,因此违反了 Fortran 标准中的规范。请参阅@francescalus 的答案和我在本问题末尾的总结。]

\n

[更新:在原来的问题中,我翻转了“4”和“8”,所以我的想法kind(ieee_is_nan(1.0))被改变了;事实上,kind(.false.)从 4 变为 8,而kind(ieee_is_nan(1.0))始终保持 4。@francescalus 的精彩回答充分解释了一切,谢谢!]

\n

为了进行比较,这里是未与 MATLAB 交互的相同代码,当使用 gfortran 编译时,它在屏幕上打印“4”和“4”。使用 nagfor 编译器,数字虽然变成了 3,但仍然相等。

\n
! test_kind.f90\n! Tested by GNU Fortran (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0\n\nprogram test_kind\n\nuse ieee_arithmetic, only: ieee_is_nan\nimplicit none\n\nwrite(*, *) kind(ieee_is_nan(1.0))  ! This prints a number on STDOUT (screen)\nwrite(*, *) kind(.false.)   ! A benchmark, which should print the same number on STDOUT (screen)\n\nend program test_kind\n
Run Code Online (Sandbox Code Playgroud)\n

以下是Fortran 2018 标准ieee_is_nan中的部分,供您参考。它指定返回一个“默认逻辑”,我猜它应该与内在函数或常量的类型相同——或者我弄错了?ieee_is_nan.true..false.

\n
17.11.13 IEEE_IS_NAN (X)\n1 Description. Whether a value is an IEEE NaN.\n2 Class. Elemental function.\n3 Argument. X shall be of type real.\n4 Restriction. IEEE_IS_NAN (X) shall not be invoked if IEEE_SUPPORT_NAN (X) has the value false.\n5 Result Characteristics. Default logical.\n6 Result Value. The result has the value true if the value of X is an IEEE NaN; otherwise, it has the value false.\n
Run Code Online (Sandbox Code Playgroud)\n

在我看来,MEX 可以更改默认逻辑的类型而不需要处理ieee_is_nan. 也许 MEX 存在一个选项可以纠正这种行为,但为什么它首先应该是默认选项呢?

\n

我在更多机器上使用其他版本的 MATLAB 和 Fortran 编译器尝试了相同的代码。结果是一样的。

\n
    \n
  1. MATLAB 9.7.0.1319299 (R2019b) 更新 5 与 GNU Fortran (GCC) 8.3.1 20191121 (Red Hat 8.3.1-5):

    \n

    kind(ieee_is_nan(1.0))= 4, kind(.false.) = 8

    \n

    不与 MATLAB 交互的相同编译器:

    \n

    kind(ieee_is_nan(1.0)) = 4 = kind(.false.)

    \n
  2. \n
  3. MATLAB 9.5.0.1049112 (R2018b) 更新 3 与 GNU Fortran (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0:

    \n

    kind(ieee_is_nan(1.0)) = 4, kind(.false.) = 8

    \n

    不与 MATLAB 交互的相同编译器:

    \n

    kind(ieee_is_nan(1.0)) = 4 = kind(.false.)

    \n
  4. \n
  5. (在Windows 10上)MATLAB 9.5.0.944444 (R2018b),采用 Intel(R) Visual Fortran Intel (R) 64 编译器,版本 19.1.1.216 内部版本 20200306

    \n

    kind(ieee_is_nan(1.0)) = 4, kind(.false.) = 8

    \n

    不与 MATLAB 交互的相同编译器:

    \n

    kind(ieee_is_nan(1.0)) = 4 = kind(.false.)

    \n
  6. \n
\n
\n

接受@francescalus'回答后的总结

\n
    \n
  1. 事实证明,kind(.false.)和之间的不匹配kind(ieee_is_nan(1.0))来自于 gfortran 选项-fdefault-integer-8,该选项被 MEX 默认采用。此选项强制 gfortran 使用 64 位整数和 64 位逻辑作为默认类型,但不会更改 的返回类型ieee_is_nan,即使 Fortran 标准指定ieee_is_nan应返回默认逻辑类型。这可能是因为它ieee_is_nan并不是真正的内在过程,而只是来自内在模块的过程ieee_arithmetic

    \n
  2. \n
  3. 请注意,ifort(版本 ifort (IFORT) 2021.2.0 20210228)和 nagfor(NAG Fortran Compiler Release 7.0 (Yurakucho) Build 7036)的行为也与上述方式相同,相应的选项适用于-i8它们两者。因此,编译器供应商同意,在强制执行某些选项时,打破某些内部模块的一致性是可以的。这让我感到惊讶。幸运的是,flang(在 clang 7.1.0 下)遵循 Fortran 标准,即使-fdefault-integer-8强加,保持kind(is_ieee_nan(1.0)) == kind(.false.)--- 所以这不是一个不可能的任务。

    \n
  4. \n
  5. NAG编译器nagforieee_is_nan何时-i8采用会发出警告,指出它们不兼容;然而,gfortran 和 ifort 保持绝对沉默,即使您分别使用-Wall -Wexta和调用它们-warn all。这更令我惊讶。

    \n
  6. \n
  7. 鉴于这些事实,我决定不使用ieee_is_nan而是实现我自己的is_nan,并对内部模块提供的所有过程保持警惕(或远离)。否则,如果用户选择强制使用 64 位整数作为默认值(不考虑逻辑类型;为什么要这么做?),我的包将因类型不匹配而无法编译。更严重的是,MATLAB 已经在没有告诉他们的情况下为所有用户做出了这样的选择。

    \n
  8. \n
  9. 我很高兴看到我的问题引发了有趣的讨论。由于某些编译器似乎需要针对此处发现的问题进行改进(您可能有不同的意见),因此我在Fortran Discourse上发表了一篇关于此主题的文章。我希望它能引起社区更多的关注,并且至少有人会修补像 gfortran 这样的开源编译器。

    \n
  10. \n
\n

非常感谢您的任何意见或批评。

\n

fra*_*lus 4

默认情况下,MEX 使用 gfortran 选项进行编译-fdefault-integer-8。gfortran 处理此结果的方式如您所见。

考虑非墨西哥计划

  use, intrinsic :: ieee_arithmetic, only : ieee_is_nan, ieee_support_nan
  implicit none

  if (.not.ieee_support_nan(1.)) error stop "Lack of support"
  print*, KIND(ieee_is_nan(1.)), KIND(.TRUE.)

end
Run Code Online (Sandbox Code Playgroud)

使用/不使用-fdefault-integer-8.

此选项使默认整数和逻辑变量的宽度为 8 个字节(并且具有类型参数 8)。

这都是有道理的。然而,似乎 gfortran 不会ieee_arithmetic使用此选项更改内部模块中函数的函数结果类型参数(这在某种程度上是合理的:它只是采用“预编译”模块文件所说的内容,即“返回”是合乎逻辑的(4)”。

(如果当您看到它似乎在另一个内部模块(如 )中使用正确的类型时,事情看起来很混乱,注意iso_c_binding,提供的模块文件不支持第二个内部模块。您将看到 IEEE 模块的相同行为OpenMP 内部模块也以文件形式提供。)

作为解决方法,您可以使用LOGICAL(ieee_is_nan(1.))来使事情再次匹配。


像这样的编译器选项-fdefault-integer-8不是玩具。它们应该只在最罕见的情况下使用。对于 gfortran,如果您单独使用此选项,您就是在告诉编译器“继续做您喜欢做的事情:我不在乎您是否按照 Fortran 标准指定的方式对待我的程序。” 在这种情况下,这是 MATLAB 为您做出的选择。

特别是,如果您使用编译器选项来更改事物的类型参数(例如默认类型或 NAG 编译器中看到的类型编号方案),则必须使用该选项将程序的所有组成部分编译为确保一致性。从本例中可以看出,这并不总是那么容易,因此将这些极其危险的选项作为默认选项。甚至内部模块也可能包含在需要考虑一致性的事物列表中。

  • 我本来打算建议这可能不是 gfortran 期望的行为,所以事情很可能会随着版本的不同而改变,等等。但如果你没有看到这样的变化,那么就不太需要注意了。(我责怪 MATLAB 不可原谅地使用了这个选项。) (3认同)