更改子例程内的指针时接收内存访问错误

Receiving Memory Access Error when changing a pointer inside a subroutine

我正在使用 Fortran 和 gfortran 4.7.2。我是 Fortran 的新手,正在深入寻找解决我问题的方法。我要使用的程序有很多功能,应该根据给定的条件正确地别名。为此,我想使用指针。

主程序根据模块func_interface中的接口创建指针。基于我想要别名的函数,我编写了一个子例程,该子例程应该将指针更改为所需的函数。尽管如此,我在尝试 运行 程序时收到 'Memory Access Error' - 显然是因为我不理解 Fortran 中的指针或如何将它们传递给子例程以便在子例程中正确更改它们。

有人知道如何更改程序以便以这种方式使用它吗?程序如下

MODULE func_interface
    ABSTRACT INTERFACE
        FUNCTION func(z)
            DOUBLE PRECISION func
            DOUBLE PRECISION, INTENT (IN) :: z
        END FUNCTION func
    END INTERFACE
END MODULE func_interface

SUBROUTINE assign_pointer(i, func_ptr)
    USE         func_interface
    IMPLICIT    NONE

    PROCEDURE (func), POINTER, INTENT(INOUT) ::     func_ptr => NULL ()

    INTEGER, INTENT (IN) :: i

    DOUBLE PRECISION        f1, f2
    EXTERNAL                f1, f2

    SELECT CASE ( i )
        CASE ( 1 )
            func_ptr => f1
            RETURN
        CASE ( 2 )
            func_ptr => f2
            RETURN
    END SELECT
END SUBROUTINE assign_pointer

DOUBLE PRECISION FUNCTION f1(x)
    IMPLICIT            NONE
    DOUBLE PRECISION, INTENT(IN) :: x

    f1 = 2*x
END FUNCTION f1

DOUBLE PRECISION FUNCTION f2(x)
    IMPLICIT            NONE
    DOUBLE PRECISION, INTENT(IN) :: x

    f2 = 4*x
END FUNCTION f2

PROGRAM pointer_test
    USE         func_interface
    IMPLICIT    NONE

    DOUBLE PRECISION    f1, f2
    EXTERNAL            f1, f2

    PROCEDURE (func), POINTER :: func_ptr => NULL ()

    CALL                    assign_pointer( 1, func_ptr )
    WRITE(*, '(1PE12.4)')   func_ptr(5.2D1)

END PROGRAM pointer_test

错误信息:

Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

Backtrace for this error:
#0  0x7F32AFB92667
#1  0x7F32AFB92C34
#2  0x7F32AF14F19F
#3  0x4007CE in assign_pointer_
#4  0x40085B in MAIN__ at pointer_test.f90:0
Speicherzugriffsfehler

francescalus 和 Vladimir 的评论正是您所需要的。下面我建议对您的代码进行简单的重组,我将所有功能都放在现有模块中。我还评论了 external 语句,因为它们对模块中的函数变得无用。 您会在 S.O 上的许多 fortran 问题上找到以下评论。但值得再次放在这里。开始新项目时,您应该坚持使用现代编程技术。最好将程序放在模块中而不是使用外部。这将自动为您构建界面并在编译时进行一些检查。

现在如果你要使用一些已经存在的函数并且你不修改它们,你需要提供显式接口。


感谢francescalus的评论,我修改了主程序中对selected函数的调用,只在初始化时调用。为避免这种情况,可以在过程 assign_pointer.

中处理默认情况
MODULE func_interface
    ABSTRACT INTERFACE
        FUNCTION func(z)
            DOUBLE PRECISION func
            DOUBLE PRECISION, INTENT (IN) :: z
        END FUNCTION func
    END INTERFACE
