Chapter 4: Variables and Constants

What's in Chapter 4?

A static variable exists permanently
A static global can be accessed only from within the same file
A static local can be accessed only in the function
We specify volatile variables when using interrupts and I/O ports
Automatic variables are allocated on the stack
We can understand automatics by looking at the assembly code
A constant local can not be changed
External variables are defined elsewhere
The scope of a variable defines where it can be accessed
Variables declarations
8-bit variables are defined with char
Discussion of when to use static versus automatic variables
Initialization of variables and constants
We can understand initialization by looking at the assembly code

The purpose of this chapter is to explain how to create and access variables and constants. The storage and retrieval of information are critical operations of any computer system. This chapter will also present the C syntax and resulting assembly code generated by the ImageCraft and Metrowerks compilers.

A variable is a named object that resides in RAM memory and is capable of being examined and modified. A variable is used to hold information critical to the operation of the embedded system. A constant is a named object that resides in memory (usually in ROM) and is only capable of being examined. As we saw in the last chapter a literal is the direct specification of a number character or string. The difference between a literal and a constant is that constants are given names so that they can be accessed more than once. For example

short MyVariable;          /* variable allows read/write access */
const short MyConstant=50; /* constant allows only read access */
#define fifty 50
void main(void){
    MyVariable=50;         /* write access to the variable */
    OutSDec(MyVariable);   /* read access to the variable */
    OutSDec(MyConstant);   /* read access to the constant */
    OutSDec(50);           /* "50" is a literal */
    OutSDec(fifty);        /* fifty is also a literal */
}

Listing 4-1: Example showing a variable, a constant and some literals

With ICC11 and ICC12 both int and short specify to 16-bit parameters, and can be used interchangeably. The compiler options in Metrowerks can be used to select the precision of each of the data formats. I recommend using short because on many computers, int specifies a 32-bit parameter. As we saw in the last chapter, the ICC11 and ICC12 compilers actually implement 32-bit long integer literals and string literals in a way very similar to constants.

The concepts of precision and type (unsigned vs. signed) developed for numbers in the last chapter apply to variables and constants as well. In this chapter we will begin the discussion of variables that contain integers and characters. Even though pointers are similar in many ways to 16 bit unsigned integers, pointers will be treated in detail in Chapter 7. Although arrays and structures fit also the definition of a variable, they are regarded as collections of variables and will be discussed in Chapter 8 and Chapter 9.

The term storage class refers to the method by which an object is assigned space in memory. The ImageCraft and Metrowerks compilers recognize three storage classes--static, automatic, and external. In this document we will use the term global variable to mean a regular static variable that can be accessed by all other functions. Similarly we will use the term local variable to mean an automatic variable that can be accessed only by the function that created it. As we will see in the following sections there are other possibilities like a static global and static local.

Statics

Static variables are given space in memory at some fixed location within the program. They exist when the program starts to execute and continue to exist throughout the program's entire lifetime. The value of a static variable is faithfully maintained until we change it deliberately (or remove power from the memory). A constant, which we define by adding the modifier const, can be read but not changed.

In an embedded system we normally wish to place all variables in RAM and constants in ROM. In the ICC11/ICC12 compilers we specify the starting memory address for the static variables in the options_compiler_linker dialog with the data section. The constants and program instructions will be placed in the text section. For more information on how to set the absolute addresses for statics (data section), automatics (stack), and program object codes (text section) using ICC12, see ICC12 options menu for developing software for the Adapt812 The ICC11/ICC12 compilers place the static variables in the bss area, which we can view in the assembly listing following the .area bss pseudoop. The ICC11/ICC12 compilers place the constants and program in the text area, which we can view in the assembly listing following the .area text pseudoop.

At the assembly language ICC11/ICC12 uses the .blkb directive to define a block of uninitialized bytes. Each static variable has a label associated with its .blkb directive. The label consists of the variable's name prefixed by a compiler generated underscore character. The following example sets a global, called TheGlobal, to the value 1000. This global can be referenced by any function from any file in the software system. It is truly global.

short TheGlobal;   /* a regular global variable*/
void main(void){
    TheGlobal=1000;    
}

