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

Bug? MATLAB MEX changes the kind of the default logical

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

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

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

! test_kind.F
! Tested by MATLAB 9.8.0.1323502 (R2020a) with GNU Fortran (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

#include "fintrf.h"

      subroutine mexFunction(nlhs, plhs, nrhs, prhs)

      use ieee_arithmetic, only : ieee_is_nan
      implicit none
      mwPointer plhs(*), prhs(*)
      integer nlhs, nrhs

      write(99, *) kind(ieee_is_nan(1.0))  ! This prints a number in fort.99
      write(99, *) kind(.false.)  ! A benchmark, which should print the same number in fort.99
      close(99)

      end subroutine mexFunction

我认为两个打印出来的数字应该总是相等的,虽然具体值取决于编译器,不一定是4或8。(强调Fortran 博士 @SteveLionel,Fortran 标准在这些种类数字和用于表示数据的字节数之间没有任何关系。请参阅史蒂夫的博客 Doctor Fortran in “It Takes All KINDs” 以获得对此的详细阐述主题。)

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

[Update:原题中,我把“4”和“8”翻转了,以至于我以为kind(ieee_is_nan(1.0))改了;事实上,它是 kind(.false.) 从 4 变为 8,而 kind(ieee_is_nan(1.0)) 始终保持 4 。所以一切都可以通过@francescalus 的漂亮回答得到充分解释,谢谢!]

为了比较,这里是没有与 MATLAB 接口的相同代码,当使用 gfortran 编译时,它在屏幕上打印“4”和“4”。使用 nagfor 编译器,尽管变为 3,但数字仍然相等。

! test_kind.f90
! Tested by GNU Fortran (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

program test_kind

use ieee_arithmetic, only: ieee_is_nan
implicit none

write(*, *) kind(ieee_is_nan(1.0))  ! This prints a number on STDOUT (screen)
write(*, *) kind(.false.)   ! A benchmark, which should print the same number on STDOUT (screen)

end program test_kind

下面是 Fortran 2018 standard 中关于 ieee_is_nan 的部分供您参考。它指定 ieee_is_nan return 是一个“默认逻辑”,我猜它应该与内在 .true..false. 常量的类型相同——或者我看错了吗?

17.11.13 IEEE_IS_NAN (X)
1 Description. Whether a value is an IEEE NaN.
2 Class. Elemental function.
3 Argument. X shall be of type real.
4 Restriction. IEEE_IS_NAN (X) shall not be invoked if IEEE_SUPPORT_NAN (X) has the value false.
5 Result Characteristics. Default logical.
6 Result Value. The result has the value true if the value of X is an IEEE NaN; otherwise, it has the value false.

我觉得 MEX 可以更改默认逻辑的类型而无需处理 ieee_is_nan,这对我来说似乎非同寻常。 也许 MEX 有一个选项可以纠正这种行为,但为什么它首先应该是默认值?

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

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

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

    没有与 MATLAB 接口的相同编译器:

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

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

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

    没有与 MATLAB 接口的相同编译器:

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

  3. (On Windows 10) MATLAB 9.5.0.944444 (R2018b) with Intel(R) Visual Fortran Intel (R) 64 编译器,版本 19.1.1.216 内部版本 20200306

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

    没有与 MATLAB 接口的相同编译器:

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


接受@francescalus 的回答后的总结

  1. 原来kind(.false.)kind(ieee_is_nan(1.0))的不匹配来自gfortran选项-fdefault-integer-8,MEX默认采用了这个选项。此选项强制 gfortran 使用 64 位整数和 64 位逻辑作为默认种类,但不会更改 ieee_is_nan 的 returned 种类,即使 Fortran 标准指定 ieee_is_nan 应该 return 默认的逻辑类型。这可能是因为 ieee_is_nan 并不是真正的内部过程,而只是来自内部模块 ieee_arithmetic.

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

  3. NAG编译器nagfor在采用-i8的时候对ieee_is_nan发出警告,指出它们不兼容;然而,即使您分别使用 -Wall -Wexta-warn all 调用它们,gfortran 和 ifort 也会保持绝对沉默。这更让我惊讶。

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

  5. 很高兴看到我的问题引发了有趣的讨论。由于一些编译器似乎需要针对这里发现的问题进行改进(您可能有不同的意见),我就这个话题在Fortran Discourse上做了一个post。我希望它能引起社区的更多关注,并且至少有人会修补 gfortran 等开源编译器。

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

默认情况下,MEX 使用 gfortran 选项编译 -fdefault-integer-8。 gfortran 处理这个结果的方式就是你所看到的。

考虑非 MEX 程序

  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

已编译with/without-fdefault-integer-8.

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

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

(如果当您看到它 确实 似乎在另一个内部模块(如 iso_c_binding 中使用了正确的种类时,如果事情看起来令人困惑,请注意,这第二个内部模块是提供的模块文件不支持。您将看到 IEEE 模块与 OpenMP 内部模块的行为相同,该模块也作为文件提供。)

作为变通方法,您可以使用 LOGICAL(ieee_is_nan(1.)) 再次匹配。


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

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

(编辑 - 我误读了声明。请参阅下面的评论。)

Certainly, these two numbers depend on the compiler --- they are 3 for the nagfor compiler, but I thought they should always be equal to each other.

不!一种类型的种类编号与另一种类型的种类编号之间没有隐含的对应关系!不要被种类数字的字节大小的常见用法所迷惑 - 这只是一些编译器采用的约定,让习惯于 integer*4 扩展的程序员看起来更熟悉。

如果编译器拥有 14、27 和 830 类用于 LOGICAL 以及 2,9 和 13 用于 REAL,那将是完全有效的。您唯一可以信赖的是:

  • 默认整数、默认实数、默认逻辑各占一个“数值存储单元”
  • 双精度和默认复数占用两个数值存储单元

(参见 Fortran 2018 19.5.3.2 存储顺序)

当然,正如已经指出的那样,如果您使用编译器选项更改一种类型的默认类型而不是另一种类型,那么您就违反了这条规则。

如需进一步阅读,请参阅我的博客 post、Doctor Fortran in “It Takes All KINDs”