CONTAINS

    SUBROUTINE assign_pointer(i, func_ptr)
    ! USE         func_interface
        IMPLICIT    NONE

        PROCEDURE (func), POINTER, INTENT(INOUT) ::     func_ptr => NULL ()

        INTEGER, INTENT (IN) :: i

        !DOUBLE PRECISION        f1, f2
        !EXTERNAL                f1, f2

        SELECT CASE ( i )
            CASE ( 1 )
                func_ptr => f1
                RETURN
            CASE ( 2 )
                func_ptr => f2
                RETURN
        END SELECT
    END SUBROUTINE assign_pointer

    DOUBLE PRECISION FUNCTION f1(x)
        IMPLICIT            NONE
        DOUBLE PRECISION, INTENT(IN) :: x

        f1 = 2*x
    END FUNCTION f1

    DOUBLE PRECISION FUNCTION f2(x)
        IMPLICIT            NONE
        DOUBLE PRECISION, INTENT(IN) :: x

        f2 = 4*x
    END FUNCTION f2
END MODULE func_interface


PROGRAM pointer_test
    USE         func_interface
    IMPLICIT    NONE

    !DOUBLE PRECISION    f1, f2
    !EXTERNAL            f1, f2

    PROCEDURE (func), POINTER :: func_ptr => NULL ()

    CALL                    assign_pointer( 1, func_ptr )
    IF(associated(func_ptr))then
        WRITE(*, '(1PE12.4)')   func_ptr(5.2D1)
    ELSE
        ! manage the cas
    END IF
END PROGRAM pointer_test

给出了解决方案的基本方面:扩展模块包含的内容,以便在主程序中为子例程 assign_pointer 提供一个显式接口。我会提供更多细节并解决评论中提出的困难。

首先,看一下(简化的)子程序定义:

subroutine assign_pointer(i, func_ptr)
    use func_interface  ! func is given in here
    procedure(func), pointer, intent(inout) :: func_ptr
    integer, intent(in) :: i
end subroutine assign_pointer

该子例程的伪参数 func_ptr 具有 pointer 属性。正如给定的 这样的属性需要在引用子例程的范围内有一个显式接口。另一个答案显示了如何安排(还有许多其他问题和答案可以找到)。

子例程和函数是外部过程,不会自动具有可用的显式接口。

然后你问了

Although I thought that using USE func_interface is explicitly defining the pointer.. what is the mistake in this thought?

模块 func_interface 包含抽象接口 func。这个抽象接口用于过程指针的声明。但是,如上所述,子例程 assign_pointer 是有问题的。可以看到伪参数

    procedure(), pointer, intent(inout) :: func_ptr

(具有隐式接口)完全独立于模块,但仍然要求子例程的接口在调用范围内是显式的。

所以,抽象接口只是让这个程序工作的一小部分。

甚至那个抽象接口也可能是不必要的。根据 f1f2 的可用方式,我们可以将模块编写为:

module full_mod
 contains
  function f1(..)
  end function f1

  function f2(..)
  end function f2

  subroutine assign_pointer(i, func_ptr)
    procedure(f1), pointer, intent(inout) :: func_ptr
    integer, intent(in) :: i
    ! f1 and f2 available from the host module
  end subroutine assign_pointer

end module

use full_mod
implicit none

procedure(f1), pointer :: func_ptr => NULL()
...
end

也就是说,f1f2 本身可用于提供过程指针的接口,当这些函数在范围内时。


最后一点:伪参数 func_ptr 可能没有显式初始化。一行如

procedure(func), pointer, intent(inout) :: func_ptr => NULL()

正在努力做到这一点。它试图说 func_ptr 最初是分离的。从我上面的代码块中可以看出,=> NULL() 应该被删除。应使用标准指针赋值

procedure(func), pointer, intent(inout) :: func_ptr
func_ptr => NULL()

或者我们可以注意到主程序中的显式初始化

procedure(func), pointer :: func_ptr => NULL()

允许的,并且由于伪参数具有 intent(inout) 属性,它在进入子例程时保留未关联状态。