我写了一个简单的小代码,它复制了我在另一个更大的代码中得到的错误:
PROGRAM allocateBug
IMPLICIT NONE
INTEGER, PARAMETER :: Nx = 10
INTEGER, PARAMETER :: Ny = 20
INTEGER, PARAMETER :: Nz = 30
REAL, ALLOCATABLE, DIMENSION(:,:,:) :: a
ALLOCATE(a(0:Nx-1,0:Ny-1,0:Nz-1))
a(Nx+2,:,:) = 0.4
PRINT*, "size(a) = ", SIZE(a,1)
DEALLOCATE(a)
END PROGRAM allocateBug
代码的输出是:
`size(a) = 10`
以下是以下错误消息:
*** glibc detected *** ./a.out: free(): invalid next size (normal): 0x0000000001a97060 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7f652d0bcb96]
./a.out[0x40719c]
./a.out[0x402ebf]
./a.out[0x402bc6]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7f652d05f76d]
./a.out[0x402ab9]
(... more lines ...)
尝试访问数组a
越界时,我没有收到错误,这是我在ifort中已经知道的一个功能。为什么只在解除分配数组时出错?另外,如果我在a
或Nx
访问Nx+1
,则代码退出时没有错误。
修改
为了澄清我的问题,在打印a
的大小时,代码告诉我它仍然认为a
在第一维中的大小为10。但是,解除分配a
时的错误告诉我,a
的状态在写入超出范围时发生了变化。我对这段代码中发生的事情非常好奇,以便发生错误。
答案 0 :(得分:4)
首先,写出界限是一个未定义的操作,因此整个程序变得不确定。它所做的任何事情都是正确的。您的程序是否正常运行,崩溃,根本不运行,或其他一些选项,这是未定义行为的正确结果,而不是错误。
根据您的评论,您更感兴趣的是错误的写入如何导致无法从低级别角度解除分配,而不仅仅是接受未定义的行为可以做任何想做的事情。
让我们先看看你的数组a
:
a(0:9,0:19,0:29)
大小为(10,20,30)
的,其具有6,000个4字节浮点值的元素,总共24,000个字节的存储空间。您的未定义写入是
a(12,:,:) = 0.4
这将写入数组a(12,0:19,0:29)
的600个元素,但只有一个元素超出范围。元素a(12,19,29)
将写入6003rd元素。其他写入将在边界内,但会通过从超出范围的索引中写入不正确的元素来破坏数组的内容。
如果您的变量a
被分配到地址0x0000-0x5DBF,则元素(9,19,29)将位于地址0x5DBC-0x5DBF,并且您的越界写入元素(12,19,29)将在0x5DC8-0x5DCB,或超出数组末尾8-12个字节。
接下来的内容依赖于实现,并基于对gfortran 4.9.2的分析。
与C不同,Fortran中的数组具有称为"数组描述符"的元数据。 GNU gfortran对4字节实数的数组使用以下描述符:
typedef struct gfc_array_r4 {
GFC_REAL_4 *base_addr;
size_t offset;
index_type dtype;
descriptor_dimension dim[r];
}
变量descriptor_dimension
是一个长度为GFC_MAX_DIMENSIONS
的数组,具有以下结构:
typedef struct descriptor_dimension
{
index_type _stride;
index_type lower_bound;
index_type _ubound;
}
您的示例代码仍然可以告诉您a
的正确大小的原因是此元数据包含该信息。
遵循可分配组件的内部代码路径更加困难,我没有时间进行适当的检查。然而,从粗略的外观来看,似乎有更多的元数据与可分配类型和各种分配策略(malloc和其他)相关联。
我可以从上面得到的唯一一般声明是,gcc内部释放的例程所需的一些重要数据至少部分地位于内存的8-12个字节之外。阵列记忆。当您在超过数组末尾的0到8个字节之间写入内存并注意到没有致命的运行时错误时,您没有覆盖重要数据。您正在破坏的数据的细节以及它相对于您的数组在堆中的排列方式严重依赖于实现,不仅在编译器供应商之间,而且可能在编译器版本之间。
此外,请注意,虽然写入像a(12,0,0)
这样的数组元素,但是相对于已分配的数组内存而言,它的入界点是相对于维度边界的越界。虽然没有边界检查它不会发出运行时错误,但请注意,例如a(12,0,0)
与内存中的a(2,1,0)
元素相同,因此您的写入越界是破坏入境值。
答案 1 :(得分:1)
ALLOCATE(a(0:Nx-1,0:Ny-1,0:Nz-1))
a(Nx+2,:,:) = 0.4
在这里,您沿第一维分配A,范围从0到Nx-1。然后在这些边界之外为它指定一个值,从Nx + 2开始。
糟糕的主意。要么你得到一些奇怪的东西,比如堆损坏,要么设置正确的编译器标志并获得运行时错误。 gfortran抱怨-fcheck =所有
At line 10 of file a.f90
Fortran runtime error: Index '12' of dimension 1 of array 'a' outside of expected range (0:9)
这足以说明错误的位置。