Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced C 1992

.pdf
Скачиваний:
92
Добавлен:
17.08.2013
Размер:
4.28 Mб
Скачать

Data Types, Constants, Variables, and Arrays

C C C

 

C2C

 

C C C

 

C

return (0);

}

In Listing 2.1, strcpy() receives two addresses—a destination string and a source string. When the prototype for strcpy() is examined by the compiler, it sees that the second parameter is a constant and that it will not be modified. The first parameter, however—the destination—is not a constant and can be modified. Compiling the example in the listing enables you to determine whether your compiler keeps separate copies of strings that are identical or keeps only one copy (in an attempt to conserve memory). You cannot depend on the compiler to store identical strings either once in memory or separately for each occurrence. Nor can you depend on the compiler (or the CPU) to make a string constant read-only. On some systems, this attempt causes an error (at execution time); on others, the program generally fails.

Except for string constants, obtaining the address of a constant or modifying the constant is not possible. Using the address of operator (&) on a constant isn’t allowed.

Because a string literal can be more than 500 characters long, and because it is difficult (or even impossible) to edit source lines that are that long, you can concatenate string literals. The process is easy because no operator is used—you simply follow one string literal with a second (or third):

char

szMyAddress[] =

“John Q. Public\n” “123 Main Street\n”

“Our Town, NH 03458\n”;

In this code fragment, the variable szMyAddress prints as three lines (because of the embedded \n newline character). The initialization is easier to read because it’s not spread out on a single line; rather, it is formatted the way it should look.

Definitions versus Declarations

There is a difference between defining an object and declaring it. This section looks at the differences and the information that should be provided to the compiler in defining and declaring objects.

29

Part I • Honing Your C Skills

Both data objects (variables) and functions are defined or declared. This chapter discusses only variables; however, the concepts are the same for a function also.

The difference between defining and declaring a data object is that, when a data object is declared, only its attributes are made known to the compiler. When an object is defined, not only are its attributes made known, but also the object is created. For a variable, memory is allocated to hold it; for a function, its code is compiled into an object module.

Because this chapter deals with data objects, this section looks at both declarations and definitions.

Declarations

The simplest declaration of a variable is shown in the following code fragment:

void OurFunction( int nType)

{

int nTest;

nTest = nType;

}

In the fragment, an integer variable is defined. That is, both its attributes (the variable is an integer) were made known to the compiler, and storage was allocated. Because the variable is located in a function, its scope is limited and its life is auto (by default, you can change it). This means that each time OurFunction() is called, the storage for the variable nTest is reallocated automatically (using C’s stack). Notice that nTest wasn’t initialized when it was declared. This isn’t good programming style. To prevent your using an uninitialized variable, I recommend that you initialize all auto variables.

The following fragment shows a declaration for a static variable. The difference is that the static variable’s storage space is allocated by the compiler when the program is compiled; and because the storage space is never reallocated, it remembers its previous value.

30

Data Types, Constants, Variables, and Arrays

C C C

 

C2C

 

C C C

 

C

void OurFunction( int nType)

{

static int nTest;

nTest += nType;

}

You do not initialize this declaration either. Fortunately, however, because the compiler initializes static variables (to zero), the preceding function works and adds nType to nTest every time the function is called. If the function were called enough times, it is likely that nTest would not be capable of holding the constantly increasing sum, and that an integer overflow would occur.

A fatal error? Perhaps, but on most implementations, integer overflow isn’t caught as an error, and on these systems (and compilers), this error doesn’t cause any warning messages to be displayed to the user. The only solution is to make sure that nType, when added to nTest, doesn’t overflow.

Whenever a variable is defined within a function, it has local scope. Whenever a variable is defined outside any functions, it is said to have global scope.

In each of the preceding examples, you have created a variable that is known within the function and that cannot be referenced by any other function. Many programmers (almost all of whom are very good programmers) will argue that a variable should be known within a single function, and for any external data objects to be known, the objects should be passed as parameters.

Experience has shown, however, that this viewpoint can be idealistic. You often will want to share variables between a number of functions, and these variables may be unknown to the caller. Common uses include common buffers, storage areas, flags, indexes, tables, and so on.

To enable a variable to be used by more than one function, it must be declared outside any function—usually very near the top of the source file (see Chapter 1, “The C Philosophy”). An example is shown in Listing 2.2.

