CMPSC 311, Introduction to Systems Programming

Types and Objects

Scope, Linkage, Lifetime, Location



Reading
References



An expression is a sequence of operators and operands (with punctuators) that
A type is a set of values and a set of operations on those values.
Types in C are partitioned into
The meaning of a value stored in an object or returned by a function is determined by the type of the expression used to access it.
A declaration specifies the interpretation and attributes of a set of identifiers.
A definition of an identifier is a declaration for that identifier that
Notes
A declaration specifies the linkage, the storage duration, the scope, and (part of) the type of the entities that are being declared.
The placement of a declaration can affect the properties of the identifier being declared.
Declarations for object identifiers specify a type, with type qualifiers, and a storage class specifier.
A declarator describes the properties of an identifier, in a declaration.
A type specifier describes the properties of a type, in a declaration.
We'll ignore statement labels and preprocessor macros for now, as they do not represent objects or properties of objects.



The syntax of declarations in C11 (but not all of it; for example, we left out static assertions and alignment specifiers)

declaration:
declaration-specifiers  init-declarator-listopt ;

declaration-specifiers:
storage-class-specifier  declaration-specifiersopt
type-specifier  declaration-specifiersopt
type-qualifier  declaration-specifiersopt
function-specifier  declaration-specifiersopt

init-declarator-list:
init-declarator
init-declarator-list , init-declarator

init-declarator:
declarator
declarator = initializer

storage-class-specifier:
typedef
extern
static
_Thread_local
auto
register

type-specifier:
void
char
short
int
long
float
double
signed
unsigned
_Bool
_Complex

atomic-type-specifier
struct-or-union-specifier

enum-specifier
typedef-name

type-qualifier:
const
restrict
volatile

_Atomic

function-specifier:
inline
_Noreturn

etc., etc.

There are some constraints on declarations:



An object (in C) is a region of memory that can be examined and stored into.
From the C Standard,
Not everything is specified completely.
An lvalue is an expression that refers to an object in such a way that the object may be examined or altered.
A modifiable lvalue is an expression that refers to an object in such a way that the object may be altered.
For example,
const int foo = 13;
int blat;
int *iptr = &blat;
defines an identifier foo, and while the expression foo is an lvalue, it is not a modifiable lvalue.  The identifier iptr is an lvalue, and a modifiable lvalue; the expression *iptr is also an lvalue and a modifiable lvalue.
More about lvalues and rvalues
A function designator is a value of function type.

For example,
int bar(void);
int *gptr(void);
int (*fptr)(void) = bar;
declares a function bar of type "function returning int", declares a function gptr of type "function returning pointer to int", and defines an object fptr of type "pointer to function returning int".  The expression fptr is an lvalue, and a modifiable lvalue, while the expressions bar, gptr and *fptr are function designators.
Any number of derived types can be constructed from the object, function, and incomplete types, as follows:
Some further notes



(Excerpt from the C Standard, lightly edited)

The address and indirection operators, unary & and *
The unary & operator yields the address of its operand. The unary * operator denotes indirection.



(Excerpt from the C Standard, lightly edited)

The sizeof operator
Examples
The _Alignof operator (C11)



A quick review and some vocabulary

Types and type qualifiers (more about these later) Types and storage qualifiers (more about these later)
The const keyword
External agents and the volatile keyword
Aliases and the restrict keyword



Scope - over which region of the program is something defined and accessible?
Linkage - how can we connect declarations and definitions between scopes?

Lifetime - over what length of time is something allocated?
Location - where is something allocated in memory?
What can affect the content of memory?



Scope - over which region of the program is something defined and accessible?
Related concepts
Examples

extern int foo;  // referencing declaration
int foo;         // defining declaration

extern void f(void);   // referencing declaration
void g(void) { f(); }  // reference to f
void f(void) { g(); }  // defining declaration of f


Exercise.  If a variable is "out of scope", does that mean it no longer has any associated storage?



An identifier in C is a name for something.

An identifier in C can denote
The same identifier can denote different entities at different points in the program.
There are separate name spaces for various categories of identifiers,
There are four kinds of scopes in C,
Function prototype scope
Function scope
Block scope (also called local scope) File scope
Properties of scopes
% gcc -Wall -Wextra -c x.c
x.c: In function 'foo':
x.c:1: error: 'x' redeclared as different kind of symbol
x.c:1: error: previous definition of 'x' was here
x.c: At top level:
x.c:1: warning: unused parameter 'x'
Where does a scope begin?
We skipped preprocessor macros for convenience.  Their scope extends from a #define to the end of the source file, or to a corresponding #undef.



More about block scope

A block allows a set of declarations and statements to be grouped into one syntactic unit.  The initializers of objects that have automatic storage duration, and the variable length array declarators of ordinary identifiers with block scope, are evaluated and the values are stored in the objects (including storing an indeterminate value in objects without an initializer) each time the declaration is reached in the order of execution, as if it were a statement, and within each declaration in the order that declarators appear.

A compound statement is a block.

compound-statement:
    { block-item-listopt }

block-item-list:

    block-item
    block-item-list block-item

block-item:

    declaration
    statement