Listing 4-2: Example showing a regular global variable

In assembly language the ICC11 assembler defines a label to be global (can be accessed from modules in other files) using the .global pseudoop. The 6811 code generated by the ICC11 (Version 4) compiler is as follows

    .area text
    .global _main
_main:
    ldd #1000
    std _TheGlobal
    rts
    .area bss
    .global _TheGlobal
_TheGlobal: .blkb 2

In assembly language the ICC12 assembler defines a label to be global (can be accessed from modules in other files) using the :: symbol after the label. The 6812 code generated by the ICC12 (Version 5.1) compiler is as follows

    .area text
_main::
    movw #1000,_TheGlobal
    rts
    .area bss
_TheGlobal:: .blkb 2

The 6812 code generated by the Metrowerks compiler is as follows

main:
    LDD #1000
    STD TheGlobal
    RTS

The fact that these types of variables exist in permanently reserved memory means that static variables exist for the entire life of the program. When the power is first applied to an embedded computer, the values in its RAM are usually undefined. Therefore, initializing global variables requires special run-time software consideration. The ICC11/ICC12 compilers will attach the assembly code in the CRT11.s/CRT12.s file to the beginning of every program. This software is executed first, before our main() program is started. We can see by observing the CRT11.s/CRT12.s file that the ICC11/ICC12 compilers will clear all statics to zero immediately after a hardware reset. See the section on initialization for more about initializing variables and constants.

A static global is very similar to a regular global. In both cases, the variable is defined in RAM permanently. The assembly language access is identical. The only difference is the scope. The static global can only be accessed within the file where it is defined. The following example also sets a global, called TheGlobal, to the value 1000. This global can not be referenced by modules in other files. In particular, notice the line .global _TheGlobal is missing in the 6811 code. Similarly, notice the double colon, ::, is replaced by a single colon, :, in the 6812 code. In both cases, this static global can not be referenced outside the scope of this file.

static short TheGlobal;   /* a static global variable*/
void main(void){
    TheGlobal=1000;    
}

Listing 4-3: Example showing a static global variable

The 6811 code generated by the ICC11 (Version 4) compiler is as follows

    .area text
    .global _main
_main:
    ldd #1000
    std _TheGlobal
    rts
    .area bss
_TheGlobal: .blkb 2

The 6812 code generated by the ICC12 (Version 5.1) compiler is as follows

    .area text
_main::
    movw #1000,_TheGlobal
    rts
    .area bss
_TheGlobal: .blkb 2

The 6812 code generated by the Metrowerks compiler is the same as a regular global. Metrowerks does properly limit the access only to the static global to functions defined in this file.

main:
    LDD #1000
    STD TheGlobal
    RTS

A static local is similar to the static global. Just as with the other statics, the variable is defined in RAM permanently. The assembly language code generated by the compiler that accesses the variable is identical. The only difference is the scope. The static local can only be accessed within the function where it is defined. The following example sets a static local, called TheLocal, to the value 1000. The compiler limits the access to the static local, so that this variable can not be accessed by other functions in this file or in other files. Notice that the assembly language name of the static local is a unique compiler-generated name (L2 in this example.) This naming method allows other functions to also define a static local or automatic local with the same name.

void main(void){
    static stort TheLocal;   /* a static local variable*/
    TheLocal=1000;    
}

Listing 4-4: Example showing a static local variable

The 6811 code generated by the ICC11 (Version 4) compiler is as follows

    .area text
    .global _main
_main:
    ldd #1000
    std L2
    rts
    .area bss
L2: .blkb 2

The 6812 code generated by the ICC12 (Version 5.1) compiler is as follows

    .area text
_main::
    movw #1000,L2
    rts
    .area bss
L2: .blkb 2

Again the 6812 code generated by the Metrowerks compiler is the same as a regular global. Metrowerks does properly limit the access only to the static local to the function in which it is defined.

main:
    LDD #1000
    STD TheLocal
    RTS

A static local can be used to save information from one instance of the function call to the next. Assume each function wished to know how many times it has been called. Remember upon reset, the ICC11/ICC12/Metrowerks compilers will initialize all statics to zero (including static locals). The following functions maintain such a count, and these counts can not be accessed by other functions. Even though the names are the same, the two static locals are in fact distinct.