31

Part I • Honing Your C Skills

Listing 2.2. An example of a global variable, in a single source file.

long

int

lSum; // Using ‘int’ is optional.

long

int

lCount;

void SumInt(

 

int

nItem)

{

lSum += (long)nItem; ++lCount;

}

void SubInt(

int nItem)

{

lSum -= (long)nItem; —lCount;

}

int Average()

{

int nReturn = 0;

nReturn = (int)(lSum / lCount);

return (nReturn);

}

The preceding code fragment has a set of two functions that add to a sum and count (used to create an average), and return an average.

If you look at the Average() function, you may wonder why I thought that I could divide two long (32-bit) integers and be sure that I would get a returned value that fit in a short (16-bit) integer. The answer is easy because I know that I’ve never added to the sum a value that was larger than would fit into a short integer, and that

32

Data Types, Constants, Variables, and Arrays

C C C

 

C2C

 

C C C

 

C

when the sum was divided by the count, the result had to be smaller than (or equal to) the largest value added. Or, will it? No. I made a bad assumption because SumInt() can add a large number, and SubInt() then could remove a smaller number.

Again, in the preceding example, all three of the functions are located in a single source file. What if each of these functions is large and you need to have three source files? For that, you must use both declarations and definitions.

Definitions

Assume that your three functions are larger than they really are, and that each one therefore has its own source file. In this case, you must declare the variables (but in only one file) and then define them in the other files. Let’s look at what this declaration would look like. Listing 2.3 shows each of the files.

Listing 2.3. An example of a global variable, in three source files.

----------------------FILE-SUMINT.C--------------------------------

/* SUMINT.C routines to sum integers and increment a counter. */

/* Declare the variables that will be shared between these functions. */

long

int

lSum; // Using ‘int’ is optional.

long

int

lCount;

void SumInt(

 

int

nItem)

{

lSum += (long)nItem; ++lCount;

}

----------------------FILE-SUBINT.C--------------------------------

/* Declare the variables that will be shared between these functions. */

continues

33

Part I • Honing Your C Skills

Listing 2.3. continued

extern long

int

lSum;

// Using ‘int’ is optional.

extern long

int

lCount;

 

/* SUBINT.C

routines to de-sum integers and decrement a counter. */

void SubInt(

 

 

int

nItem)

 

 

{

 

 

 

lSum -=

(long)nItem;

 

--lCount;

 

 

}

 

 

 

----------------------

 

FILE-AVERAGE.C--------------------------------

/* AVERAGE.C

routines to

return the average. */

/* Declare the variables

that will be shared between these functions. */

extern long

int

lSum;

// Using ‘int’ is optional.

extern long

int

lCount;

int Average()

 

 

 

{

 

 

 

int nReturn

= 0;

 

 

nReturn = (int)(lSum

/ lCount);

return (nReturn);

}

Noticethatthetwo variables lSum andlCountin theSUBINT.CandAVERAGE.C files are defined—using the extern attribute. This definition tells the compiler what the variables’ attributes are (long int), and tells the compiler not to allocate any memory for these variables. Instead, the compiler writes special information into the object module to tell the linker that these variables are declared in a different module.

34

Data Types, Constants, Variables, and Arrays

C C C

 

C2C

 

C C C

 

C

In both files, this information constitutes a definition of the variable, but not a declaration (which would have allocated the storage for the variable three times—once for each file).

You might ask what would happen if the variables never were declared in any module. The linker (not the compiler) usually is the one to complain, by displaying an error message. The typical error message is that an object was undefined (the message provides the name of the object). Don’t confuse the linker’s use of the word defined with the C compiler’s use of it: The linker doesn’t use the word defined in exactly the same way as the compiler uses it.

When ANSI C uses the modifier static, its meaning changes depending on the context of how it is used. To help you understand the differences, the following section describes variables and their scope and life span.

Variables

Variables make it all happen. Unlike constants, a variable data object can be modified. C’s use of variables can be rather complex when you consider its capability to modify any variable either directly or by using its address. Any data object that can be defined as a singular variable can be defined also as an array. The definition (and use) of arrays is discussed later in this chapter.

Variable Types and Initializing Variables

