What's in Chapter 4?
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.
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
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:
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.
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.
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
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
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
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
}
}
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 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.
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
• 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.
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.
Thuschar 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. Thuschar 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 inchar 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.
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.
No comments:
Post a Comment