Storage class specifiers and storage duration

Storage class specifiers don't specify scope but combine with scope to determine storage duration. Here's the second part in a series on scope, storage allocation, and linkage.

A declaration is a source code construct that associates attributes with names. A declaration either introduces a name into the current translation unit or redeclares a name introduced by a declaration that appeared earlier in the same translation unit. A declaration might also be a definition , which provides not just some of the attributes of a name, but rather all the information the compiler needs to create the code for that name.

Among the attributes that a name may have are its type, scope, storage duration, and linkage. Not every name has all of these attributes. For example, a function name has a type, a scope, and a linkage, but no storage duration. A statement label name has only a scope.

An object's type determines the object's size and memory address alignment, the values the object can have, and the operations that can be performed on that object. A function's type specifies the function's parameter list and return type. I've discussed the concept of data types in prior columns.1, 2

I devoted my November column to the concept of scope as it applies to C and C++.3 In essence, the scope of a name is that portion of a translation unit in which the name is visible. C and C++ each support several different kinds of scope, summarized in the sidebar entitled “Scope regions in C and C++.”

Scope regions in C and C++
C and C++ each support five different kinds of scope regions. Although the C and C++ standards use different names for some regions and different verbiage to define those regions, the two languages support essentially the same five regions:

  • In C, a name has file scope if it's declared in the outermost scope of a translation unit. C++ extends the concept of file scope to the broader concept of namespace scope. In C++, a name has namespace scope if it's declared either in a namespace definition or in what C calls file scope. The C++ standard refers to the C concept of file scope as global namespace scope , or just global scope .
  • A name (other than a statement label) has block scope if it's declared within a function definition or a block nested therein.
  • A name has function prototype scope if it's declared in the function parameter list of a function declaration that is not also a definition.
  • Each statement label has function scope , which spans the body of the function containing the label.
  • A name in C++ has class scope if it's declared within the brace-enclosed body of a class definition. Classes in C++ include structures and unions, so a member of a C++ structure or union has class scope as well. The C standard doesn't have a corresponding notion of structure scope, but rather says that each structure or union has a separate name space for its members. Despite the different verbiage in their respective standards, C and C++ look up structure and union members in much the same way.

Scope is closely related to, but nonetheless distinct from, the concepts of storage duration and linkage. The storage duration for an object determines how and when the storage for that object comes and goes. Linkage determines whether declarations in different scopes can refer to the same object or function. It's easy to confuse these concepts because they're so intertwined.

Much of the confusion stems from the complex semantics of storage class specifiers, keywords such as extern and static . For example, the precise meaning of static depends on the scope in which it appears. Sometimes, declaring an object static affects the object's storage duration. It can also affect the object's linkage. Understanding these distinctions can help you program more effectively.

This month, I'll explain the syntax of storage class specifiers and the concept of storage duration in C and C++. I'll also show you how they're related to the concept of scope.Storage class specifiers
Storage class specifiers are keywords you can use in declarations to control storage duration and linkage. First I'll show you how they fit into the syntax. Then I'll explain their impact on semantics.

Every declaration in C and C++ has two principal parts: a sequence of zero or more declaration specifiers, and a sequence of zero or more declarators, separated by commas.

For example:

A declarator is the name being declared, possibly surrounded by operators such as * , [] , () , and (in the case of C++) & . In the previous example, *x[N] is a declarator indicating that x is an “array of N pointers to …” something, where that something is the type specified in the declaration specifiers.

A declarator may contain more than one identifier. The declarator *x[N] contains two identifiers, x and N . Only one of those identifiers is the one being declared and it's called the declarator-id . The other(s), if any, must have been declared previously. The declarator-id in *x[N] is x .

(The term declarator-id comes from the C++ standard. The C standard makes do without it, but I find it to be a useful concept.)

Some of the declaration specifiers leading up to a declarator can be type specifiers such as int , unsigned , long , const , or a user-defined type name. They can also be storage class specifiers such as extern or static , or function specifiers such as inline .

The type specifiers contribute to the type of the declarator-id; the other specifiers provide non-type information that applies directly to the declarator-id. For example:

static unsigned long int *x[N];   

declares x as an object of type “array of N pointers to unsigned long int “. The keyword static specifies x 's storage class.

The C standard lists five storage class specifiers: auto , extern , register , static , and typedef ; however, C considers typedef to be a storage class specifier for syntactic convenience only. C++ doesn't consider typedef as a storage class, so I won't either.

The C++ standard lists mutable as another storage class specifier, but this, too, is more for syntactic convenience than anything else. Unlike the other storage class specifiers, mutable has no impact on storage duration or linkage. I don't consider it a storage class specifier for the purpose of this discussion.

A declaration need not have any storage class specifier and can have no more than one.

Storage duration
The storage duration of an object determines the lifetime of the storage for that object. That is, it determines that part of program execution during which storage for the object must exist. Programmers often use the term storage allocation instead of storage duration , but both the C and C++ standards favor the latter. Only objects have storage duration. Enumeration constants, functions, labels, and types don't.