A selection statement is a block whose scope is a strict subset of the scope of its enclosing block.
selection-statement:
    if ( expression ) statement
    if ( expression ) statement else statement
    switch ( expression ) statement

An iteration statement is a block whose scope is a strict subset of the scope of its enclosing block.
iteration-statement:
    while ( expression ) statement
    do statement while ( expression ) ;
    for ( expressionopt ; expressionopt ; expressionopt ) statement
   
for ( declaration expressionopt ; expressionopt ) statement
The statement
for ( clause-1 ; expression-2 ; expression-3 ) statement
behaves as follows
Thus, clause-1 specifies initialization for the loop, possibly declaring one or more variables for use in the loop; the controlling expression, expression-2, specifies an evaluation made before each iteration, such that execution of the loop continues until the expression compares equal to 0; and expression-3 specifies an operation (such as incrementing) that is performed after each iteration.

Both clause-1 and expression-3 can be omitted. An omitted expression-2 is replaced by a nonzero constant.

continue statements do not impact the current scope.  break statements terminate execution of the smallest enclosing switch or iteration statement, so execution leaves the current scope.  return statements leave all scopes entered since most-recently calling the function.



An identifier declared in different scopes or in the same scope more than once can be made to refer to the same object or function by a process called linkage.
There are three kinds of linkage:
In the set of translation units and libraries that constitutes an entire program, each declaration of a particular identifier with external linkage denotes the same object or function.  Within one translation unit, each declaration of an identifier with internal linkage denotes the same object or function.  Each declaration of an identifier with no linkage denotes a unique entity.

If the declaration of a file scope identifier for an object or a function contains the storage class specifier static, the identifier has internal linkage.  [A function declaration can contain the storage-class specifier static only if it is at file scope.]

For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible, if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration.  If no prior declaration is visible, or if the prior declaration specifies no linkage, then the identifier has external linkage.

If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern.  If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

The following identifiers have no linkage: an identifier declared to be anything other than an object or a function [a label or a type, for ex.]; an identifier declared to be a function parameter; a block scope identifier for an object declared without the storage-class specifier extern.

If, within a translation unit, the same identifier appears with both internal and external linkage, the behavior is undefined.



An object has a storage duration that determines its lifetime, or extent.

Lifetime - over what length of time is something allocated?
Equivalent terms Equivalent terms?
Quick summary
Exercises
Storage class specifiers in C
There are four storage durations in C.

storage duration extent
address space,
section
storage class specifier
static
static
text, data
extern, static
automatic local
stack
auto
allocated dynamic
heap

thread (C11)
thread
thread-specific
_Thread_local
The key difference between extern and static for file-scope objects and functions:
The key difference between auto (the default) and static for block-scope variables:
The lifetime of an object is the portion of program execution time during which storage is guaranteed to be reserved for it.
An object whose identifier is declared without the storage-class specifier _Thread_local, and either with external or internal linkage, or with the storage-class specifier static, has static storage duration.  Its lifetime is the entire execution of the program and its stored value is initialized only once, prior to program startup.

An object whose identifier is declared with the storage-class specifier _Thread_local has thread storage duration.  Its lifetime is the entire execution of the thread for which it is created, and its stored value is initialized when the thread is started.  There is a distinct object per thread, and use of the declared name in an expression refers to the object associated with the thread evaluating the expression.  The result of attempting to indirectly access an object with thread storage duration from a thread other than the one with which the object is associated is implementation-defined.

An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic storage duration.
For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way.  (Entering an enclosed block or calling a function suspends, but does not end, execution of the current block.)  If the block is entered recursively, a new instance of the object is created each time.  The initial value of the object is indeterminate.  If an initialization is specified for the object, it is performed each time the declaration is reached in the execution of the block; otherwise, the value becomes indeterminate each time the declaration is reached.

For such an object that does have a variable length array type, its lifetime extends from the declaration of the object until execution of the program leaves the scope of the declaration.  (Leaving the innermost block containing the declaration, or jumping to a point in that block or an embedded block prior to the declaration, leaves the scope of the declaration.)  If the scope is entered recursively, a new instance of the object is created each time.  The initial value of the object is indeterminate.



Location - where is something allocated in memory?



What can affect the content of memory?



Common bugs associated with misunderstanding Scope



Common bugs associated with misunderstanding Scope and Lifetime



Common bugs associated with misunderstanding Location



Some notes on identifiers (choice of names)
Identifiers to avoid when choosing your own names - these are already used, etc.
An experiment with the C99 predefined identifier __func__

% cat foo.c
#include <stdio.h>
int foo(void) { return 0; }
int bar(void) { return 1; }
int hack_Foo(void) { printf("%s\n", __func__); return 0; }
int hack_Bar(void) { printf("%s\n", __func__); return 1; }

% gcc -std=c99 -c foo.c


% strings foo.o

hack_Foo
hack_Bar

Can you explain this version of the experiment?

% cat foo.c
#include <stdio.h>
int foo(void) { printf("%s\n", __func__); return 0; }
int bar(void) { printf("%s\n", __func__); return 1; }

% gcc -std=c99 -c foo.c


% strings foo.o

% strings -n 2 foo.o
}h
=k
$}
foo
%s
bar




Last revised, 8 Apr. 2013