void function1(void){
    static short TheCount;
    TheCount=TheCount+1;
}
void function2(void){
    static short TheCount;
    TheCount=TheCount+1;
}

Listing 4-5: Example showing two static local variables with the same name

The 6811 code generated by the ICC11 (Version 4) compiler is as follows

    .area text
    .global _function1
_function1:
    ldd L2
    addd #1
    std L2
    rts
    .global _function2
_function2:
    ldd L3
    addd #1
    std L3
    rts
    .area bss
L2: .blkb 2
L3: .blkb 2

The 6812 code generated by the ICC12 (Version 5.1) compiler is as follows

    .area text
_function1::
    ldd L2
    addd #1
    std L2
    rts
_function2::
    ldd L3
    addd #1
    std L3
    rts
    .area bss
L2: .blkb 2
L3: .blkb 2

Volatile

We add the volatile modifier to a variable that can change value outside the scope of the function. Usually the value of a global variable changes only as a result of explicit statements in the C function that is currently executing. The paradigm results when a single program executes from start to finish, and everything that happens is an explicit result of actions taken by the program. There are two situations that break this simple paradigm in which the value of a memory location might change outside the scope of a particular function currently executing:

1) interrupts and
2) input/output ports.

An interrupt is a hardware-requested software action. Consider the following multithreaded interrupt example. There is a foreground thread called main(), which we setup as the usual main program that all C programs have. Then, there is a background thread called TOFhandler(), which we setup to be executed on a periodic basis (e.g., every 16 ms). Both threads access the global variable, Time. The interrupt thread increments the global variable, and the foreground thread waits for time to reach 100. Notice that Time changes value outside the influence of the main() program.

volatile char Time;
void interrupt 16 TOFhandler(void){    /* every 16ms */
    TFLG2 = 0x80;    /* TOF interrupt acknowledge */
    Time = Time+1;
}
void main(void){
    TSCR1 |= 0x80; /* TEN(enable) */
    TSCR2 = 0x80;  /* TOI arm, timer/1 (250ns) */
    PACTL = 0x00;
    Time = 0;
    while(Time<100){}; /* wait for 100 counts of the 16 ms timer*/
}

Listing 4-6: Metrowerks 9S12C32 example showing shared access to a common global variable

Without the volatile modifier the compiler might look at the two statements:

Time = 0;
while(Time<100){}; 

and conclude that since the while loop does not modify Time, it could never reach 100. Some compilers (not yet in the current versions of ICC11 and ICC12) might attempt to move the read Time operation, performing it once before the while loop is executed. The volatile modifier disables the optimization, forcing the program to fetch a new value from the variable each time the variable is accessed.

In the next 6812 example, assume PORTA is an input port containing the current status of some important external signals. The program wishes to collect status versus time data of these external signals.

unsigned char data[100];
#define PORTA *(unsigned char volatile *)(0x0000)
#define DDRA *(unsigned char volatile *)(0x0002)
void main(void){ short i;
    DDRA = 0x00; /* make Port A an input */
    for(i=0;i<100;i++){ /* collect 100 measurements */
        data[i] = PORTA;  /* collect ith measurement */
    }
}

Listing 4-7: Example showed shared access to a common global variable

Without the volatile modifier in the PORTA definition, the compiler might optimize the for loop, reading PORTA once, then storing 100 identical copies into the data array. I/O ports will be handled in more detail in Chapter 7 on pointers.

Automatics

Automatic variables, on the other hand, do not have fixed memory locations. They are dynamically allocated when the block in which they are defined is entered, and they are discarded upon leaving that block. Specifically, they are allocated on the 6811/6812 stack by subtracting a value (one for characters, two for integers and four for long integers) from the stack pointer register (SP). Since automatic objects exist only within blocks, they can only be declared locally. Automatic variables can only be referenced (read or write) by the function that created it. In this way, the information is protected or local to the function.

When a local variable is created it has no dependable initial value. It must be set to an initial value by means of an assignment operation. C provides for automatic variables to be initialized in their declarations, like globals. It does this by generating "hidden" code that assigns values automatically after variables are allocated space.

