数组的分段错误,但仅限于派生类型的组件

时间:2018-08-23 12:40:25

标签: fortran gfortran

使用Linux(red hat)上的gfortran 4.8.5进行非常简单的设置:

  • 如果我的实数数组(在派生类型内部)的大小大于2,000,000,则会出现段错误。这似乎是一个标准的堆栈/堆问题,因为如果我使用ulimit进行检查,则堆栈大小为8mb。

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

  • 请注意,正如@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的较新版本)?出于某种原因,我几乎肯定会使用可分配的数组组件来解决该问题,但这可能不是理想的常规解决方案,我想知道我在这里拥有的所有不错的选择。

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

2 个答案:

答案 0 :(得分:6)

这可能是由于堆栈不足而引起的运行时问题,而不是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。如果使该组件可分配并在子例程中分配,则该数组存储在堆中而不是堆栈中,通常可以避免此问题。在这种情况下,可能实际上是在子例程中而不是在编译时动态地实际设置数组的值。它可能不太优雅,但是我认为这是值得的,因为它是代码开发工作的典型示例,可以防止执行二进制文件时避免与环境相关的错误。

您的上述代码符合标准。如评论中所述,缺少用于子例程的显式接口不是一个好习惯,但是对于这些简单的子例程,这并不违反规则。

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

最后,请注意,如果您使用的是OpenMP,则上面的ulimit命令仅会影响主线程-您需要通过环境变量OMP_STACKSIZE设置其他线程的堆栈大小,该变量不能为unlimited。并且请记住,非主线程用完堆栈是一个很难诊断的问题,因为二进制文件可能会在没有适当的分段错误的情况下停止运行。

答案 1 :(得分:0)

这些不一定是有用的解决方案,但以下是分段故障消失的一些条件。几个人提到缺少显式接口(这是一种不好的做法,尽管从技术上来讲不是错误的),并且似乎这可能是一个关键,因为这两个代码更改都摆脱了seg错误,尽管不是就这么简单,正如我将解释的那样:

  1. 将所有内容放入主目录,而无需子例程调用

  2. 将类型定义table放入模块

让我简要介绍一下#2。只需在OP中以示例为例,然后通过将子例程放入模块即可为其提供显式接口。但是,如果将类型定义放在模块中然后使用它(如下所示),则不会发生段错误:

program main

    use table_mod

    type(table) :: table1

    table1%col = 1.0

end program main