Your allocations and deallocations will occasionally fail. This can happen if you try to allocate more memory than available, allocate an object that’s already allocated, or free an object that has been freed. When it happens, the program will abort. However, the allocate statement also comes with built-in error handling if you want finer control over what happens when the allocation fails. You can use
allocate(u(im), stat=stat, errmsg=err)
where stat and errmsg are optional arguments:
stat–Anintegerthat indicates the status of theallocatestatement.statwill be zero if allocation was successful; otherwise, it will be a nonzero positive number.errmsg–Acharacterstring that contains the error message if an error occurred (such asstatbeing nonzero) and is undefined otherwise.
By using the built-in error handling, you get the opportunity to decide how the program should proceed if the allocation fails. For example, if there isn’t enough memory to allocate a large array, perhaps we can split the work into smaller chunks. Even if you want the program to stop on allocation failure, this approach lets you handle things gracefully and print a meaningful error message.
Tip If you want control over what happens if (de)allocation fails, use stat and errmsg in your allocate and deallocate statements to catch any errors that may come up. Of course, you’ll still need to tell the program what to do if an error occurs; for example, stop the program with a custom message, print a warning message and continue running, or try to recover in some other way.
We can use the built-in error handling in our stock analysis app. However, we’re going to need this for several arrays. This seems suitable to implement once in a subroutine, and then reuse it as needed. That’s the goal of our first exercise for this chapter, as shown in the “Exercise 1” sidebar.
Exercise 1: Convenience (de)allocator subroutines
Explicitly allocating and deallocating arrays can be quite tedious. This is especially true if you decide to make use of the built-in error handling. If you’re working with many different arrays at a time, this can quickly build up to a lot of boilerplate code.
For this exercise, write subroutines for allocation and deallocation that handle the allocation status, as well as handling errors:
- Write a subroutine,
alloc, that allocates a given array with a given integer size. If the input array is already allocated, free it from memory first. (See #2.) - Write a subroutine,
free, that takes an input allocatable array and deallocates it. If the input array is not allocated, do nothing and return.
Both subroutines should use the stat and errmsg arguments to catch and report any errors if they occur. Once implemented, you should be able to allocate and free your arrays like this:
call alloc(a, 5)
! do work with a
call free(a)
You can find the solution in the “Answer key” section near the end of this chapter, or in the stock-prices repository in stock-prices/src/mod_alloc.f90.
We’ll use these convenience subroutines to greatly reduce the boilerplate in the read _stock subroutine. Be aware, however, that convenience procedures like this add a layer of abstraction over existing code. This can be a blessing and a curse–abstractions help reduce the amount of boilerplate code we need to write, but they also obscure how things are implemented under the hood, which may add to cognitive complexity for a reader who’s trying to understand how the code works. Use abstractions carefully and mindfully.