Fermat618's Blog

Happy coding

gfortran 新坑: 未初始化的变量不会给警告

每当遇到些意外的时候,也就是初坑了的时候,我就要写篇日志了。

这两天模拟一篇 PRL 上文章,结果一直不对。我一直以为是随机模拟的数据错了,最后用了一个很笨的办法,看出来,原来是理论曲线算错了。

这实在是一个很奇怪的事情,因为算理论曲线的那个程序,相当清晰,而且是在我加了 -Wall 参数编译以后,没有任何警告就通过了。程序运行起来也没有任何的问题,但是结果不对。

只好拿着代码,一行一行慢慢看,寻找每一个变量的来源,与公式进行对照。忽然间,发现一个参数定义了,但是没有初始化。这頗为让我惊讶。如果一个变量声明了,没有使用,会有一个警告的。但变量声明了,没有定义而使用了,竟然半点警告也没有。

我记不清Fortran标准里面有没有规定一个变量如果没有初始化,哪种情况下会被初始化为 0。在 C 语言里面,函数里面的自动变量(main也是函数), 如果没有初始化,值会是随机的,不管是教科书,还是编译器,都给了警告。gfortran 竟然不会给警告,真是难以让人相信。

我继续查看 gfortran 的文档,想开启更多的检查开关,结果发现了如下一些项。


-ffpe-trap=invalid,zero,overflow,underflow,denormal


开启浮点数异常的检查,以及

-finit-real=snan

来把real型变量初始化为 snan. snan 是在开了检查之后会报错的 Nan.

Fortran 中根据语义的补全

Fortran 中的 if 结构,函数声明等,都有不少的信息冗余。如

if (3 > 2) then
    !...
endif

real function add(a, b)  
...
end function

后面的 endif, end function 等,长度都不少。这样虽然提高了可读性,但手动敲下未免还是太麻烦。把时间花在敲入一个很长还必定会有的东西上面,很不划算。如果用 snippet 之类的方案,需要记住一个 key, 然后每次按 tab 展开,除了语言本身,又多了一个记忆 key 的负担。

于是,我想到了一个更为自动化的补全方案,就是让程序根据已经输入的内容,在敲入回车键换行时,自己判定这是不是一个未完成的 if 结构,然后自动在后面补全 endif.

因为这个功能绑定到了一个很重要的按键上,所以功能要尽量的不添乱 。所以,我想到把当前行按照程序语义给解析出来。如果能成功的解析,就补全后面的内容;如果不行,就执行普通的Enter.

解析程序的语义,还可以带来另外的好处。一个是自动加空白,如

do i=1,n

给自动化成为

do i = 1, n

有了空格之后更加的好读。另一个功能是把一些冗余的东西省掉,如

if (a > 3) then

有用的信息只有

if a > 3

因此,只需要敲下上面的文字,再回车,就可以补全成符合 Fortran 语法规范的形式。

代码在此 https://github.com/fermat618/fortran-construct-complete

 

掉 intel fortran 编译器返回派生类型的方式的坑里了

 
 

最 近写 matlab 与 Fortran 的混编程序时,为了使程序写起来更容易,采用了 Fortran 2003 中 iso_c_binding 模块的一些内容。这些内容的采用使得那些接口写起来 更容易 了,本来松了一口气,想换到 intel fortran 编译器下的时候,又来了那个让我 一看到 就头疼的段错误。

然后又得开始调试了。在仔细查看了程序后,没发现有问题。 然后开始写小程序测试 里面用到的语句。吸引以前的教训,现在仔细检查调用 matlab mex API 后的返回值。可 是 type(c_ptr), 这个 iso_c_binding 模块中规定的放 C 指 针值的数据类型, 却没法打印出来看。

通过 locate iso_c_binding 命令。找到了 intel 的 iso_c_binding.f90 这个文件。打开后查看其内容,发 现

type, bind(c) :: c_ptr
integer(xx), private :: ptr
end type 

type(c_ptr) 的内容被声明为了私有的。 把这个文件拿出来,去掉 private 属性,再手动编译,总算是可以看到 type(c_ptr) 的 内容了。 把同一个 mex 程序用 intel fortran 与 gnu fortran 编译器分别编译两份, 再开一个 matlab, 在里面声明一个变量,传进去,查看各个函数的返回,找到了 mxGetPr 函数返回的结果不对。

 

又另写了一个函数,把 mxGetPr 函数的返回值申明为 integer(8), 再看结果,却又是对 的了。

终于定位到了是 integer*8 和 type(c_ptr) 的区别,才想到可能是 intel 对于后一 种可能采取了不是传值而是传值的方式。另写函数一验证,果然如此。

派生类型与内置类型一样,也是标量。同样的标量,在 intel fortran 中的处理,却是 很不相同,这很超出我预期。这种处理方式真是相当的不统一。标量传值,矢量传址,这 才是统一的方式嘛。

又试了给函数加上 bind(c) 属性,这次终于可以传值了。但是加上了这个属性的函数, 其编译出来的文件的符号的处理又跟普通的 fortran 函数不一样了。 matlab 给 Fortran 的那些 api 的符号名本来是按照 fortran 来的,这么一来就得在 bind(c, name=xxx) 里面写明了。使用 nm 得到实际的符号名后,写死在里面,再加上条件编译,总算是使得 gfortran 与 intel fortran 都可以编译那个程序了,代价就是只能用在 64 平台及开启 了 -largeArrayDims 选项的情况下了。