【问题标题】:Segmentation fault for array, but only if a component of a derived type数组的分段错误,但仅限于派生类型的组件
【发布时间】:2018-08-23 12:40:24
【问题描述】:

非常简单的设置,在 linux (red hat) 上使用 gfortran 4.8.5:

  • 如果我的实数数组(在派生类型内)的大小 > 2,000,000,我会收到段错误。这似乎是一个标准的堆栈/堆问题,因为如果我检查 ulimit,我的堆栈大小是 8mb。

  • 如果数组NOT在派生类型中,则没有问题

  • 请注意,正如@francescalus 猜测的那样,删除初始值= 0.0 可以消除问题

编辑添加:请注意,我已经发布了一个后续问题Segmentation fault related to component of derived type,它代表了一个更现实的用例,并进一步缩小了似乎发生这种情况的条件。

program main

    call sub1     ! seg fault  if col size >   2,100,000
    call sub2     ! works fine at col size = 100,000,000  

end program main

subroutine sub1

    type table
        real :: col(2100000) = 0.0     ! works if "= 0.0" removed
    end type table

    type(table) :: table1
    table1%col = 1.0

end subroutine sub1

subroutine sub2
    real :: col(100000000) = 0.0
    col = 1.0
end subroutine sub2

这里有一些明显的问题:

  • 这是预期行为,还是已在较新版本的 gfortran 中修复的某些错误?

  • 我是在遵循标准的 fortran 操作程序,还是做错了什么?

  • 建议的避免这种情况的方法是什么(请假设我近期无法更新到较新版本的 gfortran)?我几乎肯定会使用可分配数组组件来解决这个问题,但这可能不是一个理想的通用解决方案,我想知道我在这里拥有的所有好的选择。

  • 特别是,初始化派生类型的组件是不好的做法吗?

【问题讨论】:

  • 你有什么特别想要的“修复”吗?也就是说,您是否愿意更改代码(不仅仅是可分配组件),或者只是编译器标志等?
  • 我不是 gfortran 方面的专家,所以不能给你答案。但是,问题似乎在于派生类型的默认初始化:如果可能,删除它可能就足够了。 [请注意,组件的默认初始化和sub2 中的显式初始化确实意味着不同的东西。]
  • @HighPerformanceMark 我试图制作一个 MCVE stackoverflow.com/help/mcve 我当然可以让它更长,更“有用”。我使用的实际代码将子例程放在一个模块中,但有同样的问题。我不确定是应该像这里那样做还是使用“包含”来模拟实际的模块化。使用 2 个没有参数的“无意义”子程序仅仅是为了将它们完全分开并且没有 2 个程序。 sub2 仅用于比较,本身并不特别感兴趣。
  • @JohnE,变量和数组的静态初始化通常会放在堆栈上。 gfortran 有一个 -fstack-array 选项。它可能有它的否定,即-fno-stack-array,它可能会阻止使用堆栈。这可能会增加代码大小和运行时间。最后,这不是 gfortran 问题。这是 Fortran 程序暴露的环境的问题。使用不同的编译器也可能会达到堆栈限制。
  • 请注意,gfortran 开发人员@Steve 在上面的评论中讨论的编译器选项实际上是-fstack-arrays 和(手册页中没有)-fno-stack-arrays,在这两种情况下都是复数arrays。不幸的是,对于 gfortran 7.3.0 和 8.2.0,-fno-stack-arrays 似乎无法解决这里的问题。

标签: fortran gfortran


【解决方案1】:

这可能是由于堆栈不足导致的运行时问题,而不是 gfortran 的错误。

Gfortran 使用堆栈来存储自动数组和其他初始化数据。当一个这样的数组很小时代码不会产生问题,但是当数组的大小增加时会出现段错误,可能的原因是堆栈不足。

在较新版本的 gfortran 中,问题似乎相同。我使用 gfortran 4.8.4、4.9.3、5.5.0、6.4.0、7.3.0 和 8.2.0 编译并运行了您的程序。在所有情况下,我在默认堆栈大小下都获得了分段错误,但在堆栈大小略微增加时没有错误。

$  ./sfa
Segmentation fault
$ ulimit -s
8192
$ ulimit -s 8256 
$ ./sfa && echo "DONE"
DONE

你的问题可以通过运行解决

$ ulimit -s unlimited

在执行你的二进制文件之前。我不知道这样做有什么特别的惩罚,但是更了解内存管理细节的程序员,比如编译器开发人员,可能会不这么认为。

初始化派生类型的组件并不是一个坏习惯,但是正如您所看到的,如果组件是一个大数组,它可能会在堆栈中产生问题 - 无论是由于组件本身的存储,还是由于存储在作业的 RHS 上工作的内存。如果组件是可分配的并在子例程中分配,则数组存储在堆中而不是堆栈中,通常可以避免这个问题。在这种情况下,它可能实际上是在子例程中而不是在编译时动态设置数组的值。它可能不那么优雅,但我认为它是值得的,因为它是代码开发工作的典型示例,可以在执行二进制文件时防止可避免的与环境相关的错误。

您上面的代码符合标准。正如 cmets 中所解释的,缺少子例程的显式接口不是好的做法,但对于这些简单的子例程,这并不违反规则。

某些编译器具有允许您更改某些对象在内存中的分配位置的标志。虽然它可以解决特定问题,但标志取决于编译器,并且在比较不同编译器时通常不等效。根据我的经验,通过 allocatables 使用动态内存是一种更强大的解决方案。

最后要注意,如果你使用的是OpenMP,上面的ulimit命令只会影响主线程——你需要通过环境变量OMP_STACKSIZE来设置其他每个线程的栈大小,不能是@987654324 @。请记住,非主线程耗尽堆栈是一个更难以诊断的问题,因为二进制文件可能会在没有适当的分段错误错误的情况下停止。

【讨论】:

  • 感谢您的回答,我选择了它,尽管我还有一些其他问题(我可能会为此添加赏金或对此问题的后续问题)。特别是,我希望更好地了解无限堆栈大小的优缺点,以及堆栈大小 (ulimit-s) 与编译器选项之间的关系,例如 -fmax-stack-var-size。这不是要求添加到您的答案(尽管随意),只是对我试图跟进的进一步问题的解释。但再次感谢,很好的答案!
【解决方案2】:

这些不一定是有用的解决方案,但以下是 seg 故障消失的一些条件。有几个人提到缺少显式接口(这是一种不好的做法,尽管在技术上并非不正确),这似乎是这里的一个关键,因为对代码的这两个更改中的任何一个都摆脱了 seg 错误,尽管它不是就这么简单,我会解释:

  1. 将所有内容放在 main 中,不调用子程序

  2. 将类型定义table放到一个模块中

让我简要地扩展#2。简单地以 OP 中的示例为例,然后通过将子例程放入模块中来为其提供显式接口不起作用。但是,如果我将类型定义放在一个模块中然后使用它(如下所示),则不会发生段错误:

program main

    use table_mod

    type(table) :: table1

    table1%col = 1.0

end program main

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-18
    • 1970-01-01
    • 2017-05-18
    • 1970-01-01
    • 2015-05-20
    • 2010-10-20
    • 2018-11-15
    相关资源
    最近更新 更多