Jump to content
xisto Community
Sign in to follow this  
AlanDS

Declarations (and Initialization) getting even more deep in C

Recommended Posts

My Tutorial number 4

 

You must go through my previous tutorials:

 

Previous pages:

http://forums.xisto.com/topic/36088-detailed-c-beginner-tut-basis-for-learning-any-application-programming-language/

http://forums.xisto.com/topic/36089-basic-data-types-and-operators-very-basic-a-must-for-c-coders/

http://forums.xisto.com/topic/36120-statements-and-control-flow-a-c-tutorial-no-3/

 

 

4.1.1 Array Initialization

Although it is not possible to assign to all elements of an array at once using an assignment expression, it is possible to initialize some or all elements of an array when the array is defined. The syntax looks like this:

int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

The list of values, enclosed in braces {}, separated by commas, provides the initial values for successive elements of the array.

(Under older, pre-ANSI C compilers, you could not always supply initializers for ``local'' arrays inside functions; you could only initialize ``global'' arrays, those outside of any function. Those compilers are now rare, so you shouldn't have to worry about this distinction any more. We'll talk more about local and global variables later in this chapter.)

If there are fewer initializers than elements in the array, the remaining elements are automatically initialized to 0. For example,

int a[10] = {0, 1, 2, 3, 4, 5, 6};

would initialize a[7], a[8], and a[9] to 0. When an array definition includes an initializer, the array dimension may be omitted, and the compiler will infer the dimension from the number of initializers. For example,

int b[] = {10, 11, 12, 13, 14};

