Guru Classic: Automatic Or Static Storage?
January 16, 2019 Susan Gantner
Author’s Note: This tip was first published in August 2008. One thing that has changed in the intervening 10-plus years is that I find a lot more RPGers regularly using subprocedures now. Something that hasn’t really changed much is that many of those using subprocedures still don’t fully understand the behavioral differences between automatic and static storage. The concepts and handling of automatic versus static storage haven’t really changed. So the only modifications I’ve made for this reprise of the tip is to update the style of the code example.
If you write RPG subprocedures, you should know about the differences between automatic and static storage. (If you don’t write subprocedures, shame on you!) I’ve found that many writers and/or users of subprocedures don’t fully understand the differences. So let’s start at the beginning.
By default, fields (i.e., standalone fields, data structures, arrays, etc.) defined inside a subprocedure (a.k.a. local fields) use automatic storage. This means that the storage doesn’t exist until the procedure is called and it is cleaned up when the procedure returns to its caller. Of course, since the storage goes away between calls to the procedure, that means the values are reinitialized between each call to the procedure (and the status of the LR indicator has no impact.)
This differs from fields defined outside of subprocedures (i.e., in the main part of the program – or before the first P spec in the source member) that use static storage. Static storage exists from the time the program is called until the activation group it is running in ends or until the job ends. Notice though that the values in static fields defined outside the P specs are re-initialized on return if the LR indicator was turned on; with LR off, the values remain the same between multiple calls to the program.
You have no choice for the type of storage used for the global fields, but for the local fields in your procedures, you do have the option of making them static by coding the STATIC keyword. This makes them behave almost the same as the global fields. They will retain their values between multiple calls to the procedure.
However, there is still a difference in behavior between local static fields in a procedure and global fields. The local static fields are not impacted by the status of the LR indicator, so they retain their values between calls to the program even if LR was set on before the return.
To see if you’ve got the idea, study the following code and then try to answer these questions.
- On the first call in the job to this *PGM, what values will be displayed for AutoCount, StaticCount, and GlobalCount on each of the three calls?
- After the program has run once, if you were to call it again right away in the same job, what values would you see for them on the second iteration of the program?
- If you issue a RCLACTGRP QILE (or sign off and back on) and call the program a third time, what values do I see?
Ctl-Opt DftActGrp(*NO) ActGrp('QILE') Option(*SrcStmt); Dcl-Pr ProcAuto End-Pr; Dcl-S I Uns(5); Dcl-S GlobalCount Packed(3) Inz; For I = 1 to 3; // Call the subprocedure 3 times ProcAuto(); EndFor; *INLR = *On; Dcl-Proc ProcAuto; Dcl-Pi *N End-Pi; Dcl-S AutoCount Packed(3); Dcl-S StaticCount Packed(3) Static; Dcl-S Reply Char(1); // Used to hold Dsply text on screen StaticCount += 1; AutoCount += 1; GlobalCount += 1; Dsply (' AutoCount=' + %Char(AutoCount) + ', StaticCount=' + %Char(StaticCount) + ', GlobalCount=' + %Char(GlobalCount)) ' ' Reply; End-Proc ProcAuto;
If you answered the following for question 1…
AutoCount=1, StatCount=1, GlobalCount=1 AutoCount=1, StatCount=2, GlobalCount=2 AutoCount=1, StatCount=3, GlobalCount=3
. . . and the following for question 2 . . .
AutoCount=1, StatCount=4, GlobalCount=1 AutoCount=1, StatCount=5, GlobalCount=2 AutoCount=1, StatCount=6, GlobalCount=3
. . . and the following for question 3 . . .
AutoCount=1, StatCount=1, GlobalCount=1 AutoCount=1, StatCount=2, GlobalCount=2 AutoCount=1, StatCount=3, GlobalCount=3
. . . congratulations, you’ve got it!
Of course, all this begs the question of how to initialize or re-initialize static values in a procedure when you need to? The simple answer is to do it manually in the logic, with something like a CLEAR or EVAL operation.
But how do you signal the subprocedure when to re-initialize the static fields? Presumably since you made them static, they should not be initialized on every call. This requires a bit of thought and there are many potential solutions. Many people have a special parameter that is passed to tell the procedure to initialize. It might be a *NoPass parameter at the end of the list that is only used when initialization is required. Another popular option is to make all the parameters *NoPass and when the procedure is called with %Parms = 0, it is the signal to reinitialize.
There are many other options that could be used. The ones you use will likely be different for internal subprocedures versus procedures in a Service Program. But the important thing to remember is that you’ll need to accommodate this in your design if you need to use static fields in a subprocedure.
Now that we’ve covered the different types of storage available to us, when do you choose one over the other? In my own experience, I’ve had very little occasion to use static storage for my local variables. I’m an enthusiastic user of local variables and, for the most part, the fact that they are by default automatically cleaned up and reset on each call is a plus.
If I have a rare occasion when I need to keep a value around across multiple calls — perhaps to count or accumulate something across multiple calls to a procedure — I tend to declare the variable globally and pass it into the procedure as a variable and using the return value to get the updated value back. I don’t update the global variable directly from the procedure because I prefer to restrict my data access and updates in procedures to local data. While it does require a little extra code to pass the parameter and return the new value, I consider that a good thing because it means the call to the procedures self-documents what data is being impacted by the called routine. To me, that’s one of the major advantages of using subprocedures over subroutines.
Of course, this assumes that the counter or accumulated value is needed back in the main program or caller. If not, then simply declaring it as static and then finding a method to reinitialize it (if necessary) works fine.
In this tip, I’ve limited the discussion to local variables, not local files. The use of local files and the important role of the STATIC keyword with them were covered in a tip by Jon Paris here a while ago. I encourage you to check it out to continue your explorations in the use (or not) of static storage.
Susan Gantner, an IBM Champion and co-author of the popular Redbook, Who Knew You Could Do That with RPG IV, is one of the top speakers/writers/trainers on IBM i development topics. She is a partner at Partner400 and System i Developer, and she hosts the RPG & DB2 Summit twice per year with partners Jon Paris and Paul Tuohy.
RELATED STORIES
Subprocedures: Better than Subroutines
The Geezer’s Guide To Free-Form RPG, Part 4: Prototypes and Procedure Interfaces