It is tempting to forget that automatic variables go away when the block in which they are defined exits. This sometimes leads new C programmers to fall into the "dangling reference" trap in which a function returns a pointer to a local variable, as illustrated by

int *BadFunction(void) {
   int z;
   z = 1000;
   return (&z);
}

Listing 4-8: Example showing an illegal reference to a local variable

When callers use the returned address of z they will find themselves messing around with the stack space that z used to occupy. This type of error is NOT flagged as a syntax error, but rather will cause unexpected behavior during execution.

 

Implementation of automatic variables

If locals are dynamically allocated at unspecified memory (stack) locations, then how does the program find them? This is done by using the pointer register (X) to designate a stack frame for the currently active function. There is a difference between the ICC11 and ICC12 compilers. The ICC11 compiler generates code that will define a new value of X (executing the tsx instruction) whenever it wishes to access a local variable. Consequently we see many tsx instructions throughout the function. On the other hand, the ICC12 compiler generates code that attempts to define the stack frame pointer x only once at the beginning of the function. Consequently we usually see only one tfr s,x instruction in each the function. The 6812 tfr s,x instruction is just an alternative specification of the instruction tsx (i.e., they produce the same machine code and perform the same function when executed). When the ICC12 function is entered, the prior value of Register X is pushed onto the stack and then the new value of SP is moved to X. The ICC11 function does not save the prior value of Register X. This address--the new value of SP--then becomes the base for references to local variables that are declared within the function. The 6812 has a much richer set of machine instructions and addressing modes to simplify this process. The 6811 SP register points to a free memory space to be used to place the next byte to be pushed. On the other hand the 6812 SP register points to the top data byte that has already been pushed.

In order to understand both the machine architecture and the C compiler, we can look at the assembly code generated. For both the ICC11 and ICC12 compilers, the linker/loader allocates 3 segmented memory areas: code pointed to by the PC (text area); global accessed with absolute addressing (data area); and locals pointed to by the stack pointer SP. This example shows a simple C program with three local variables. Although the function doesn't do much it will serve to illustrate how local variables are created (allocation), accessed (read and write) and destroyed (deallocated.)

void sub(void){ short y1,y2,y3;   /* 3 local variables*/
    y1 = 1000;
    y2 = 2000;
    y3 = y1+y2;
}

Listing 4-9: Example showing three local variables

The first compiler we will study is the ImageCraft ICC11 version 4.0 for the Freescale 6811. The disassembled output has been edited to clarify its operation (although the compiler does create the "; y3 -> 0,x" comment). The pshx instruction allocates the local variable, and the tsx instruction establishes a stack frame pointer, X.

    .area text   ; _sub in ROM
    .globl _main
; y3 -> 0,x
; y2 -> 2,x
; y1 -> 4,x
_sub: pshx  ; allocate y1
    pshx    ; allocate y2
    pshx    ; allocate y3
    tsx
    ldd  #1000  
    std  4,x   ; y1=1000
    ldd  #2000
    std  2,x   ; y2=2000
    ldd  4,x
    addd 2,x
    std  0,x  ; y3=y1+y2
    pulx      ; deallocate y3
    pulx      ; deallocate y2
    pulx      ; deallocate y1
    rts

The stack frame at the time of the addd instruction is shown. Within the subroutine the local variables of other functions are not accessible.

Figure 4-1. 6811 stack frame showing three local variables.

The next compiler we will study is the ImageCraft ICC12 version 5.0 for the Freescale 6812. Again, the disassembled output has been edited to clarify its operation (although the compiler does create the "; y3 -> -6,x" comment). Like the 6811, the linker/loader also allocates 3 segmented memory areas: code pointed to by the PC; global accessed with absolute addressing; and locals pointed to by the stack pointer SP. The leas -6,sp instruction allocates the local variables, and the tfr s,x instruction establishes a stack frame pointer, X. Within the subroutine the local variables of other functions are not accessible.

 

Figure 4-2. 6812 implementation of three local variables.

 