A variable can be of any type that C supports: an integer or character, or composed of compound data objects—structures or unions. This section discusses some examples.

In the following declaration, nCount is an integer:

int

nCount; /* An integer of default size, uninitialized */

On most PCs, it is a short int; when it is compiled with one of the 32-bit compilers (or under a different operating system), however, it can be a 32-bit long integer.

long

lCount = 0; /* An integer of long size, initialized */

This declaration leaves no doubt about the size of the object. First, because long and short are defaulted to integer types (to create a long double, you must specify long

35

Part I • Honing Your C Skills

double in your declaration), the keyword int is optional. It might be better style to include it (I usually try to). The variable lCount is initialized explicitly; if it were a static variable, this initialization would be optional, but by including it, you can be sure of its value.

char cKeyPressed = ‘\0’;

This declaration is interesting: Because the data type is character, it must be initialized with the correct type. Because character constants are enclosed in single quotes, this initialization works well. I don’t recommend it, but you can use

char cKeyPressed = (char)NULL;

Because the NULL identifier is intended for use as a pointer value, the cast to type char isn’t a smart idea. This hasn’t prevented much C code from being written in exactly this way.

Look at the following floating-point number:

float fTimeUsed = 0.0F;

If this code had been written before the ANSI C standard was written, the initialization probably would look like this:

float fTimeUsed = (float)0.0;

It was necessary to cast the double to a float because there was no other way to specify a float value.

Because the default floating-point constant size is double, the following initialization is fine.

double dTimeUsed = 0.0;

ANSI introduced the long double, a data type that was not often found in various C implementations:

long double fTimeUsed = 0.0L;

Again, because the default floating-point constant is a double, the size is specified in the initializer. This specification definitely is much easier than specifying a cast of (long double), unless you like to type.

This chapter discusses character string declaration later, in the “Arrays” section. In all cases, C creates strings using arrays of type char because there is no distinct data type for strings.

36

Data Types, Constants, Variables, and Arrays

C C C

 

C2C

 

C C C

 

C

Scope (Or I Can See You)

The scope of a variable is often one of the things programmers don’t understand at first. Depending on where they are declared, variables can be either visible or not visible.

Let’s look at an example of scope that shows some poor programming practices. SCOPE.C is created in Listing 2.4. Because the program has two variables with the same name, it can be difficult to know which variable is being referred to.

Listing 2.4. SCOPE.C.

/* SCOPE, written 15 May 1992 by Peter D. Hipson */ /* An example of variable scope. */

#include <stdio.h> /* Make includes first part of file */ #include <string.h>

int main(void); /* Declare main() and the fact that this program doesn’t use any passed parameters. */

int main()

{

int nCounter = 0;

do

{

int nCounter = 0; /* This nCounter is unique to the loop. */

nCounter += 3; /* Increments (and prints) the loop’s nCounter */ printf(“Which nCounter is = %d?\n”, nCounter);

}

while (++nCounter < 10); /* Increments the function’s nCounter */

printf(“Ended, which nCounter is = %d?\n”, nCounter);

return (0);

}

37

Part I • Honing Your C Skills

This is the result of running SCOPE.C:

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Which nCounter is = 3?

Ended, which nCounter is = 10?

Notice that nCounter was never greater than three inside the loop. The reason is that the variable is being reallocated from within the do{} block, and, because it is initialized, it is set to zero when it is reallocated. To create a variable that can be used in the loop and still not have scope outside the loop, you have to create a dummy block:

{

int nCounter = 0; /* This nCounter is unique to the loop */

do

{

nCounter += 3; /* Increments (and prints) the loop’s nCounter */ printf(“Which nCounter is = %d?\n”, nCounter);

}

while (++nCounter < 10); /* Increments the function’s nCounter */

}

This example doesn’t work, however, because the while()’s use of nCounter then uses the wrong nCounter. Only one solution exists: Use unique names for variables when you are declaring them from within a block in a function. Resist the urge, if you are using the style shown in Chapter 1, “The C Philosophy,” to redefine the for() loop index variables—i, j, and so on. Listing 2.5 shows the successful implementation of SCOPE.C.

Listing 2.5. SCOPE1.C.

/* SCOPE1, written 15 May 1992 by Peter D. Hipson */ /* An example of variable scope that works. */

38