跳转到内容

Fortran/内存管理

来自维基教科书,自由的教科书

简介和历史背景

[编辑 | 编辑源代码]

在 Fortran90 标准之前的大多数 Fortran 程序使用自包含数据,没有结构,也没有太多共享的结构化数据。但是,可以使用公共块以结构化和非结构化的方式共享数据。此外,Fortran 程序中几乎没有进行内存管理。在 Fortran90 之前,分配的存储甚至不可能,除了通过某些扩展(例如 Cray 指针)。然而,现代 Fortran 支持许多现代编程范式,完全支持可分配数据(包括可分配类型),并允许使用指针。

模块中的共享变量

[编辑 | 编辑源代码]

自从 Fortran90 以来,共享变量通过使用模块得到了方便的管理。在 Fortran90 标准之前,公共块用于定义全局内存;在现代 Fortran 中,不建议使用它们。Fortran 模块还可以包含子程序和函数,但我们将把这些功能的讨论留待以后。至于共享变量的管理,它们可以在模块中定义

module shared_variables
    implicit none
    private
    integer, public, save :: shared_integer
    integer, public, save :: another_shared_integer
    type, public :: shared_type
        logical :: my_logical
        character :: my_character
    end type shared_type
    type (shared_type), public :: shared_stuff
end module shared_variables

请注意,即使模块只包含公共变量,也建议将其声明为私有。虽然 save 是模块中变量的默认值,这意味着它会在模块中的变量使用时保留其先前值,但有时建议明确地进行此操作。然后可以在主程序中使用该模块

program my_example
    use shared_variables, only: shared_integer, shared_stuff
    implicit none
    integer :: some_local_integer

    ! This will work and assign shared_integer to some local variable.
    shared_integer = some_local_integer
    ! This will print the component my_character from type shared_stuff
    ! to stdout.
    write (*,*) shared_stuff%my_character
    ! This, however, will not work, since another_shared_integer was not
    ! imported from the module - the program will not compile.
    shared_integer = another_shared_integer
end program my_example

公共块

[编辑 | 编辑源代码]

在现代 Fortran 标准(Fortran90 及更高版本)中,公共块已被模块中公共变量的使用所取代。但是,由于它们在较旧的 Fortran 标准(77 及更早版本)中的使用,它们在历史上很重要。公共块是 Fortran 在 Fortran90 之前的标准中使用共享的公共存储的方式。在最简单的形式中,公共块是一种定义全局内存的方式。但是要小心。在大多数语言中,公共内存中的每个项目都是作为单独的全局已知名称共享的。然而,在 Fortran 中,公共块是一个共享的东西。我将展示几个示例,但每个示例都将共享 ianother_integer 以及 my_array,一个 10x10 的实数数组。

例如,在 C 中,我可以使用以下方法定义共享内存

int i;
int another_integer;
float my_array[10][10];

并在其他地方使用这些数据

extern float my_array[10][10];
extern int i;
extern int another_integer;

请注意,一个模块声明存储,另一个模块使用存储。另请注意,定义和用法顺序不同。这是因为在 C 中,就像在大多数语言中一样,ianother_integermy_array 都是共享项目。在 Fortran 中并非如此。在 Fortran 中,所有共享此存储的例程都将具有类似于此的定义

common i, another_integer, my_array
integer another_integer
real my_array(10,10)

此公共块存储为数据块,作为可链接的命名结构。唯一的问题是我们不知道它的名字。各种编译器会给这个块起各种名字。在某些系统中,该块实际上没有名称。我们可以通过给结构体起一个名字来避免这个问题。例如,

common /my_block/ i, another_integer, my_array
integer another_integer
real my_array(10,10)

使用此形式,两个不同的 Fortran 程序可以识别相同的存储区域并共享它,而无需知道所有共享存储的结构。同样使用这种格式,C 或其他程序可以共享存储。例如,希望共享此存储的 C 程序将声明相同的存储,如下所示

extern struct {
    int i;
    int another_integer;
    float my_array[10][10];
} my_block;

在上面的示例中,my_block 名称匹配至关重要,以及类型、大小和顺序匹配。但是,由于这些名称仅在本地已知,因此内部名称不必匹配。另请注意,在上面的示例中,Fortran 的 my_array(i,j) 与 C 的 my_block.my_aArray[j][i] 相匹配。

字节对齐

[编辑 | 编辑源代码]

内在数据类型的字节对齐可以通过简单地使用适当的种类来确保。Fortran 没有任何方法可以自动确保派生数据类型是字节对齐的。但是,程序员可以很容易地确保插入适当的数据填充。例如,假设我们有一个派生类型,它包含一个字符和一个整数

type :: my_type
    integer (kind=4) :: ival
    character (len=1) :: letter
end type

此类型的数组将具有大小为 5 字节的元素。如果我们希望此类型数组的元素每 8 个字节对齐一次,我们需要添加 3 个字节的填充。我们可以通过添加仅作为填充的字符来做到这一点。

type :: my_type
    integer (kind=4) :: ival
    character (len=1) :: letter
    character (len=3) :: padding
end type

使用指针的内存管理

[编辑 | 编辑源代码]

在 Fortran 中,可以使用 指针 作为其他数据的某种 别名,例如矩阵中的一行。

指针状态

[编辑 | 编辑源代码]

每个指针都处于以下状态之一

  • 未定义:如果指针没有被初始化,则在定义后立即处于此状态
  • 已定义
    • /未关联:不是任何数据的别名
    • 已关联:某些数据的别名。

内在函数 associated 区分第二种和第三种状态。

我们将使用以下示例:令指针ptr为某个实际值x的别名。

real, target :: x
real, pointer :: ptr
ptr => x

在下一个示例中,我们将使用一个实际矩阵matr作为目标,指针ptr应该成为特定行的别名。

real, dimension (4, 4), target :: matr
real, dimension (:), pointer :: ptr
ptr => matr(2, :)

指针也可以指向其他指针。这会导致它们成为第一个指针所指向的相同数据的别名。请参见下面的示例。

real, target :: x
real, pointer :: ptr1, ptr2
ptr1 => x
ptr2 => ptr1

普通赋值与指针赋值

[编辑 | 编辑源代码]

指针的普通赋值和指针赋值之间的区别可以用以下等式来解释。假设以下设置

real, target :: x1, x2
real, pointer :: ptr1, ptr2
ptr1 => x1
ptr2 => x2

指针的普通赋值会导致它们指向的数据的赋值。可以通过以下两个等效语句来观察这一点。

! Two equal statements
ptr1 = ptr2
x1 = x2

相反,指针赋值会改变其中一个指针的别名,而不会改变底层数据。请参见以下等效示例语句。

! Two equal statements
ptr1 => ptr2
ptr1 => x2

内存分配

[编辑 | 编辑源代码]

在定义指针后,可以使用allocate命令为其分配内存。指针指向的内存可以通过deallocate命令释放。请参见以下示例。

program main
    implicit none
    real, allocatable :: ptr

    allocate (ptr)
    ptr = 1.
    print *, ptr
    deallocate (ptr)
end program main

可分配与指针

[编辑 | 编辑源代码]

您可以声明一个数组具有已知的维度数量,但使用分配可以使其大小未知

real, dimension (:,:), allocatable :: my_array
allocate (my_array(10,10))
deallocate (my_array)

您也可以声明一个指针

real, dimension (:,:), pointer :: some_pointer
allocate (some_pointer(10,10))
deallocate (some_pointer)

在古老版本的 FORTRAN(77 及更早版本)中,您只需要一个大的静态数组,然后使用其中您需要的部分。

华夏公益教科书