would declare, define, and initialize an array b of 5 elements (i.e. just as if you'd typed int b[5]). Only the dimension is omitted; the brackets [] remain to indicate that b is in fact an array.

In the case of arrays of char, the initializer may be a string constant:

char s1[7] = "Hello,";

char s2[10] = "there,";

char s3[] = "world!";

As before, if the dimension is omitted, it is inferred from the size of the string initializer. (We haven't covered strings in detail yet--we'll do so in chapter 8--but it turns out that all strings in C are terminated by a special character with the value 0. Therefore, the array s3 will be of size 7, and the explicitly-sized s1 does need to be of size at least 7. For s2, the last 4 characters in the array will all end up being this zero-value character.)

4.1.2 Arrays of Arrays (``Multidimensional'' Arrays)

[This section is optional and may be skipped.]

When we said that ``Arrays are not limited to type int; you can have arrays of... any other type,'' we meant that more literally than you might have guessed. If you have an ``array of int,'' it means that you have an array each of whose elements is of type int. But you can have an array each of whose elements is of type x, where x is any type you choose. In particular, you can have an array each of whose elements is another array! We can use these arrays of arrays for the same sorts of tasks as we'd use multidimensional arrays in other computer languages (or matrices in mathematics). Naturally, we are not limited to arrays of arrays, either; we could have an array of arrays of arrays, which would act like a 3-dimensional array, etc.

The declaration of an array of arrays looks like this:

int a2[5][7];

You have to read complicated declarations like these ``inside out.'' What this one says is that a2 is an array of 5 somethings, and that each of the somethings is an array of 7 ints. More briefly, ``a2 is an array of 5 arrays of 7 ints,'' or, ``a2 is an array of array of int.'' In the declaration of a2, the brackets closest to the identifier a2 tell you what a2 first and foremost is. That's how you know it's an array of 5 arrays of size 7, not the other way around. You can think of a2 as having 5 ``rows'' and 7 ``columns,'' although this interpretation is not mandatory. (You could also treat the ``first'' or inner subscript as ``x'' and the second as ``y.'' Unless you're doing something fancy, all you have to worry about is that the subscripts when you access the array match those that you used when you declared it, as in the examples below.)

To illustrate the use of multidimensional arrays, we might fill in the elements of the above array a2 using this piece of code:

int i, j;

for(i = 0; i < 5; i = i + 1)

{

for(j = 0; j < 7; j = j + 1)

a2[j] = 10 * i + j;

}

This pair of nested loops sets a[1][2] to 12, a[4][1] to 41, etc. Since the first dimension of a2 is 5, the first subscripting index variable, i, runs from 0 to 4. Similarly, the second subscript varies from 0 to 6.

We could print a2 out (in a two-dimensional way, suggesting its structure) with a similar pair of nested loops:

for(i = 0; i < 5; i = i + 1)

{

for(j = 0; j < 7; j = j + 1)

printf("%d\t", a2[j]);

printf("\n");

}

(The character \t in the printf string is the tab character.)

Just to see more clearly what's going on, we could make the ``row'' and ``column'' subscripts explicit by printing them, too:

for(j = 0; j < 7; j = j + 1)

printf("\t%d:", j);

printf("\n");

for(i = 0; i < 5; i = i + 1)

{

printf("%d:", i);

for(j = 0; j < 7; j = j + 1)

printf("\t%d", a2[j]);

printf("\n");

}

This last fragment would print

0: 1: 2: 3: 4: 5: 6:

0: 0 1 2 3 4 5 6

1: 10 11 12 13 14 15 16

2: 20 21 22 23 24 25 26

3: 30 31 32 33 34 35 36

4: 40 41 42 43 44 45 46

 

Finally, there's no reason we have to loop over the ``rows'' first and the ``columns'' second; depending on what we wanted to do, we could interchange the two loops, like this:

for(j = 0; j < 7; j = j + 1)

{

for(i = 0; i < 5; i = i + 1)

printf("%d\t", a2[j]);

printf("\n");

}

Notice that i is still the first subscript and it still runs from 0 to 4, and j is still the second subscript and it still runs from 0 to 6.

4.2 Visibility and Lifetime (Global Variables, etc.)

We haven't said so explicitly, but variables are channels of communication within a program. You set a variable to a value at one point in a program, and at another point (or points) you read the value out again. The two points may be in adjoining statements, or they may be in widely separated parts of the program.

How long does a variable last? How widely separated can the setting and fetching parts of the program be, and how long after a variable is set does it persist? Depending on the variable and how you're using it, you might want different answers to these questions.

The visibility of a variable determines how much of the rest of the program can access that variable. You can arrange that a variable is visible only within one part of one function, or in one function, or in one source file, or anywhere in the program. (We haven't really talked about source files yet; we'll be exploring them soon.)

Why would you want to limit the visibility of a variable? For maximum flexibility, wouldn't it be handy if all variables were potentially visible everywhere? As it happens, that arrangement would be too flexible: everywhere in the program, you would have to keep track of the names of all the variables declared anywhere else in the program, so that you didn't accidentally re-use one. Whenever a variable had the wrong value by mistake, you'd have to search the entire program for the bug, because any statement in the entire program could potentially have modified that variable. You would constantly be stepping all over yourself by using a common variable name like i in two parts of your program, and having one snippet of code accidentally overwrite the values being used by another part of the code. The communication would be sort of like an old party line--you'd always be accidentally interrupting other conversations, or having your conversations interrupted.

To avoid this confusion, we generally give variables the narrowest or smallest visibility they need. A variable declared within the braces {} of a function is visible only within that function; variables declared within functions are called local variables. If another function somewhere else declares a local variable with the same name, it's a different variable entirely, and the two don't clash with each other.

On the other hand, a variable declared outside of any function is a global variable, and it is potentially visible anywhere within the program. You use global variables when you do want the communications path to be able to travel to any part of the program. When you declare a global variable, you will usually give it a longer, more descriptive name (not something generic like i) so that whenever you use it you will remember that it's the same variable everywhere.

Another word for the visibility of variables is scope.

How long do variables last? By default, local variables (those declared within a function) have automatic duration: they spring into existence when the function is called, and they (and their values) disappear when the function returns. Global variables, on the other hand, have static duration: they last, and the values stored in them persist, for as long as the program does. (Of course, the values can in general still be overwritten, so they don't necessarily persist forever.)

Finally, it is possible to split a function up into several source files, for easier maintenance. When several source files are combined into one program (we'll be seeing how in the next chapter) the compiler must have a way of correlating the global variables which might be used to communicate between the several source files. Furthermore, if a global variable is going to be useful for communication, there must be exactly one of it: you wouldn't want one function in one source file to store a value in one global variable named globalvar, and then have another function in another source file read from a different global variable named globalvar. Therefore, a global variable should have exactly one defining instance, in one place in one source file. If the same variable is to be used anywhere else (i.e. in some other source file or files), the variable is declared in those other file(s) with an external declaration, which is not a defining instance. The external declaration says, ``hey, compiler, here's the name and type of a global variable I'm going to use, but don't define it here, don't allocate space for it; it's one that's defined somewhere else, and I'm just referring to it here.'' If you accidentally have two distinct defining instances for a variable of the same name, the compiler (or the linker) will complain that it is ``multiply defined.''

It is also possible to have a variable which is global in the sense that it is declared outside of any function, but private to the one source file it's defined in. Such a variable is visible to the functions in that source file but not to any functions in any other source files, even if they try to issue a matching declaration.

You get any extra control you might need over visibility and lifetime, and you distinguish between defining instances and external declarations, by using storage classes. A storage class is an extra keyword at the beginning of a declaration which modifies the declaration in some way. Generally, the storage class (if any) is the first word in the declaration, preceding the type name. (Strictly speaking, this ordering has not traditionally been necessary, and you may see some code with the storage class, type name, and other parts of a declaration in an unusual order.)

We said that, by default, local variables had automatic duration. To give them static duration (so that, instead of coming and going as the function is called, they persist for as long as the function does), you precede their declaration with the static keyword:

static int i;

 

By default, a declaration of a global variable (especially if it specifies an initial value) is the defining instance. To make it an external declaration, of a variable which is defined somewhere else, you precede it with the keyword extern:

extern int j;

 

Finally, to arrange that a global variable is visible only within its containing source file, you precede it with the static keyword:

static int k;

 

Notice that the static keyword can do two different things: it adjusts the duration of a local variable from automatic to static, or it adjusts the visibility of a global variable from truly global to private-to-the-file.

To summarize, we've talked about two different attributes of a variable: visibility and duration. These are orthogonal, as shown in this table:

duration

visibility automatic static

local normal local static local

variables variables

global N/A normal global

variables

 

We can also distinguish between file-scope global variables and truly global variables, based on the presence or absence of the static keyword.

We can also distinguish between external declarations and defining instances of global variables, based on the presence or absence of the extern keyword.

4.3 Default Initialization

The duration of a variable (whether static or automatic) also affects its default initialization.

If you do not explicitly initialize them, automatic-duration variables (that is, local, non-static ones) are not guaranteed to have any particular initial value; they will typically contain garbage. It is therefore a fairly serious error to attempt to use the value of an automatic variable which has never been initialized or assigned to: the program will either work incorrectly, or the garbage value may just happen to be ``correct'' such that the program appears to work correctly! However, the particular value that the garbage takes on can vary depending literally on anything: other parts of the program, which compiler was used, which hardware or operating system the program is running on, the time of day, the phase of the moon. (Okay, maybe the phase of the moon is a bit of an exaggeration.) So you hardly want to say that a program which uses an uninitialized variable ``works''; it may seem to work, but it works for the wrong reason, and it may stop working tomorrow.

Static-duration variables (global and static local), on the other hand, are guaranteed to be initialized to 0 if you do not use an explicit initializer in the definition.

(Once upon a time, there was another distinction between the initialization of automatic vs. static variables: you could initialize aggregate objects, such as arrays, only if they had static duration. If your compiler complains when you try to initialize a local array, it's probably an old, pre-ANSI compiler. Modern, ANSI-compatible compilers remove this limitation, so it's no longer much of a concern.)

4.4 Examples

Here is an example demonstrating almost everything we've seen so far:

int globalvar = 1;

extern int anotherglobalvar;

static int privatevar;

f()

{

int localvar;

int localvar2 = 2;

static int persistentvar;

}

 

Here we have six variables, three declared outside and three declared inside of the function f().

globalvar is a global variable. The declaration we see is its defining instance (it happens also to include an initial value). globalvar can be used anywhere in this source file, and it could be used in other source files, too (as long as corresponding external declarations are issued in those other source files).

anotherglobalvar is a second global variable. It is not defined here; the defining instance for it (and its initialization) is somewhere else.

privatevar is a ``private'' global variable. It can be used anywhere within this source file, but functions in other source files cannot access it, even if they try to issue external declarations for it. (If other source files try to declare a global variable called ``privatevar'', they'll get their own; they won't be sharing this one.) Since it has static duration and receives no explicit initialization, privatevar will be initialized to 0.

localvar is a local variable within the function f(). It can be accessed only within the function f(). (If any other part of the program declares a variable named ``localvar'', that variable will be distinct from the one we're looking at here.) localvar is conceptually ``created'' each time f() is called, and disappears when f() returns. Any value which was stored in localvar last time f() was running will be lost and will not be available next time f() is called. Furthermore, since it has no explicit initializer, the value of localvar will in general be garbage each time f() is called.

localvar2 is also local, and everything that we said about localvar applies to it, except that since its declaration includes an explicit initializer, it will be initialized to 2 each time f() is called.

Finally, persistentvar is again local to f(), but it does maintain its value between calls to f(). It has static duration but no explicit initializer, so its initial value will be 0.

The defining instances and external declarations we've been looking at so far have all been of simple variables. There are also defining instances and external declarations of functions, which we'll be looking at in the next chapter.

(Also, don't worry about static variables for now if they don't make sense to you; they're a relatively sophisticated concept, which you won't need to use at first.)

The term declaration is a general one which encompasses defining instances and external declarations; defining instances and external declarations are two different kinds of declarations. Furthermore, either kind of declaration suffices to inform the compiler of the name and type of a particular variable (or function). If you have the defining instance of a global variable in a source file, the rest of that source file can use that variable without having to issue any external declarations. It's only in source files where the defining instance hasn't been seen that you need external declarations.

You will sometimes hear a defining instance referred to simply as a ``definition,'' and you will sometimes hear an external declaration referred to simply as a ``declaration.'' These usages are mildly ambiguous, in that you can't tell out of context whether a ``declaration'' is a generic declaration (that might be a defining instance or an external declaration) or whether it's an external declaration that specifically is not a defining instance. (Similarly, there are other constructions that can be called ``definitions'' in C, namely the definitions of preprocessor macros, structures, and typedefs, none of which we've met.) In these notes, we'll try to make things clear by using the unambiguous terms defining instance and external declaration. Elsewhere, you may have to look at the context to determine how the terms ``definition'' and ``declaration'' are being used.

© ADS

 

Notice from BuffaloHELP:
We explained to you that when your work is already published, however yours, you must quote it. When quoting this long, just provide us with the link and contribute with ORIGINAL contents. http://forums.xisto.com/no_longer_exists/

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×
×
  • Create New...

Important Information

Terms of Use | Privacy Policy | Guidelines | We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.