本节摘要:类型系统解决了"数据是什么",本节解决"数据归谁管、活多久"。allocatable 用所有权语义换来自动释放的安全网,pointer 用灵活关联换来悬空指针与泄漏的风险。加上 move_alloc 的零拷贝交接,本节给出一条明确的设计路线:默认 allocatable,必要时指针,交接用 move_alloc。读完你能为任何一块动态内存说清它的出生、归属与销毁。
数值模拟的问题规模是运行时才知道的:网格细化后节点数翻倍,粒子注入后数量暴涨。声明一个固定上限的数组,要么浪费内存,要么运行时溢出崩溃。动态内存是绕不开的,而 Fortran 给了两条路:allocatable 与 pointer。两条路长得像,哲学截然不同——一条是"我拥有这块内存",另一条是"我只是看着它"。选错路,轻则泄漏,重则悬空。
allocatable 变量拥有自己的内存:分配后由它独占,离开作用域时自动释放,不需要你记得 deallocate。这就是 RAII 思路——资源的生命周期绑定到变量的生命周期。
subroutine work_on_grid(n) use iso_fortran_env, only: real64 implicit none integer, intent(in) :: n real(real64), allocatable :: field(:) allocate(field(n)) ! 按需分配 field = 1.0_real64 write(*,*) 'size =', size(field) ! 子程序结束,field 自动释放,无需手动 deallocate end subroutine work_on_grid
allocated() 可以查询分配状态,这是防御性编程的基础:访问之前先确认内存真的在。
if (.not. allocated(field)) allocate(field(n)) field = field + 1.0_real64
注意 allocatable 数组的赋值是深拷贝:b = a 会把 a 的内容整体复制进 b 自己的内存。语义安全,但大数组赋值有拷贝开销,这引出后面 move_alloc 的存在理由。
pointer 的核心语义是"关联"而非"拥有"。它指向某个 target,不持有内存;超出作用域时,被指的内存不会自动释放。两份危险就此产生:内存泄漏(指向动态内存的指针丢了,那块内存成了孤岛)和悬空指针(目标已释放,指针还留着旧地址)。
subroutine risky(n) use iso_fortran_env, only: real64 implicit none integer, intent(in) :: n real(real64), pointer :: p(:) real(real64), allocatable, target :: storage(:) allocate(storage(n)) p => storage ! 指针关联到 target p = 0.0_real64 deallocate(storage) ! storage 没了 ! p 现在是悬空指针:再访问就是未定义行为 end subroutine risky
指针的两大用途是值得保留的:一是构建链表、树这类结构(节点之间靠关联而不是连续存储),二是多态引用(第 5 章里 class(*) 的载体必须是指针或 allocatable 多态)。用指针就要立规矩:声明时初始化为 null(),解除关联后立即重置,每次解引用前用 associated() 检查。
把一个大数组从函数里"运"出来,传统做法是复制——数组越大,代价越高。move_alloc 干的是另一件事:把源数组的内存描述符直接转给目标,源变成未分配。代价 O(1),不搬数据。这在迭代求解器交换新旧缓冲区时尤其划算。
subroutine swap_buffers(a, b) use iso_fortran_env, only: real64 implicit none real(real64), allocatable, intent(inout) :: a(:), b(:) real(real64), allocatable :: tmp(:) call move_alloc(a, tmp) ! a 的内存 → tmp,a 变未分配 call move_alloc(b, a) ! b 的内存 → a call move_alloc(tmp, b) ! tmp 的内存 → b end subroutine swap_buffers
三行代码完成两块缓冲区的角色互换,全程零数据拷贝。代价是契约:move_alloc 之后源对象不可再用。把"内存归谁"写清楚,是这类代码能长期活着的前提。

第一,类型组件优先 allocatable——自动释放、布局连续,对 SIMD 友好;只有实现多态或链表树结构才用指针。第二,热循环内部禁止分配与释放,系统调用与堆管理开销会被放大成性能灾难;做法是在循环外一次性分配工作数组,循环内复用。第三,长期运行的模拟考虑内存池思路:预分配一大块,逻辑上切成子区域按需分发,减少堆碎片化。这三点配合 move_alloc 交接,就能把"内存归谁管"这件最容易烂尾的事做成明确的契约。
阅读完本节,你应当能够:
内存管好了,下一步是 Fortran 的看家本领——数组编程。