【问题标题】:Where is FORTRAN getting these values from?FORTRAN 从哪里获得这些值?
【发布时间】:2013-11-14 22:01:03
【问题描述】:

我是 FORTRAN 的绝对初学者,我煞费苦心地将一个 1000 行的 FORTRAN 源文件转换为 MATLAB 以供我自己使用。到目前为止,我通过放入大量 PRINT 语句来跟踪变量并确保数学正常工作,这一切都很好。我在 FORTRAN 中遇到了一件我无法解决的奇怪事情。

有一个子程序是这样定义的

subroutine addprc 
complex tmat,b,ci,c1,c2,cim,ab1(50),ab2(50),acans(10,2),fg1(50),fg2(50)
common dtr,rtd,pi 
common /mtxcom/ nrank,nranki,tmat(50,50),b(50,50),cmxnrm(25)
common /cmvcom/ nm,kmv,cmv,twm,prodm 

下面还有更多common 和dimension 分配,但它们不应该相关。第一次定义ab1(50) 数组是在这个子例程中。对于我的 MATLAB 脚本,我已将其初始化为 ab1=zeros(1,50);。然后在这个循环中使用它(在 FORTRAN 中):

do 20 n = 1,nrank 
np = n+nrank 
cn = real(n)
n1 = n+1 
c1 = 4.0*ci**n 
c2 = 4.0*ci**n1 
p1 = cn*costh*pnmllg(n1)-(cn+cmv)*pnmllg(n) 
p2 = cmv*pnmllg(n1) 
ab1(n-ij) = c1*p2*uu1 
ab1(np-ijt) = -c2*p1*uu1 
ab2(n-ij) = -c1*p1*uu2 
ab2(np-ijt) = c2*p2*uu2 

此时我已经检查了所有其他变量的值,它们与我的 MATLAB 脚本一致。向后工作我发现ab1 已经包含值的问题。循环之前的打印命令,例如

PRINT *,'before ab1', SUM(ab1)
do 20 n = 1,1

返回(107.500008, 5.38305187)。它是一个复数,所以这两个值都很好,但有什么不好的是它里面有什么东西呢?事实上,唯一一次使用ab1 就是在这段代码中。

这里有什么我遗漏的吗?我查看了源文件 (ctrl-f:ab1) 以找到它的任何实例。原始源代码是一个 .for 文件,我在 Eclipse 下使用 gfortran 编译了它们。谢谢你的时间。

【问题讨论】:

  • re:公共块无关紧要,请注意,如果符号是公共块,则其他程序单元可能会使用不同的符号名称访问该公共块。
  • ij ijt 循环在哪里?
  • @george:我确实从其他变量中注意到了这种行为。我唯一不确定的是如何知道它在使用什么,因为这是第一次定义 ab1。 @ja72 ij ijt 循环稍早定义如下。 ij = kmv-1, if(ij.lt.0), ij = 0 ijt = 2*ij 所以没什么太花哨的。

标签: fortran gfortran


【解决方案1】:

ab1 是小数组(50 * 8 = 400 字节),因此很可能分配在程序堆栈上。堆栈用于存储局部变量以及调用子程序时调用者的返回地址。最初,堆栈页面包含全零,但随着子程序被调用,它会增长并填充一些数字。稍后当子例程返回时,堆栈指针已更改,但值仍保留在堆栈上,稍后会被新调用覆盖。当ab1 被分配时,它的内存最初会被来自以前调用的子程序的旧堆栈值填充。大多数 FORTRAN 编译器默认不会生成初始化堆栈变量的指令,因为这可能是一项昂贵的操作。

如果您的程序是完全确定性的,即没有依赖于某些(伪)随机数的值的子例程调用,或者初始 PRNG 种子始终相同,则返回地址不包含堆栈的所有其他内容在您的程序的许多执行之间将是相同的,例如晚上一个,早上一个。

这就是为什么SUM(ab1) 总是返回(107.500008, 5.38305187)。它实际上是一个 GoodThing(tm) - 它表明您的计算机是一个确定性设备,即它在给定相同算法和相同输入的情况下重现输出,因此可用于执行编程任务。这也是一个 BadThing(tm),因为可预测的堆栈值是对操作系统安全性的许多攻击的基础,但这超出了您的问题范围。

【讨论】:

    【解决方案2】:

    只是想知道:你会在那里期待什么?

    这些值来自内存。当您调用定义变量的某些内容(函数/程序/子例程)时,某些内存与定义的变量相关联,但是在您为变量分配某些内容之前,它将使用之前内存中存在的任何内容。由于计算机内存不断被重用而不被重置,您可能会从另一个已结束的调用函数/子例程的变量中获取值,来自其他进程的一块内存(除了必须由内核清空的某些页面)它们可能包含私人数据)或其他任何内容。一般规则是,除非他非常需要,否则没有人会执行将内存设置为某个值这样昂贵的操作(例如,在这里您确实将与变量相关的内存设置为您需要的数字,或者当不擦除内存时是一个潜在的安全漏洞) .

    总而言之:这些值来自未初始化的内存。具体内容未定义。

    在访问未初始化的内存时不会出错,因为检查内存是否已初始化是一项非常昂贵的操作。但是在这种情况下,有些工具会显示错误:

    1. 如果您想知道您的程序是否访问未初始化的内存,您可以在valgrind 下运行它。请注意,此类检查非常昂贵:在 valgrind 下,您很容易让程序运行得更糟,然后慢一个数量级。
    2. 还有一个 clang MemorySanitizer 过滤器声称可以在仅 3 倍减速的情况下完成这项工作,但您需要在使用 clang 之前将 fortran 程序翻译为 C(例如使用 f2c),因为它是 C/C++ 编译器。您还需要在启用此过滤器的情况下编译您的程序使用的所有库。

    【讨论】:

    • Fortran 编译器(我熟悉的那些)可以识别未初始化变量的使用,valgrind 对于这样一个简单的任务几乎没有必要。
    • @HighPerformanceMark 这里:也许。一般来说:没有。考虑当您调用另一个文件中定义的子例程并为其提供未初始化的变量并分别编译这些文件时的情况。编译调用者的编译器实例不知道您是要路径一个值还是低级语言在参数中返回值的情况通常是这样。编译被调用者的编译器实例不知道子例程是否将传递已初始化或未初始化的内存。链接器根本不在乎,也没有办法检测到这种情况。因此,您将不得不使用 valgrind。
    • 嗨 ZyX。我确实有一点 C++ 的背景知识,所以它可以读取内存值的事实让我印象深刻。但是,我今天早上刚刚打开电脑并再次运行程序,ab1 (107.500008,5.38305187 )。因此,它与上一届会议所做的完全相同。我认为这对于垃圾值来说是不太可能的。请注意,如果有帮助,这是 FORTRAN 77 标准。不过我会尝试一下 valgrind...
    • 嗯,我想你可能是对的 ZyX。我去了程序的最开始,其中一个变量被定义为complex aa(50,50),它也有一些值。奇怪的是,只需添加语句PRINT *,'aa is', aa 就改变了SUM(ab1) 的值。所以我猜这个程序只会将值写入 ab1 的前 n 个值,然后再使用它们。我的推理有什么问题吗?
    • 我已经进一步追踪了问题,我可以看到你是对的。出于某种原因,我认为 FORTRAN 将所有变量初始化为 0,就像 MATLAB 所做的那样......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-16
    • 2011-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多