【问题标题】:Segmentation faults in Fortran recursive tree implementationFortran 递归树实现中的分段错误
【发布时间】:2014-11-04 16:05:44
【问题描述】:

我需要在 Fortran 中为一个项目实现树形结构,所以我已经阅读了各种在线指南,解释了如何做到这一点。但是,我不断收到错误或奇怪的结果。

假设我想构建一个二叉树,其中每个节点都存储一个整数值。我还希望能够将新值插入树中并打印树的节点。所以我写了一个类型“树”,它包含一个整数、两个指向子子树的指针和一个我设置为 .true 的布尔值。如果没有子子树:

module class_tree
implicit none

type tree
    logical :: isleaf
    integer :: value
    type (tree), pointer :: left,right
end type tree

interface new
    module procedure newleaf
end interface

interface insert
    module procedure inserttree
end interface

interface print
    module procedure printtree
end interface

contains

subroutine newleaf(t,n)
    implicit none
    type (tree), intent (OUT) :: t
    integer, intent (IN) :: n

    t % isleaf = .true.
    t % value = n
    nullify (t % left)
    nullify (t % right)
end subroutine newleaf

recursive subroutine inserttree(t,n)
    implicit none
    type (tree), intent (INOUT) :: t
    integer, intent (IN) :: n
    type (tree), target :: tleft,tright

    if (t % isleaf) then
        call newleaf(tleft,n)
        call newleaf(tright,n)

        t % isleaf = .false.
        t % left => tleft
        t % right => tright
    else
        call inserttree(t % left,n)
    endif
end subroutine inserttree

recursive subroutine printtree(t)
    implicit none
    type (tree), intent (IN) :: t

    if (t % isleaf) then
        write(*,*) t % value
    else
        write(*,*) t % value
        call printtree(t % left)
        call printtree(t % right)
    endif
end subroutine printtree
end module class_tree

除非试图插入叶子,否则插入总是在左子树中完成。在这种情况下,将插入到两个子树中以确保节点始终具有 0 或 2 个子节点。打印是在前缀遍历中完成的。

现在如果我尝试运行以下程序:

program main
use class_tree
implicit none
type (tree) :: t

call new(t,0)
call insert(t,1)
call insert(t,2)
call print(t)
end program main

我得到了所需的输出 0 1 2 2 1。但是如果我在“call insert(t,2)”之后添加“call insert(t,3)”并再次运行,则输出为 0 1 2 0 然后我得到一个段错误。

我试图查看故障是否发生在插入或打印过程中,所以我尝试运行:

program main
use class_tree
implicit none
type (tree) :: t

call new(t,0)
call insert(t,1)
call insert(t,2)
write(*,*) 'A'
call insert(t,3)
write(*,*) 'B'
call print(t)
end program main

它使段错误消失,但我得到一个非常奇怪的输出 A B 0 1 2673568 6 1566250180。

在网上搜索类似错误时,我得到了类似here 的结果,它说这可能是由于递归调用过多。但是,对 insert(t,3) 的调用应该只包含 3 个递归调用......我还尝试使用带有 -g -Wall -pedantic -fbounds-check 的 gfortran 进行编译并使用调试器运行。似乎错误发生在打印子程序中的“if (t % isleaf)”行,但我不知道如何理解。

编辑:

在 cmets 之后,我在 gfortran 中使用 -g -fbacktrace -fcheck=all -Wall 编译并尝试检查内存的状态。我对此很陌生,所以我不确定我是否正确使用了我的调试器 (gdb)。

在三个插入之后和调用print 之前,似乎一切都很顺利:例如,当我在gdb 中输入p t % left % left % right % value 时,我得到了预期的输出(即3)。如果我只输入p t,则输出为 (.FALSE.,0,x,y),其中 x 和 y 是十六进制数(我猜是内存地址)。但是,如果我尝试p t % left,我会得到类似指针的“描述”:

PTR TO -> (Type tree
logical(kind=4) :: isleaf
integer(kind=4) :: value

因为每个指针都指向一个包含两个指针的树,所以它会重复很多次。我本来希望输出类似于 p t 的输出,但我不知道这是否正常。

我还尝试检查内存:例如,如果我输入 x/4uw t % left,我得到 4 个字,前 2 个字似乎对应于 isleafvalue,最后 2 个字对应于内存地址。通过这样的内存地址,我设法访问了所有节点,我没有发现任何问题。

段错误发生在打印例程中。如果我在故障后键入p t,它说我无法访问 0x0 地址。这是否意味着当我尝试打印它时,我的树以某种方式被修改了?

【问题讨论】:

  • 我看不出你的代码有什么问题,但是当它全部变成递归和指针时我通常看不到。对我来说,这似乎是一个调试会话的机会。启动您最喜欢的调试器,单步执行您的代码,密切注意指针和内存使用情况。如果您还没有最喜欢的调试器,那么现在是结识的好时机。
  • 启用编译器的所有检查。 Gfortran:-g -fbacktrace -fcheck=all -Wall,ifort:-g -fbacktrace -check -warn。你的-fbounds-check 不够用。
  • 感谢您提供这些答案,我将使用调试器中的详细信息编辑我的帖子。
  • +1 勇敢地在 Fortran 中尝试此操作。

标签: recursion tree fortran


【解决方案1】:

您的问题的原因是,超出范围的变量不再有效。这与 Python 等语言不同,后者的现有指针数量是相关的(引用计数)。

在您的特定情况下,这意味着对newleaf(left, n)newleaf(right, n) 的调用分别设置了leftright 的值,但是这些变量超出了范围,因此无效。

更好的方法是根据需要分配每个叶子(第一个除外,因为它已经分配并且在程序结束之前不会超出范围)。

recursive subroutine inserttree(t,n)
  implicit none
  type (tree), intent (INOUT) :: t
  integer, intent (IN) :: n

  if (t % isleaf) then
    allocate(t%left)
    allocate(t%right)
    call newleaf(t%left,n)
    call newleaf(t%right,n)

    t % isleaf = .false.
  else
    call inserttree(t % left,n)
  endif
end subroutine inserttree

【讨论】:

  • 解决了这个问题,谢谢!但是,我很难理解为什么前两次插入后输出是正确的。第一次插入后,两片叶子不应该已经失效了吗?另外,为什么我能够在调试器中“手动”访问所有节点?
  • 关于前两个插入的“正确性”,我猜原始叶子的内容还没有被覆盖并且仍然可读。结果,没有报告错误(尽管,valgrind 抱怨未初始化的值......)。关于调试器:我没有真正的想法,但我的经验告诉我,调试器有时可能会改变程序的行为,从而导致程序完美运行。
猜你喜欢
  • 1970-01-01
  • 2018-12-07
  • 1970-01-01
  • 2016-11-06
  • 2021-10-10
  • 1970-01-01
  • 2017-06-05
相关资源
最近更新 更多