数组的分段错误,但前提是派生类型的组件
Segmentation fault for array, but only if a component of a derived type
非常简单的设置,在 linux (red hat) 上使用 gfortran 4.8.5:
如果我的实数数组(在派生类型中)的大小 > 2,000,000,我会遇到段错误。这似乎是一个标准的 stack/heap 问题,因为如果我检查 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)?出于与此问题无关的原因,我几乎肯定会使用可分配的数组组件来解决,但这可能不是理想的通用解决方案,我想知道我在这里有所有好的选择。
特别是,初始化派生类型的组件是不是坏习惯?
这可能是由于堆栈不足导致的运行时问题,而不是 gfort运行 的错误。
Gfort运行使用栈来存放自动数组等初始化数据。当这样的数组很小时代码不会产生问题,但当数组的大小增加时会出现段错误,一个可能的原因是 运行 出栈。
这个问题在 gfort运行 的更新版本中似乎是一样的。我使用 gfort运行 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
。请记住,非主线程 运行 出栈是一个更难诊断的问题,因为二进制文件可能会在没有适当的分段错误的情况下停止。
这些不一定是有用的解决方案,但以下是段错误消失的一些条件。一些人提到缺少显式接口(这是一种不好的做法,尽管在技术上并不正确),看起来这可能是这里的一个关键,因为对代码的这两个更改中的任何一个都消除了段错误,尽管它不是就这么简单,正如我将要解释的那样:
将所有内容放在主程序中,不调用子程序
将类型定义 table
放入模块中
让我简要介绍一下#2。简单地以 OP 中的示例为例,然后通过将子例程放入模块 中为其提供显式接口是行不通的 。但是,如果我将类型定义放在模块中然后使用它(如下所示),则不会发生段错误:
program main
use table_mod
type(table) :: table1
table1%col = 1.0
end program main
非常简单的设置,在 linux (red hat) 上使用 gfortran 4.8.5:
如果我的实数数组(在派生类型中)的大小 > 2,000,000,我会遇到段错误。这似乎是一个标准的 stack/heap 问题,因为如果我检查
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)?出于与此问题无关的原因,我几乎肯定会使用可分配的数组组件来解决,但这可能不是理想的通用解决方案,我想知道我在这里有所有好的选择。
特别是,初始化派生类型的组件是不是坏习惯?
这可能是由于堆栈不足导致的运行时问题,而不是 gfort运行 的错误。
Gfort运行使用栈来存放自动数组等初始化数据。当这样的数组很小时代码不会产生问题,但当数组的大小增加时会出现段错误,一个可能的原因是 运行 出栈。
这个问题在 gfort运行 的更新版本中似乎是一样的。我使用 gfort运行 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
。请记住,非主线程 运行 出栈是一个更难诊断的问题,因为二进制文件可能会在没有适当的分段错误的情况下停止。
这些不一定是有用的解决方案,但以下是段错误消失的一些条件。一些人提到缺少显式接口(这是一种不好的做法,尽管在技术上并不正确),看起来这可能是这里的一个关键,因为对代码的这两个更改中的任何一个都消除了段错误,尽管它不是就这么简单,正如我将要解释的那样:
将所有内容放在主程序中,不调用子程序
将类型定义
table
放入模块中
让我简要介绍一下#2。简单地以 OP 中的示例为例,然后通过将子例程放入模块 中为其提供显式接口是行不通的 。但是,如果我将类型定义放在模块中然后使用它(如下所示),则不会发生段错误:
program main
use table_mod
type(table) :: table1
table1%col = 1.0
end program main