Each object in C and C++ has one of the following three storage durations: static, automatic, and dynamic. (The C standard lists the third kind of storage duration as “allocated” rather than “dynamic” but then never uses the term after that. I'll call it dynamic.)

An object declared at file scope (in C) or namespace scope (in C++), or declared with the storage class specifier extern or static , has static storage duration . The lifetime of the storage for that object is the entire time that the program is executing.

An object declared at block scope, and without the storage class specifier extern or static , has automatic storage duration . The lifetime of the storage for that object begins upon entry into the block immediately enclosing the object's declaration and ends upon exit from the block. Entering an enclosed block or calling a function suspends, but doesn't end, the execution of a block.

When a program allocates storage for an object by calling an allocation function, such as malloc in C or an operator new in C++, that object has dynamic storage duration . The lifetime of the object's storage lasts until the program passes the address of that object to a corresponding deallocation function, such as free in C or an operator delete in C++.

Table 1 shows how C and C++ determine the storage duration for an object based on the storage class specifier in the object's declaration and the scope in which the declaration appears. For example, the first row (below the column headings) says that an object declared with no storage class specifier at block scope has automatic storage duration, but if it appears at file scope in C or at namespace scope in C++, it has static storage duration. If it appears as a structure or class member, then it has the storage duration of the structure or class object of which it's a member.

None of the entries in Table 1 specify dynamic storage allocation. Unlike objects with static or automatic storage duration, a program can't declare any objects with dynamic storage duration. A program can create them by calling an allocation function; it just can't declare them.

View the full-size imageThe mechanics of storage allocation
The exact manner in which static storage is allocated and deallocated depends on the target platform. However, allocating storage for an object with static storage duration typically costs nothing at run time because the compiler, linker, and loader together determine the size and address of the object before the program starts running. From the running program's perspective, an object with static storage duration is always there.

Typical C and C++ programs allocate automatic storage on a run-time stack, often the same stack that they use for storing function-call return addresses. Allocating storage for a local object isn't free, but it's usually dirt cheap–just one machine instruction. For example, in:

int foo(int v)    {    int m;    ...    return m;    }   

function foo has a single local object, m . The compiler determines m 's size from its type, typically 2 or 4 bytes. When it compiles foo , the compiler simply generates an instruction such as:

sub sp, 4   

as one of the first instructions in the function body to carve room for an int on the stack. (This example assumes that an int object occupies 4 bytes and that the stack grows downward from higher addresses to lower addresses.)

Allocating automatic storage for several local objects costs more stack space, but no more run time, than allocating storage for just one. For example, in:

int foo(int v)    {    int m;    double d;    ...    return m + n;    }   

the function has two local objects, m and d . In this case, when it compiles the function, the compiler determines the size of m , still 4, and the size of d , say 8. Rather than generate a separate instruction to allocate storage for each object, the compiler simply adds up the sizes and uses the sum in a single instruction, such as:

sub sp, 12   

A function may also declare local objects in nested blocks. For example, in:

int foo(int n)    {    char *p;    ...    if (p != NULL)        {        int v;        ...        }    return n;    }   

function foo has a block nested within the if statement. That block declares a local object v . In this case, the lifetime of the storage for v begins upon entry into the nested block and ends upon exiting the block. However, many compilers will generate code for foo to allocate the storage for v along with all the other local objects upon entering the function and deallocate the storage for v upon exiting foo . Thus, a compiler might generate code that extends the actual lifetime of the storage for a local object, but it's very hazardous for programs to try to exploit these longer lifetimes.

Dynamic allocation is typically much slower than automatic allocation. It often involves executing tens of instructions, possibly more than a hundred. Nonetheless, you can use it to manage memory very economically, and so it may be worth the price.

Linkage on the horizon
As I mentioned earlier, not only can a declaration specify type, scope, and storage duration, it can also specify linkage. I thought linkage would be the subject of this column until I started writing and realized that I needed to cover storage duration first. I'll get there yet.

Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. For more information about Dan Saks, visit his website at www.dansaks.com. Dan also welcomes your feedback: e-mail him at . For more information about Dan .

Endnotes:
1. Saks, Dan,
“A New Appreciation for Data Types,” Embedded Systems Programming, May 2001, p. 59.
Back

2. Saks, Dan, “Cast with caution,” Embedded Systems Design, July 2006, p. 15.
Back

3. Saks, Dan, “Scope regions in C and C++,” Embedded Systems Design, November 2007, p. 15.
Back

4 thoughts on “Storage class specifiers and storage duration

  1. “One wrong step could cause the entire equation to produce confusing results. This is why it is important to take note of how the equation needs to work before it is being applied to the different functions we have in mind. It is not something that we can

    Log in to Reply
  2. “I was never good with coding so I think it best if I leave all of the tweaking to the professionals. I do know that it's important to understand what each function you're using does. If you want your program or whatever to work properly, you're obviously

    Log in to Reply
  3. “I was never good with coding so I think it best if I leave all of the tweaking to the professionals. I do know that it's important to understand what each function you're using does. If you want your program or whatever to work properly, you're obviously

    Log in to Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.