A constant local is similar to the regular local. Just as with the other locals, the constant is defined temporarily on the stack. The difference is that the constant local can not be changed. The assembly language code generated by the compiler that accesses the constant local is identical to the regular local.

short TheGlobal;   /* a regular global variable*/
void main(void){
    const short TheConstant=1000;   /* a constant local*/
    TheGlobal=TheConstant;
}

Listing 4-10: Example showing a constant local

The 6811 code generated by the ICC11 (Version 4) compiler is as follows

    .area text   ; _main in ROM
    .global _main
; TheConstant -> 0,x
_main: pshx  ; allocate TheConstant
    tsx
    ldd  #1000
    std  0,x         ; TheConstant=1000
    std  _TheGlobal  ; TheGlobal=TheConstant
    pulx             ; deallocate TheConstant
    rts
    .area bss
    .global _TheGlobal
_TheGlobal: .blkb 2

The 6812 code generated by the ICC12 (Version 5.1) compiler is as follows

    .area text
_main::
    pshx
    tfr s,x
    leas -2,sp
    movw #1000,-2,x
    movw -2,x,_TheGlobal
    tfr x,s
    pulx
    rts
    .area bss
_TheGlobal:: .blkb 2

 

Externals

Objects that are defined outside of the present source module have the external storage class. This means that, although the compiler knows what they are (signed/unsigned, 8-bit 16-bit 32-bit etc.), it has no idea where they are. It simply refers to them by name without reserving space for them. Then when the linker brings together the object modules, it resolves these "pending" references by finding the external objects and inserting their addresses into the instructions that refer to them. The compiler knows an external variable by the keyword extern that must precede its declaration.

Only global declarations can be designated extern and only globals in other modules can be referenced as external.

The following example sets an external global, called ExtGlobal, to the value 1000. This global can be referenced by any function from any file in the software system. It is truly global.

extern short ExtGlobal;   /* an external global variable*/
void main(void){
    ExtGlobal=1000;    
}

Listing 4-11: Example showing an external global

Notice the assembly language the ICC11 generates does not include the definition of ExtGlobal. The 6811 code generated by the ICC11 (Version 4) compiler is as follows

    .area text
    .global _main
_main:
    ldd #1000
    std _ExtGlobal
    rts

Similarly the assembly language the ICC12 generates also does not include the definition of ExtGlobal. The 6812 code generated by the ICC12 (Version 5.1) compiler is as follows

    .area text
_main::
    movw #1000,_ExtGlobal
    rts

 

Scope

The scope of a variable is the portion of the program from which it can be referenced. We might say that a variable's scope is the part of the program that "knows" or "sees" the variable. As we shall see, different rules determine the scopes of global and local objects.

When a variable is declared globally (outside of a function) its scope is the part of the source file that follows the declaration--any function following the declaration can refer to it. Functions that precede the declaration cannot refer to it. Most C compilers would issue an error message in that case.

The scope of local variables is the block in which they are declared. Local declarations must be grouped together before the first executable statement in the block--at the head of the block. This is different from C++ that allows local variables to be declared anywhere in the function. It follows that the scope of a local variable effectively includes all of the block in which it is declared. Since blocks can be nested, it also follows that local variables are seen in all blocks that are contained in the one that declares the variables.

If we declare a local variable with the same name as a global object or another local in a superior block, the new variable temporarily supersedes the higher level declarations. Consider the following program.

unsigned char x;   /* a regular global variable*/
void sub(void){
    x=1;
    {   unsigned char x;   /* a local variable*/
        x=2;
        {   unsigned char x;  /* a local variable*/
            x=3;
            PORTA=x;}
        PORTA=x;}
    PORTA=x;}
}

Listing 4-12: An example showing the scope of local variables

This program declares variables with the name x, assigns values to them, and outputs them to PORTA with in such a way that, when we consider its output, the scope of its declarations becomes clear. When this program runs, it outputs 321. This only makes sense if the x declared in the inner most block masks the higher level declarations so that it receives the value '3' without destroying the higher level variables. Likewise the second x is assigned '2' which it retains throughout the execution of the inner most block. Finally, the global x, which is assigned '1', is not affected by the execution of the two inner blocks. Notice, too, that the placement of the last two PORTA=x; statements demonstrates that leaving a block effectively unmasks objects that were hidden by declarations in the block. The second PORTA=x; sees the middle x and the last PORTA=x; sees the global x.

