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 中,公共块是一个共享的东西。我将展示几个示例,但每个示例都将共享 i
和 another_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 中,就像在大多数语言中一样,i
、another_integer
和 my_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 及更早版本)中,您只需要一个大的静态数组,然后使用其中您需要的部分。