I am officially declaring all use of non-default array bounds as evil forevermore. If one is writing code strictly for one’s own use and if you’re confident that you won’t need to modify the code ever again, maybe it’s ok, but if you have to return to the code years later or if anyone other than you has to work with it, then I think the byzantine rules around when the bounds will or won’t be preserved are sufficiently complicated and nuanced as to make seemingly simple code modifications problematic. I first noticed this issue four years ago when a related bug essentially killed my chances of delivering on a project just weeks before the deadline because of all the time lost in debugging a related issue. I henceforth abandoned non-default bounds and wrote some code that generates a table describing a few cases mostly as a cheat sheet for myself if I ever encountered non-default bounds again. The table is here. The code that generates the table is described and posted here. But now that I’m working with some inherited code with non-default bounds, I just realized for the first time four years later that the first row in the table is only sometimes true. The code that I just wrote to demonstrate the problem that I just discovered is as follows:
% cat bounds.f90
program main
implicit none
integer, allocatable :: a(:), b(:), c(:), d(:)
a = [2,2,2]
allocate(b(0:size(a)-1)) ! size(b) == size(c)
allocate(c(0:1)) ! size(c) < size(a)
b = a
c = a
print *, lbound(a), ubound(a)
print *, lbound(b), ubound(b)
print *, lbound(c), ubound(c)
end program
% gfortran bounds.f90
% ./a.out
1 3
0 2
1 3
Notice that after the assignment b=a
, the array b
retains its lower bound of 0
because it was already correctly sized for the assignment and therefore didn’t need to be reallocated during the assignment. By contrast, the assignment c=a
resets the lower bound of c
to 1
because c
was initially too small to hold a copy of a
so the assignment reallocates c
and gives it the default lower bound of 1
in the process. So b=a
really does put an exact copy of a
into b
– bounds and all – whereas c=a
only copies the values and shape but not the bounds.
I imagine there could be plenty of people who were already aware of such rules so a great deal of this comes down to habits. If one is used to coding in a certain way but is unaware of the implicit assumptions involved in what they are doing, it can be a painful process when discovering the cases in which those assumptions are violated. I fear for anyone who is new to contributing to a code that uses non-default array bounds.