This masking of higher level declarations is an advantage, since it allows the programmer to declare local variables for temporary use without regard for other uses of the same names.

One of the mistakes a C++ programmer makes when writing C code is trying to define local variables in the middle of a block. In C local variables must be defined at the beginning of a block. The following example is proper C++ code, but results in a syntax error in C.

void sub(void){ int x;  /* a valid local variable declaration */
    x=1;
    int y;   /* This declaration is improper */
    y=2;
}

Listing 4-13: Example showing an illegal local variable declaration

 

Declarations

Unlike BASIC and FORTRAN, which will automatically declare variables when they are first used, every variable in C must be declared first. This may seem unnecessary, but when we consider how much time is spent debugging BASIC and FORTRAN programs simply because misspelled variable names are not caught for us, it becomes obvious that the time spent declaring variables beforehand is time well spent. Declarations also force us to consider the precision (8-bit, 16-bit etc.) and format (unsigned vs. signed) of each variable.

As we saw in Chapter 1, describing a variable involves two actions. The first action is declaring its type and the second action is defining it in memory (reserving a place for it). Although both of these may be involved, we refer to the C construct that accomplishes them as a declaration. As we saw above, if the declaration is preceded by extern it only declares the type of the variables, without reserving space for them. In such cases, the definition must exist in another source file. Failure to do so, will result in an unresolved reference error at link time.

Table 4-1 contains examples of legitimate variable declarations. Notice that the declarations are introduced by one or type keywords that states the data type of the variables listed. The keyword char declares 8-bit values, int declares 16-bit values, short declares 16-bit values and long declares 32-bit values. Unless the modifier unsigned is present, the variables declared by these statements are assumed by the compiler to contain signed values. You could add the keyword signed before the data type to clarify its type.

When more than one variable is being declared, they are written as a list with the individual names separated by commas. Each declaration is terminated with a semicolon as are all simple C statements.

Declaration Comment Range
unsigned char uc; 8-bit unsigned number 0 to +255
char c1,c2,c3; three 8-bit signed numbers -128 to +127
unsigned int ui; 16-bit unsigned number 0 to +65535
int i1,i2; two 16-bit signed numbers -32768 to +32767
unsigned short us; 16-bit unsigned number 0 to +65535
short s1,s2; two 16-bit signed numbers -32768 to +32767
long l1,l2,l3,l4; four signed 32 bit integers -2147483648L to 2147483647L
float f1,f2; two 32-bit floating numbers not recommended
double d1,d2; two 64-bit floating numbers not recommended

Table 4-1: Variable Declarations
 

ICC11 version 4 does not support long integers, and ICC12 does not support unsigned long integers. ICC11 and ICC12 compilers allow the register modifier for automatic variables, but the compilers still define the register locals on the stack. The keywords char int short long specifies the precision of the variable. The following tables shows the available modifiers for variables.

Modifier Comment
auto automatic, allocated on the stack
extern defined in some other program file
static permanently allocated
register attempt to implement an automatic using a register instead of on the stack

Table 4-2: Variable storage classes
 

Modifier Comment
volatile can change value by means other than the current program
const fixed value, defined in the source code and can not be changed during execution
unsigned range starts with 0 includes only positive values
signed range includes both negative and positive values

Table 4-3 Variable modifiers

In all cases const means the variable has a fixed value and can not be changed. When modifying a global on an embedded system like the 6811 or 6812, it also means the parameter will be allocated in ROM. In the following example, Ret is allocated in ROM. When const is added to a parameter or a local variable, it means that parameter can not be modified by the function. It does not change where the parameter is allocated. For example, this example is legal.
unsigned char const Ret=13;
void LegalFuntion(short in){
  while(in){
    SCI_OutChar(Ret);
    in--;
  }
}
On the other hand, this example is not legal because the function attempts to modify the input parameter. in in this example would have been allocated on the stack or in a register.
void NotLegalFuntion(const short in){
  while(in){
    SCI_OutChar(13);
    in--;  // this operation is illegal
  }
}
Similarly, this example is not legal because the function attempts to modify the local variable. count in this example would have been allocated on the stack or in a register.
void NotLegalFuntion2(void){ const short count=5;
  while(count){
    SCI_OutChar(13);
    count--;  // this operation is illegal
  }
}
 

