CMP EMBEDDED.COM

Login | Register     Welcome Guest  
HOME DESIGN PRODUCTS COLUMNS E-LEARNING CONFERENCES CODE FORUMS/BLOGS NEWSLETTERS CONTACT FEATURES RSS RSS



Programming Pointers: Static vs. Dynamic Initialization

by Dan Saks
Last month, I explained how the C standard grants compilers license to place data into ROM. (See “Placing Data into ROM with Standard C”, November 1998, p. 111.) This month, I’ll look at some reasons why the C++ standard has a harder time granting a similar license to C++ compilers. Whereas the C standard has at least a few explicit statements about read-only memory, the C++ standard has none. In fact, the terms “read-only memory” and “ROM” never appear in the C++ standard, not even in a footnote. However, if you read the standard carefully, you can reasonably deduce that C++ compilers have about as much latitude to place objects into ROM as C compilers do. But you have to read very carefully.

The freedom that C compilers have to place objects in ROM stems from one fairly simple rule appearing in a footnote:

  • The implementation may place a const object that is not volatile in a read-only region of storage
As I explained last month, these words apparently allow for programs that can write to random-access memory (RAM) and then write-protect that RAM at run time. In my experience, very few, if any, embedded systems employ write-protected RAM as ROM. Therefore, I’ll use the term ROM to mean “memory that the program can never write to.”

If the program can never write to ROM, the compiler can place an object into ROM only if it can determine the object’s location and initial contents during compilation and linking. The compiler can do this for objects with static storage, but not for objects with automatic or dynamic storage. Thus, using my more restrictive definition for ROM, C compilers have the following permission:

  • An implementation may place an object into ROM if that object is const and non-volatile, and has static storage
My interpretation of the C++ standard is that it grants compilers similar permission to place objects into ROM. However, while the requirements listed above—that the object must be const, non-volatile, and have static storage—are just as necessary in C++, they are not sufficient. The conditions for placing an object in ROM must account for the added capabilities of C++.

Non-constant initializer expressions
In both C and C++, the optional initializer in a declaration can be an expression rather than just a single value. For example:

int k = j + 1;
initializes k with the value of expression j + 1 . If j is a constant, then j + 1 is a constant-expression , which the compiler can evaluate during compilation.

The initializer for an object of array or structure type can be an initializer-list . That is, it can be a sequence of comma-separated expressions enclosed in braces. For example:

struct rational
{
int num, den;
};
...
rational r = { m + 1, 2 * n + 1 };
initializes r.num with the value of m + 1 and r.den with the value of 2 * n + 1 . Again, if m is constant, then m + 1 is constant, and if n is constant, then 2 * n + 1 is also.

If the previous declaration for k appears in block scope (in a function body), then k has automatic storage. If k ’s declaration appears in global scope or (in the case of C++) in namespace scope, then k has static storage. You can also ensure that k has static storage by adding an extern or static specifier to the declaration. The same is true for r .

This brings us to a point where C++ differs from C. In C, all the expressions in the initializer for an object with static storage must be constant expressions. In C++, they can be non-constant expressions. For example, if you compile:

int f(int j)
{
static int k = j +
1;
...
}
as C, the compiler will reject k’s declaration because the initializer j + 1 is not a constant expression. If you compile it as C++, the compiler will accept it and generate a program that initializes k using the value of j on the first call to function f.

Now consider:

int f(int j)
{
static int const k = j + 1;
...
}
As I explained earlier, a C compiler can place an object into ROM if that object is const, non-volatile, and has static storage. If these requirements were also sufficient for C++, then a C++ compiler could place k into ROM. But clearly, it can’t. Because j + 1 is not constant, the compiler can’t initialize k until run time.

Constructors and destructors
Based on the discussion so far, it appears that a C++ compiler may place an object into ROM if that object is const and non-volatile, has static storage, and all the expressions in its initializer (if present) are constant expressions . However, this rule is still too narrow because it doesn’t account for objects of class types with constructors and destructors.

Consider these C++ declarations:

class T
{
public:
T();
T(int i);
...
};
...
static T const x = 1;
Here, x is an object that’s const and non-volatile with static storage and only constant expressions in its initializer, yet the compiler might not be able to place it into ROM.

Why not? Object x has type T, and T is a class type with a user-defined constructor and destructor. Constructors initialize objects, usually at run time. In the declaration for x just above, 1 is the argument to the constructor T(int). The declaration behaves pretty much as if it were written as:

static T const x(1);
Notice that I didn’t say the compiler can’t place x into ROM; I said it might not be able to. I believe the C++ standard lets compilers place an object of a type with a constructor into ROM if the compiler can figure a way to initialize the object at compile time. The compiler can do this only if the object’s initialization meets certain constraints. Those constraints are too hairy to cover at this time. I’m not even sure I understand them yet.

Destructors that release resources at run time have a similarly chilling effect on the compiler’s ability to place objects into ROM.

Once more, from the top
Let’s recap. A compiler, whether for C or C++, can place an object in ROM only if that object is const, non-volatile, and has static storage. Furthermore, the compiler must be able to initialize the object statically (at compile time) rather than dynamically (at run time).

In C, it’s fairly easy to tell when you’re using static initialization. C has no class types with constructors that perform dynamic initialization. In C, all the expressions in the initializer for an object with static storage must be constant expressions (which can be evaluated at compile time). Thus, the only way that C will initialize a const, non-volatile object in static storage is by static initialization.

In contrast, C++ supports dynamic as well as static initialization for objects with static storage. A declaration might use dynamic initialization if the initializer contains one or more non-constant expressions. A declaration might also use dynamic initialization if it invokes a constructor.

The C standard grants compilers some freedom to transform dynamic initializations into static initializations. However it can be hard to tell if a particular C++ declaration that might use static initialization will actually do so. Thus it can be hard to tell if an object declared const, non-volatile, and with static storage will actually wind up in ROM.

Fortunately, the C++ standard has rules that dictate when a declaration must use static initialization. These rules don’t include all cases where a declaration might use static initialization, but and they can give you a pretty good idea of when you can reasonably expect a C++ compiler to place objects into ROM, and they’re not too complicated. The rules will be the subject of my next column.

Dan Saks is the president of Saks & Associates, and a contributing editor for the C/C++ Users Journal . You can write to him at dsaks@wittenberg.edu.

Return to Embedded.com

Send comments to: Webmaster
All material on this site Copyright © 2000
CMP Media Inc. All rights reserved.

Embedded.com Career Center
Looking for a new job?
SEARCH JOBS

Browse all jobs

SPONSOR
RECENT JOB POSTINGS





 :