Top Posters
Since Sunday
g
3
1
M
1
A free membership is required to access uploaded content. Login or Register.

Ch07 Functions and Program Structure II.docx

Uploaded: 7 years ago
Contributor: bio_man
Category: Programming
Type: Other
Rating: N/A
Helpful
Unhelpful
Filename:   Ch07 Functions and Program Structure II.docx (52.51 kB)
Page Count: 15
Credit Cost: 1
Views: 170
Last Download: N/A
Transcript
CHAPTER 7 Functions and Program Structure II Storage classes There are two attributes associated with every C variable data type (e.g. int, double, etc.) storage class The storage class of a variable tells the compiler how the variable is to be stored It also specifies how long the variable remains in existence, i.e. its lifetime Thirdly, it specifies the variable's visibility, i.e. its scope The scope of a variable determines where, in the program, the variable can be accessed There are four storage classes in C automatic, external, static, and register The corresponding keywords in C to specify such are auto extern static register We shall see in subsequent pages where it is valid to use these storage classes and what they mean Also, variables have a default storage class if we do not specify otherwise Scope Rules The basic rule of scoping: Identifiers are accessible only with the block in which they are declared They are unknown outside the boundaries of that block Blocks may be defined inside of other blocks A variable may be redefined inside an inner block The innermost block's variable declarations take precedence over the outer block Consider the following code fragment: { int a = 2; /*outer block*/ printf ("a=%d", a); /*2 is printed*/ { int a = 5; /*inner block*/ printf ("a=%d, a); /*5 is printed*/ } printf ("a=%d", a); /*2 is printed*/ } An equivalent code fragment is: { int a_outer = 2; printf ("a=%d", a_outer); /*2 is printed*/ { int a_inner = 5; printf ("a=%d, a_inner); /*5 is printed*/ } printf ("a=%d", a_outer); /*2 is printed*/ } Conceptually, a new a is born in the innermost block It lives throughout that inner block, hiding any external a It dies when we leave the block and the outer a then takes effect Parallel and nested blocks (1) Two blocks can come one after another In this case, the second block has no knowledge of the variables declared within the first block Such blocks which reside at the same level are called parallel blocks Functions are declared in parallel at the outermost level One block may exist within another block as well Such a block is called a nested block { int a, b; ... { /*inner block 1*/ float b; ... /*int a is known, but not b*/ } ... { /*inner block 2*/ float a; ... /*int b is known, but not int a*/ /*nothing in inner block 1 is known*/ } ... } Parallel and nested blocks (2) The formal syntax for a statement is as follows statement ::= labeled-statement | expression ; | ; | compound-statement | selection-statement | iteration-statement | jump-statement compound-statement ::= { {declarator}* {statement}* } Notice that any statement can be a compound statement A compound statement may include declarations A compound statement with declarations is a block Variables declared within a block are only visible from the point of declaration to the end of the block The scope of a variable is the section of the program in which it is visible and active Variable names within a block supersede other variables of the same name outside the block (this is called “hiding”) void foo (int i) { if (i > 0) { double i; /* declare a new i */ ... } } The storage class: auto Variables declared within function bodies are by default automatic If a compound statement starts with variable declarations, then these variables can be acted on within the scope of the compound statement A compound statement with declarations will be called a block to distinguish it from those which do not begin with declarations Declarations of variables within a block are implicitly of storage class automatic One may use the keyword auto to explicitly specify this storage class Usage of this keyword is very rare as the default, of any block, is auto typical code fragment Equivalent code fragment { int x, y; double z; ... } { auto int x, y; auto double z; ... } When the block is entered, the system allocates memory for the automatic variables This is usually performed by incrementing the stack frame pointer This is very efficient Automatic variables defined within such a block are considered local to that block When the block is exited, the system deallocates the memory it had allocated for the automatic variables This is usually performed by decrementing the stack frame pointer The initial values of automatic variables, if not specified, is undefined One should assume that its contents are random bits More scope (1) The scope of an object determines who can see what The lifetime of an object determines when it is activated and when it is deactivated The chief reason for blocks is to allow memory for variables to be created where needed Each block is given its own activation frame Conceptually, when a block is entered, space is set aside on the system stack to hold all of its variables If the block is that of a function, space for the function's parameters is also set aside Notice that inner blocks can see the outer block's environment However, the outer block cannot see inside the inner block In the following example, identify which variables are active and visible within each block Also draw the activation frame for each block void foo (void) { int a, b, c, d; ... { /* block-1 */ int c, d, e, f; ... } { /* block-2 */ double c, d, e; ... { /* block-2.1 */ int b, c; ... } } } More scope (2) void foo(void) { int a, b, c, d; ... { /* block-1 */ int c, d, e, f; ... } { /* block-2 */ double c, d, e; ... { /* block-2.1 */ int b, c; ... } } } block variables visible to block foo int a, int b, int c, int d block-1 foo::a, foo::b int c, int d, int e, int f block-2 foo::a, foo::b double c, double d, double e block-2.1 foo::a block-2::d, block-2::e int b, int c More scope (3) void foo(void) { int a, b, c, d; ... { /* block-1 */ int c, d, e, f; ... } { /* block-2 */ double c, d, e; ... { /* block-2.1 */ int b, c; ... } } } \* mergeformat More scope (4) Activation frame upon entering foo() var declaration stack block-name d int d (int) d foo c int c (int) c b int b (int) b a int a (int) a Activation frame upon entering block-1 var declaration stack block-name f int f (int) f block-1 e int e (int) e d int d (int) d c int c (int) c int d (int) d foo int c (int) c b int b (int) b a int a (int) a More scope (5) Activation frame upon entering block-2 var declaration stack block-name e double e (double) e block-2 d double d (double) d c double c (double) c int d (int) d foo int c (int) c b int b (int) b a int a (int) a Activation frame upon entering block-2.1 var declaration stack block-name c int c (int) c block-2.1 b int b (int) b e double e (double) e block-2 d double d (double) d double c (double) c int d (int) d foo int c (int) c int b (int) b a int a (int) a The storage class: register The register specifier may be used to declare heavily used variables Hint to compiler to try to optimize use of variable This does not guarantee that a register will actually be used example register int x; register char c; Can only be applied to automatic variables, i.e. function or function arguments: void f (register unsigned m, register long n) { register int i; ... } The register declaration is taken as advice to the compiler If a register variable cannot be stored in a register, it defaults to automatic Cannot take the address of a register variable (more on this later) register float radius; printf ("Enter radius: "); scanf ("%f", &radius); /*illegal*/ The compiler forbids us from taking the address of radius because it may be stored in a hardware register, not memory Compilers with good optimizers can do the job without register allocations. However, it may be useful in “guaranteeing” optimal register allocation in critical code. The storage class: extern One method of transmitting information across blocks and functions is to use external variables When a variable is declared outside a function, storage is permanently assigned to it its storage class is extern The declaration of a variable outside of a function looks just like those inside a function One does not need to explicitly specify that they use external linkage Such a variable is considered to be global to all functions declared after it The variable exists when the program begins execution and is deallocated when the program terminates #include int a = 1, b = 2, c = 3; int f (void); /*forward declaration of f()*/ int main (void) { printf ("%3d\n", f ()); /* 12 is printed */ printf ("%3d %3d %3d\n", a, b, c); /* 4 2 3 is printed*/ } int f (void) { int b, c; /* masks off global b and c */ a = b = c = 4; return a + b + c; /* return 12 */ } External variables and functions #include int a = 1, b = 2, c = 3; int f (void); /*forward declaration of f()*/ int main (void) { printf ("%3d\n", f ()); /* 12 is printed */ printf ("%3d %3d %3d\n", a, b, c); /* 4 2 3 is printed*/ } int f (void) { int b, c; /*masks off global b and c*/ a = b = c = 4; return a + b + c; /*return 12*/ } Technically speaking, this program file exports has five external symbols: data variables: a, b, c functions: main, f Because we said nothing to the contrary (and soon we will see just how to be contrary), each of the objects defaulted to external linkage Another way we could have written this, though some C compilers might complain when they see it, is... #include extern int a = 1, b = 2, c = 3; extern int f (void); /*forward declaration of f()*/ extern int main (void) /*be explicit about it*/ { printf ("%3d\n", f ()); /* 12 is printed */ printf ("%3d %3d %3d\n", a, b, c); /* 4 2 3 is printed*/ } extern int f (void) { int b, c; /*masks off global b and c*/ a = b = c = 4; return a + b + c; /*return 12*/ } External variables and functions (2) Now, consider splitting this into multiple files In one file we shall have main() and the global variables In another file we shall implement f() /*file1.c*/ #include int a = 1, b = 2, c = 3; extern int f (void); /*look elsewhere for f()*/ int main (void) { printf ("%3d\n", f ()); /* 12 is printed */ printf ("%3d %3d %3d\n", a, b, c); /* 4 2 3 is printed*/ } /*file2.c*/ int f (void) { extern int a; /*look elsewhere for a*/ int b, c; a = b = c = 4; return a + b + c; /*return 12*/ } int g (void) { return a * a; /*declaration of a out of scope*/ } The declaration of a inside of f() using the extern keyword tells the compiler that a is not to be found inside of f() but elsewhere However, a is visible in file2.c only inside of f() because the declaration appears inside of f() External variables and functions (3) The declaration extern int a = 1, b = 2; is a variable definition It forces space to be allocated for a and b The presence of an initializer make this a definition rather than just a declaration In contrast, the declaration extern int a, b; is a variable declaration It tells the compiler about the existence of a and b and that it should look elsewhere for its definition It tells the compiler that the variables are of type int It also tells the compiler that the variables are of storage class external and may be found in another file (compilation unit) No space is set aside for these variables External variables and functions (4) /*file1.c*/ int a = 1, b = 2; int f (void) { a = a * b; return a; } /*file2.c*/ #include int a = 1, b = 3; int f (void) int main (void) { printf ("%3d\n", f ()); printf ("%3d %3d\n", a, b); } In this code fragment, file1.c defines two variables using storage class extern It also defines a function f() using storage class extern The symbol identifier table for file1.c contains three entries: variables a and b, and function f Also, file2.c defines two variables using storage class extern It declares the existence of function f() It defines a function main() using storage class extern Function main() calls a function f() that is not defined in file2.c As a result, the symbol identifier table for file2.c contains three entries: variables a and b, and function main It also contains an undefined reference to a function called f() The linker will examine the tables of both files and determine the following the unresolved reference of f() in file2.c can be resolved by binding it to the function symbol f() in file1.c However, the linker sees two definitions of a and b and cannot determine which ones it should really use As a result, we get a link error: multiple definitions of variables a and b The storage class: static The keyword static alters either the scope or lifetime of the object (or both) The lifetime is extended to be from the beginning to the end of the program the lifetime of a static variable is no longer limited to the block like own variables in Algol when applied to local variables, lifetime is made global (program) The scope is affected only in declarations outside of functions in this case, the visibility of the object is limited to the file (source file) example static char buf[BUFSIZ]; /*visible within file*/ static int bufp = 0; /** ** error function for this file **/ static void error (const char *s) { ... } int getch (void) { static int counter = 0; counter++; /*#-times getch called*/ ... } void ungetch (int c) { static int counter = 0; counter++; /*#-times ungetch called*/ ... } Static variables and functions void foo(void) { int a, b; static double c = 10; { /*block-1*/ static int a; char b; ... { /*block-1.1*/ int d; static double b; ... } } } What does the activation stack look like when block-1.1 is activated? Automatic variables, i.e. those declared within functions and not static, are allocated on the stack Global variables and static variables are usually allocated elsewhere they are usually allocated in some global location they are initialized at the beginning of the program Static variables and functions (2) Before entering foo(), all of the global variables and static variables are allocated For the sake of this discussion, assume that all variables are allocated on the stack Global variables and static variables will be put at the bottom of the stack \* mergeformat Linkage file1.c file2.c extern int sp; extern double val[]; void push (double f){...} double pop (void) {...} int sp = 0; double val[MAXVAL]; void push (double f); extern double pop (void); file1.c contains external declarations for variables sp and val file1.c contains the definitions for functions push and pop file2.c contains definitions for variables sp and val file2.c contains the external declarations for functions push and pop It is important to distinguish between a declaration of an object and its definition A declaration announces the properties of an object (for variables this is primarily its type, for functions it includes the return value and the parameters) A definition also causes storage to be set aside (in the case of a variable) or includes the code making up the function In file2.c, because the lines int sp = 0; double val[MAXVAL]; appear outside of a function, they define the variables sp and val to be external and cause storage to be set aside On the other hand, the lines in file1 extern int sp; extern double val[]; declare for the rest of the source file that sp is an int and that val is a double array (whose size is determined elsewhere) They do not reserve any storage for them The storage specifier extern simply asserts that this function can be used externally; in this case, no change in meaning Linkage (2) file1.c file2.c int a = 1; int b = 1; extern int c; int a; extern double b; extern int c; If these two files are compiled and linked together as a program, there are three problems with it 1. Variable a is defined twice in file1.c, it is defined "int a=1;" in file2.c, it is defined "int a=0;" 2. Variable b is declared twice with different types in file1.c, b is defined to be an integer in file2.c, b is declared to be a double 3. Variable c is declared extern twice but never defined None of these problems will be caught by the compiler The linker will probably catch the first problem and report a "multiple definition of a" The linker will also catch the third problem and report that "variable c is undefined" However, the linker will probably fail to catch the second problem and the result will be strange results at runtime Linkage (3) file1.c file2.c int a; int f (void) { return a; } int a; int g (void) { return f (); } The first problem with this is that variable a is multiply defined This is the case even though it has the same initial value Many C systems would handle this fine because the linkers would not complain as long as the initial values of the variables matched For portability, one should define a variable in one place, and declare it extern within a common header file Another problem is that in file2.c, function f() is never declared In C this is not a problem because f() defaults to int In C++, this is a problem because functions must be declared before being used file1.c file2.c static int a = 6; static int f (void) { ... } static int a = 7; static int f (void) { ... } A name may be made local to a file by declaring it static In this example, because f() and a are declared static in file1.c and file2.c, the resulting program is correct In this example, file1.c and file2.c each have their own respective variable a and function f() An object or a function with a name that is local to a file is said to have internal linkage Conversely, an object or a function with a name that is not local to a file is said to have external linkage Initialization If no initialization has been specified... external (global) and static variables are initialized to zero automatic and register variables are undefined (garbage) Scalar and floating point variables can be initialized when defined, i.e. int x = 1; double d = 4.1; char squote = '\''; long day = 1000 * 60 * 60 * 24; /*microsec/day*/ For external and static variables, the initializer must be a constant expression the initialization is performed once conceptually done before the program begins execution For automatic and register variables, initialization is done each time a function or block is entered initializer is not restricted to being a constant it may be any expression involving previously defined values it may even be function calls Initialization (2) example FILE *openfile (const char *filename) { FILE *fp = fopen (filename, "r"); if (fp == 0) perror (filename); return fp; } example int binsearch (int x, int v[], int n) { int low = 0; int high = n - 1; int mid = (low + high) / 2; ... } instead of... int binsearch (int x, int v[], int n) { int low, high, mid; /* … gap … */ low = 0; high = n - 1; mid = (low + high) / 2; ... }

Related Downloads
Explore
Post your homework questions and get free online help from our incredible volunteers
  783 People Browsing
Your Opinion
Which industry do you think artificial intelligence (AI) will impact the most?
Votes: 798