As we shall see, a similar syntax is used to declare pointers, arrays, and functions (Chapters 7, 8, and 10).

Character Variables

Character variables are stored as 8-bit quantities. When they are fetched from memory, they are always promoted automatically to 16-bit integers. Unsigned 8-bit values are promoted by adding 8 zeros into the most significant bits. Signed values are promoted by coping the sign bit (bit7) into the 8 most significant bits.

There is a confusion when signed and unsigned variables are mixed into the same expression. It is good programming practice to avoid such confusions. As with integers, when a signed character enters into an operation with an unsigned quantity, the character is interpreted as though it was unsigned. The result of such operations is also unsigned. When a signed character joins with another signed quantity, the result is also signed.

char x;   /* signed 8 bit global */
unsigned short y;   /* unsigned signed 16 bit global */
void sub(void){
    y=y+x;
/* x treated as unsigned even though defined as signed */
}

Listing 4-13: An example showing the mixture of signed and unsigned variables

There is also a need to change the size of characters when they are stored, since they are represented in the CPU as 16-bit values. In this case, however, it matters not whether they are signed or unsigned. Obviously there is only one reasonable way to put a 16-bit quantity into an 8-bit location. When the high-order byte is chopped off, an error might occur. It is the programmer's responsibility to ensure that significant bits are not lost when characters are stored.

When Do We Use Automatics Versus Statics?

Because their contents are allowed to change, all variables must be allocated in RAM and not ROM. An automatic variable contains temporary information used only by one software module. As we saw, automatic variables are typically allocated, used, then deallocated from the stack. Since an interrupt will save registers and create its own stack frame, the use of automatic variables is important for creating reentrant software. Automatic variables provide protection limiting the scope of access in such a way that only the program that created the local variable can access it. The information stored in an automatic variable is not permanent. This means if we store a value into an automatic variable during one execution of the module, the next time that module is executed the previous value is not available. Typically we use automatics for loop counters, temporary sums. We use an automatic variable to store data that is temporary in nature. In summary, reasons why we place automatic variables on the stack include

• dynamic allocation release allows for reuse of memory
• limited scope of access provides for data protection
• can be made reentrant.
• limited scope of access provides for data protection
• since absolute addressing is not used, the code is relocatable
• the number of variables is only limited by the size of the stack allocation.

A static variable is information shared by more than one program module. E.g., we use globals to pass data between the main (or foreground) process and an interrupt (or background) process. Static variables are not deallocated. The information they store is permanent. We can use static variables for the time of day, date, user name, temperature, pointers to shared data. The ICC11/ICC12 compilers use absolute addressing (direct or extended) to access the static variables.

Initialization of variables and constants

Most programming languages provide ways of specifying initial values; that is, the values that variables have when program execution begins. We saw earlier that the ICC11/ICC12/Metrowerks compilers will initially set all static variables to zero. Constants must be initialized at the time they are declared, and we have the option of initializing the variables.

Specifying initial values is simple. In its declaration, we follow a variable's name with an equal sign and a constant expression for the desired value. Thus

short Temperature = -55;

declares Temperature to be a 16-bit signed integer, and gives it an initial value of -55. Character constants with backslash-escape sequences are permitted. Thus

char Letter = '\t';

declares Letter to be a character, and gives it the value of the tab character. If array elements are being initialized, a list of constant expressions, separated by commas and enclosed in braces, is written. For example,

const unsigned short Steps[4] = {10, 9, 6, 5};

declares Steps to be an unsigned 16-bit constant integer array, and gives its elements the values 10, 9, 6, and 5 respectively. If the size of the array is not specified, it is determined by the number of initializers. Thus

char Waveform[] = {28,27,60,30,40,50,60};

