错误?MATLAB MEX 更改了默认逻辑的类型



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

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

将以下代码命名为"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标准在这些种类数字与用于表示数据的字节数之间没有关系。请参阅史蒂夫的博客 Fortran 博士在"它需要所有种类"中,以很好地阐述这个主题。

[更新上述关于kind(ieee_is_nan(1.0)=kind(.false.)的猜测被证明是错误的,即使这是Fortran 2018标准所暗示的。使用某些编译器选项,kind(ieee_is_nan(1.0)kind(.false.)实际上可能彼此不同,因此违反了Fortran标准中的规范。请参阅@francescalus的答案和我在这个问题末尾的摘要。

[更新:在最初的问题中,我翻转了"4"和"8",所以我认为kind(ieee_is_nan(1.0))被改变了;事实上,从4更改为8的是kind(.false.),而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标准中ieee_is_nan的部分。它指定ieee_is_nan返回一个"默认逻辑",我想它应该与内在.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. (在Windows 10上)MATLAB 9.5.0.944444 (R2018b) 与英特尔® Visual Fortran 英特尔 (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类型,即使 Fortran 标准指定ieee_is_nan应返回默认逻辑类型。这可能是因为ieee_is_nan并不是真正的内在过程,而只是来自内在模块ieee_arithmetic的过程。

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

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

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

  5. 我很高兴看到我的问题引发了有趣的讨论。由于一些编译器似乎需要改进这里发现的问题(你可能有不同的意见),我在Fortran Discourse上发表了一篇关于这个主题的文章。我希望它能引起社区的更多关注,并且有人至少会修补像 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

编译有/没有-fdefault-integer-8

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

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

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

作为一种解决方法,您可以使用LOGICAL(ieee_is_nan(1.))使内容再次匹配。


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

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

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

当然,这两个数字取决于编译器---对于 nagfor 编译器来说它们是 3,但我认为它们应该始终相等。

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

编译器对 LOGICAL 有 14、27 和 830 种,对 REAL 有 2、9 和 13 种是完全有效的。您唯一可以指望的是:

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

(请参阅 Fortran 2018 19.5.3.2 存储顺序)

当然,如前所述,如果使用编译器选项更改一种类型的默认类型而不更改另一种类型的默认类型,则会破坏此规则。

有关进一步阅读,请参阅我的博客文章,"It Takes All KINDs"中的Fortran博士

最新更新