declares Waveform to be a signed 8-bit array of 7 elements which are initialized to the 28,27,60,30,40,50,60. On the other hand, if the size of the array is given and if it exceeds the number of initializers, the leading elements are initialized and the trailing elements default to zero. Therefore,

char Waveform[100] = {28,27,60,30,40,50,60};

declares Waveform to be an integer array of 100 elements, the first 7 elements of which are initialized to the 28,27,60,30,40,50,60 and the others to zero. Finally, if the size of an array is given and there are too many initializers, the compiler generates an error message. In that case, the programmer must be confused.

Character arrays and character pointers may be initialized with a character string. In these cases, a terminating zero is automatically generated. For example,

char Name[4] = "Jon";

declares Name to be a character array of four elements with the first three initialized to 'J', 'o', and 'n' respectively. The fourth element contains zero. If the size of the array is not given, it will be set to the size of the string plus one. Thus ca in

char Name[] = "Jon";

also contains the same four elements. If the size is given and the string is shorter, trailing elements default to zero. For example, the array declared by

char Name[6] = "Jon";

contains zeroes in its last three elements. If the string is longer than the specified size of the array, the array size is increased to match. If we write

char *NamePt = "Jon";

the effect is quite different from initializing an array. First a word (16 bits) is set aside for the pointer itself. This pointer is then given the address of the string. Then, beginning with that byte, the string and its zero terminator are assembled. The result is that NamePt contains the address of the string "Jon". The Imagecraft and Metrowerks compilers accept initializers for character variables, pointers, and arrays, and for integer variables and arrays. The initializers themselves may be either constant expressions, lists of constant expressions, or strings.

Implementation of the initialization

The compiler initializes static constants simply by defining its value in ROM. In the following example, J is a static constant (actually K is a literal)

short I;            /* 16 bit global */
const short J=96;   /* 16 bit constant */
#define K 97;
void main(void){
    I=J; 
    I=K;}

Listing 4-14: An example showing the initialization of a static constant

 

The 6811 code generated by the ICC11 Version 4 compiler is as follows

    .area text
    .global _main
_main:
    ldd _J
    std _I    ;16 bits
    ldd #97
    std _I    ;16 bits
    rts
    .area bss
    .global _I
_I: .blkb 2
    .area text
    .global _J
_J: .word 96

The 6812 code generated by the ICC12 Version 5.1 compiler is as follows

    .area text
_main::
    pshx
    tfr s,x
    movw _J,_I   ;16 bits
    movw #97,_I  ;16 bits
    tfr x,s
    pulx
    rts
    .area bss
_I:: .blkb 2
    .area text
_J:: .word 96

Notice the use of the #define macro implements an operation similar to the literal I=97;

The compiler initializes static variables simply by defining its initial value in ROM. It creates another segment called idata (in addition to the data and text sections). It places the initial values in the idata segment, then copies the data dynamically from idata ROM information into data RAM variables at the start of the program (before main is started). (How ICC11 and ICC12 have handled initialized variables has changed over the various release versions. The particular compiler version you are using may handle this process differently.) For example

short I=95;         /* 16 bit global */
void main(void){
    
    }

The ICC11 Version 4 code would not function properly on an embedded system because the global is defined as

    .area bss
_I:: .word 95

The ICC11 Version 4 code would work on a RAM based system like the Freescale 6811 EVB where the power is not removed between the time the S19 record is loaded into RAM and the time the software is executed. Proper 6812 code is generated by the ICC12 compiler. In the 6812 solution, code in the CRT12.S file will copy the 95 from .idata (ROM) into _I in bss (RAM) upon a hardware reset. This copy is performed transparently before the main program is started.

    .area text
_main::
    pshx
    tfr s,x

    pulx
    rts
    .area bss
_I:: .blkb 2
    .area idata
    .word 95

 

Even though the following two applications of global variable are technically proper, the explicit initialization of global variables in my opinion is a better style.

/* poor style */      /* good style */
int I=95;             int I;
void main(void){      void main(void){
                          I=95;
}                     }

Opinion: I firmly believe a good understanding of the assembly code generated by our compiler makes us better programmers.

Go to Chapter 5 on Expressions Return to Table of Contents