3 LANGUAGE IMPLEMENTATION

This chapter contains the following sections:

Introduction
Accessing Memory
Storage Types
Memory Models
Mixed Memory Model Programming
_MODEL and _ROMMODEL
The _at() Attribute
The _atbit() Attribute
Data Types
Signed Characters
ANSI C Type Conversions
Character Arithmetic
The _bit Type
The _bitbyte Type
Special Function Registers
Function Parameters
Function Overlay
Automatic Variables
Register Variables
Initialized Variables
Type Qualifier volatile
Strings
Pointers
Function Pointers
Inline C Functions
Inline Assembly
Built-in Functions
Interrupt and Using
Register Bank Independent Code Generation
MISRA C
Structure Tags
Typedef
Switch Statement
Portable C Code
How to Program Smart in C-51
Some Examples of Complex Declarators

3.1 Introduction

The TASKING 8051 C cross-compiler offers a new approach to high-level language programming for the 8051 family. It conforms to the ANSI standard, but allows the user to control the I/O registers, bit memory, interrupts (register bank switch) and multiple address spaces of the 8051 in C.

This chapter describes the language implementation in relation to the 8051 architecture.

The extensions to the C language in cc51 are:

_bit

You can use data type bit or _bit for the type definition of scalars and for the return type of functions.

_bitbyte

You can declare byte variables in the bit-addressable area as _bitbyte. You can access additional bits using the built-in functions _getbit() and _putbit().

_sfrbit

Data type for the declaration of specific, absolute bits in special function registers or special absolute bits in the SFR address space.

_sfrbyte

Data type for the declaration of Special Function Registers.

_at

You can specify a variable to be at an absolute address.

_atbit

You can specify a variable to be at a bit offset within a bitaddressable variable.

_plmprocedure

Declaration of external PL/M-51 procedures.

_inline

Used for defining inline functions.

_noregaddr

You can specify a function to be independent of register banks.

storage types

Apart from a memory category (extern, static, ...) you can specify a storage type in each declaration. This way you obtain a memory model-independent addressing of variables in several address ranges of the 8051 (_data, _bdat, _idat, _pdat, _xdat, _rom).

memory-specific pointers

cc51 allows you to define pointers which point to a specific target memory. These types of pointers are very efficient and require only 1 or 2 bytes memory space.

mixed memory models

cc51 allows you to combine memory models by using a default memory model and assigning specific memory models to functions. For example, a program created in the large memory model can be accelerated, in which functions are partially distributed to the small model. The keywords you can use to specify a model for a function are: _small, _aux, _large and _reentrant.

reentrant functions

You can selectively define functions as reentrant (_reentrant keyword). Reentrant functions can be invoked recursively. Interrupt programs can also call reentrant functions.

register bank

Each function may contain a specification regarding the register bank to be used (_using keyword).

interrupt functions

You can specify interrupt functions directly through interrupt vectors in the C language (_interrupt keyword). You can also specify the register bank to be used.

3.2 Accessing Memory

cc51 offers two ways of dealing with the separate address spaces of the 8051, which can be combined. You can:

- specify a storage type (and perhaps also a target memory of a pointer) with the declaration of a C variable

and you are able to:

- select a memory model (for a program or per function), specifying which memory space must be used (as default) for all C variables which do not have an explicit storage specifier. This is very useful for compiling existing C source, which does not need to be adapted for the 8051.

In practice the majority of the C code of a complete application is standard C (without using any language extension). You can compile this part of the application without any modification, using the memory model which fits best to the requirements of the system (code density, amount of external RAM etc.).

Only a small part of the application uses language extensions. These parts often deal with items such as:

- I/O, using the special function registers

- high execution speed needed

- high code density needed

- access to non-default memory required (ROM, internal RAM)

- bit type needed

- C interrupt functions

3.2.1 Storage Types

cc51 supports the architecture of the 8051 microprocessors and all 8051 derivatives completely. It has full access to all hardware components of the 8051. An object other than a function or an automatic (stack) variable cannot be referred to solely by its starting address, because this might be valid for several address spaces. You can explicitly assign each variable to one of the address spaces (data, bdat, idat, pdat, xdat, rom) by using a type specifier. This specifier determines the 'storage type' of static objects.

Accessing the internal data memory (_data, _idat) is considerably faster than accessing the external data memory (_xdat). Therefore, it is useful to place often used variables into internal data memory, and to place larger and less often referenced data elements into the external data memory.

cc51 recognizes the following storage type specifiers:

Storage Type Description
_data / data direct addressable on chip RAM
_bdat bitaddressable on chip RAM
_idat / idat indirect addressable on chip RAM
_pdat / pdat external RAM within 256 bytes page
_xdat / xdat external RAM
_rom / rom internal/external ROM

Table 3-1: Storage type specifiers

cc51 treats the storage specifier _rom type in a special way: it always implies the type qualifier const.

Const Qualifier

The ANSI standard states that the type qualifier const can be used to specify 'read-only' objects (or: are not 'lvalues'). An ANSI C compiler may allocate static const objects in ROM memory. However, since ROM is a different memory space (which needs special instructions to access), this is not possible for a 8051 C compiler. On the other hand, cc51 treats _rom variables as if declared with a const qualifier. So, cc51 treats const just as a type qualifier, which allows the compiler to check on illegal lvalue use. This is exactly the way it is meant to be used in the ANSI definition.

Example:

So const is no storage type specifier, but a type qualifier (like unsigned and volatile).

Examples using explicit storage types:

allocating:

1 byte in direct addressable on chip RAM for c

11 bytes in ROM for the initialized character array text[]

80 bytes in external RAM for array

4 bytes in indirectly addressable on chip RAM for l

2 bytes in one page of external RAM for i

The memory type specifiers are treated like any other type specifier (e.g. unsigned). This means the examples above can also be declared (exactly the same):

An object must be fully contained in a single storage section. See section 3.19, Structure Tags, for details.

3.2.2 Memory Models

cc51 has four memory models: small, auxpage, large and reentrant. You can select one of these models with the -M option. Each model uses a different default storage type for (non-register) automatic variables, (non-register) parameter passing areas and declarations without an explicit storage type. Parameter passing in the SMALL model is performed in internal data memory. The AUXPAGE and LARGE model permit parameter passing in external memory. The REENTRANT model permits parameter passing via a virtual software stack in external memory.

cc51 also supports mixed memory models; for example, a program created in the large memory model can be accelerated, in which functions are partially distributed to the small model.

The following table gives an overview of the different memory models.

Memory Model Non-register Parameters / automatics Other C variables Max RAM size Default storage type
small direct addressable internal RAM direct addressable internal RAM 128 data
auxpage one page of external RAM one page of external RAM 256 pdat
large external RAM external RAM 64k xdat
reentrant virtual stack in external RAM external RAM 64k xdat

Table 3-2: Memory models

Parameters can also be passed in registers, see section 3.4, Function Parameters.

Automatics and parameters may be placed in registers. See section 3.7, Register Variables. Automatics in the auxpage and large memory models may be placed in internal RAM also. See section 3.6, Automatic Variables in this chapter.

Function return addresses are on the real stack. In the small memory model all objects as well as the stack, must fit in the internal RAM. The stack length is critical since its real length depends upon the nesting depth of the various functions.

The auxpage model is especially interesting for derivatives supporting 256 bytes of 'external' RAM on chip. With these derivatives, P2 can be used to specify the external RAM page to be used for paged data (_pdat). With other 8051 derivatives, P2 must be set to 0, and cannot be used for other purposes in this model.

Each of the memory models has advantages and disadvantages, especially concerning the access efficiency and the length of the address space. Therefore, cc51 allows you to mix models.

3.2.2.1 Mixed Memory Model Programming

It is possible to specify a memory model on one function. You can use one of the following keywords:

When you use reentrant functions, a virtual stack is needed. You also need to change your start-up code. A reentrant function can be called recursively; in addition, calls are allowed at any time, even from interrupt functions.

Example

Suppose the default memory model is large (option -Ml). In this case all functions are defined _large by default. You may, however, overrule this default behavior by specifying one of the other memory function qualifiers. In this example the _small function qualifier is used to obtain fast (direct) data access. All function parameters and local data are stored in internal RAM.

3.2.2.2 _MODEL and _ROMMODEL

cc51 introduces the predefined preprocessor symbols _MODEL and _ROMMODEL. The value of _MODEL represents the memory model selected (-M option). The value of _ROMMODEL represents the rom model selected (-r option). These can be very helpful in making conditional C code in one source module, used for different applications in different memory models. See also section 3.22 , Portable C Code, explaining the include file cc51.h.

The value of _MODEL is:

The value of _ROMMODEL is:

Example:

#if _MODEL == 'a' || _MODEL == 'l' /* non-small
                                    * static model
                                    */
...

#endif

#if _ROMMODEL == 'l'               /* large rom model */
...

#endif

3.2.3 The _at() Attribute

In C-51 it is possible to place certain variables at absolute addresses. Instead of writing a piece of assembly code, a variable can be placed on an absolute address using the _at() attribute.

Example:

The example above creates a variable with the name Display at address 0x2000 in external RAM. In the generated assembly code an absolute section will appear like 'XSEG AT 2000H', on this position space is reserved for the variable Display.

A number of restrictions are in effect when placing variables on an absolute address:

3.2.4 The _atbit() Attribute

In C-51 it is possible to define bit variables within a _bitbyte or (bitaddressable) _sfrbyte variable. This can be done with the _atbit() attribute. The syntax is:

where, bytename is the name of a _bitbyte or _sfrbyte variable and offset is the bit-offset with the variable.

Examples:

The first example defines an sfrbit within an sfrbyte. The second example defines a bitaddress within a bitaddressable byte. For more information on SFR variables see section 3.3.6, Special Function Registers. For more information on _bitbyte variables see section 3.3.5, The _bitbyte Type.

3.3 Data Types

All ANSI C data types are supported, except double and long double, which both are evaluated as floats. In addition to these types, the _sfrbit, _sfrbyte, _bit and _bitbyte types are added. Two types of pointers are recognized. Object size and ranges:

Data Type Size
(in bytes)
Range
_bit / bit 1 bit 0 or 1
_sfrbit 1 bit 0 or 1
signed char 1 -128 to +127
unsigned char 1 0 to 255
_sfrbyte 1 0 to 255
_bitbyte 1 0 to 255 (byte in bitaddressable RAM)
signed short 2 -32768 to +32767
unsigned short 2 0 to 65535
signed int 2 -32768 to +32767
unsigned int 2 0 to 65535
enum 2 0 to 65535
signed long 4 -2147483648 to +2147483647
unsigned long 4 0 to 4294967295
float 4 +/- 1.176E-38 to +/- 3.402E+38
1-byte pointer
(pointer to data,
idat or pdat)
1 0 to 255
2-byte pointer
(pointer to xdat or rom)
2 0 to 65535

Table 3-3: Data types

- _bit, _sfrbit, char, _sfrbyte, _bitbyte, short, int and long are all integral types, supporting all implicit (automatic) conversions.

- cc51 generates instructions using (8 bit) character arithmetic, when it is correct to evaluate a character expression this way. This results in a higher code density compared with integer arithmetic. A special section Character Arithmetic provides details.

- the 8051 convention is used, storing variables with the most significant part at the lower memory address (Big Endian).

- float is implemented in little endian IEEE 32-bit single precision format.

- with the -se option small enumeration types can be treated as char instead of int.

3.3.1 Signed Characters

The character type is treated as signed char by default. You can overrule this default with the -u command line option, which sets the default to unsigned char.

Examples:

3.3.2 ANSI C Type Conversions

According to the ANSI C X3.159-1989 standard, a character, a short integer, an integer bit field (either signed or unsigned), or an object of enumeration type, can be used in an expression wherever an integer can be used. If a signed int can represent all the values of the original type, then the value is converted to signed int; otherwise the value will be converted to unsigned int. This process is called integral promotion.

Integral promotion is also performed on function pointers and function parameters of integral types using the old-style declaration. To avoid problems with implicit type conversions, you are advised to use function prototypes.

Many operators cause conversions and yield result types in a similar way. The effect is to bring operands into a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions.

See also section 3.3.3 , Character Arithmetic.

Sometimes surprising results may occur, for example when unsigned char is promoted to int. You can always use explicit casting to obtain the type required. The following example makes this clear:

To overcome this 'unwanted' behavior use an explicit cast:

Keep in mind that the arithmetic conversions apply to multiplications also:

3.3.3 Character Arithmetic

cc51 generates code using 8 bit (character) arithmetic as long as the result of the expression is exactly the same as if it was evaluated in integer arithmetic. This must be done, because ANSI does not know character arithmetic and character constants. Because the 8051 is an 8 bit microcontroller, cc51 tries to use the 8 bit instructions for character arithmetic as much as possible. If not possible, 16 bit arithmetic is used. So it is recommended to use character variables in expressions, because it saves data space for allocation, and often results in a higher code density. You can always force the compiler to use character arithmetic with a character cast.

The following examples clarify when integer arithmetic is used and when character arithmetic:

char  a,b,c,d;
int         i;

main()
{
   c = a + b;                 /* character arithmetic */
   i = a + b;                 /* integer arithmetic   */
   i = (char)(a + b);         /* character arithmetic */

   c = a / d;                 /* character arithmetic */
   c = (a + b) / d;           /* integer arithmetic   */
   c = ((char)(a + b)) / d;   /* character arithmetic */

   c = a >> d;                /* character arithmetic */
   c = (a + b) >> d;          /* integer arithmetic   */
   if ( a > b )         /* character arithmetic */
      c = d;
   if ( (a + b) > c )   /* integer arithmetic   */
      c = d;
}

Signed constants between -128 and 127 and unsigned constants between 0 and 255 are treated as a character constant. This means that in:

c is compared using integer arithmetic because 240 is an integer constant. However, d is compared using character arithmetic because both 5 and 240u are character constants.

The following rule applies to hexadecimal and octal constants:
If such a constant fits in an unsigned character it is treated as a character constant. Thus 0xf0 is an unsigned character constant with value 240u.

This rule is derived from a similar approach of the ANSI standard for integer constants, where 0xf000 is treated as an unsigned integer constant.

3.3.4 The _bit Type

The following rules apply to _bit type variables:

1. A _bit type variable is always unsigned.

2. A _bit type variable can be exchanged with all other integral type variables. The compiler generates the correct conversion.

A _bit type variable is like a boolean. Therefore, converting an int type variable to a _bit type variable does not mean the _bit type variable is the least significant bit of the int type variable. It is 1 (true) if the int type variable is not equal to 0, and 0 (false) if the int type variable is 0. In C:

can be seen as:

3. Pointer to _bit and array of _bit are not allowed, because the 8051 has no instructions to indirectly access bitaddressable memory.

4. Structure of _bit is supported, with the restriction that no other type than _bit is member of this structure. Structure of _bit is not allowed on parameter or return value of a function.

5. A union of a _bit structure and another type is not allowed. The _bitbyte type can be used for this purpose.

6. A _bit type variable is not allowed as a function parameter of a reentrant function.

7. A _bit type variable is not allowed as an automatic variable of a reentrant function. However, a local static _bit variable (within a function) is always allowed.

8. A function may have return type _bit. However, the next rule may not be violated.

9. Evaluation of a complex _bit expression (using non _bit types or _bit return type of a function) is not recursive nor reentrant, because the compiler might need temporary static bit space.

10. A _bit typed expression is not allowed as switch expression.

11. The sizeof of a _bit type is 1.

Normal C bit fields within a structure are not treated like _bit variables, and are therefore not allocated in BIT memory, but in one of the other memory spaces as specified during declaration. Using a structure of _bit type variables results in much better code density, less storage allocation and higher execution speed compared to a structure with a number of 1-bit bit field declarations.

For example:

3.3.5 The _bitbyte Type

You can declare byte variables in the bit-addressable area as _bitbyte. You can access individual bits using the built-in functions _getbit() and _putbit() or declare the individual bits of this _bitbyte variable using _atbit. A prototype for these functions is given in the include file cc51.h.

For example:

See also section 3.2.4 , The _atbit() Attribute.

The _bitbyte type is subject to the following rules.

1. A _bitbyte type variable is always unsigned.

2. A _bitbyte type variable can be exchanged with all other integral type variables. The compiler generates the correct conversion.

3. Pointer to a _bitbyte variable and array of _bitbyte is allowed.

4. Structure of _bitbyte is supported, with the restriction that no other type than _bitbyte is member of this structure. Structure of _bitbyte is not allowed on parameter or return value of a function.

5. A _bitbyte type variable is not allowed as a function parameter of a reentrant function.

6. A _bitbyte type variable is not allowed as an automatic variable of a reentrant function. However, a local static _bitbyte variable (within a function) is always allowed.

7. A function can not have return type _bitbyte.

8. The sizeof of a _bitbyte type is 1.

9. A _bitbyte typed expression is allowed as switch expression.

3.3.6 Special Function Registers

As shown in the section Sample Session, cc51 allows direct access to all special function registers (both bits and bytes), as if they were C variables. These special function registers can be used the same way as any other integral data type, including all automatic conversions.

An _sfrbit is handled the same way as a volatile _bit variable. An _sfrbyte is handled as a volatile unsigned char variable.

In order to 'include' a special function register definition file, you must use the -C option. You are able to specify which cpu must be used.

For example, the command:

causes the compiler to look for a file named reg552.sfr, and use this file as a special function register definition file. We deliver a number of these definition files with cc51, but you can easily make your own one for the 8051 derivative you are using. cc51 uses the same searching method for these register definition files as with include files. For details, see section 4.3, Include Files in chapter Compiler Use.

You can also declare sfr-registers within your C-source by using the data types _sfrbit or _sfrbyte. The notation is as follows:

where, name must be replaced with the name of the sfr-register you want to specify. bitaddress/byteaddress is the bit or byte address of the sfr-register. offset is the bit-offset in an sfrbyte.

Because these registers are placed in the sfr-area of the processor, the compiler will not allocate any storage space.

The words 'sfrbyte' and 'sfrbit' are not reserved words for cc51. So, these words can be used as identifiers. cc51 does not generate symbolic debugging information for special function registers, because they are already known by the debugger.

Appendix C shows the contents of one of the register definition files delivered with this package (reg51.sfr).

Because the special function registers are dealing with I/O, it is not correct to optimize away the access to them. Therefore, cc51 deals with the special function registers as if they were declared with the volatile qualifier.

For example:

int               i;
volatile int      v;

main()
{
      i;    /* optimized away                               */
      SBUF; /* access SBUF register (implicit volatile)     */
      v;    /* volatile: access variable                    */
}

3.4 Function Parameters

cc51 supports (ANSI) prototyping of function parameters. Therefore, cc51 allows passing parameters of type char, and (in static models only) of type _bit, without converting these parameters to int type. This results into higher code density, higher execution speed and less RAM data space needed for parameter passing. This is very important in single chip applications.

For example, in the following C code:

the code generator uses the prototype of func() and:

- passes c as a byte

- passes b as a _bit (in bit-memory)

- promotes i to long before passing it as a long

However, the code generator does not know anything of the printf() arguments, because this function is declared with a variable argument list. If there is no prototype (as with the old style K & R functions), the compiler promotes both char type and _bit type parameters to int type, the same way an automatic conversion is done in an assignment of a char/_bit type variable to an int type variable. So, with the printf() call the code generator:

- promotes c to int before passing it as int

- promotes b to int before passing it as int

- passes i as int

A lot of execution time of an application is spent transferring parameters between functions. Therefore this is an area which is very interesting for optimizations. cc51 has several different ways of parameter passing, which depend on the memory model/function qualifier used and on the parameter passing method selected. The memory model can be specified with the command line option -M{s|a|l|r}. A memory model on a function can be specified with one of the function qualifiers _small, _aux, _large or _reentrant.

The fastest parameter transport is via registers. Therefore, the compiler by default treats functions as _regparm functions. Up to three parameters can be passed via CPU registers. If a register is no longer available for a parameter or if a function is specified as _cdecl, parameter passing occurs in the fixed memory areas; the address space which is used for parameter passing is dependent on the memory model or function qualifier used.

If functions and function prototypes are not explicitly programmed with _regparm or _cdecl, the parameter passing method selected depends on the option -Or/-OR. Option -Or treats those functions as _regparm functions (default), and option -OR treats those functions as _cdecl functions.

1. _regparm _small
first parameters in registers, next in static area using naming convention

2. _cdecl _small
in static area using naming convention

3. _regparm _auxpage
first parameters in registers, next in static area using naming convention

4. _cdecl _auxpage
first parameters in fast area, next in static area using naming convention

5. _regparm _large
first parameters in registers, next in static area using naming convention

6. _cdecl _large
first parameters in fast area, next in static area using naming convention

7. _regparm _reentrant
first parameters in registers, next via virtual stack

8. _cdecl _reentrant
via a virtual stack

Up to three parameters can be passed via CPU registers. The following table shows how arguments are passed via the register parameter passing protocol.

Parameter char _data/ _idat / _pdat pointer int _xdat/ _rom pointer long float
parm1 r7 r7 r67 r67 r4567 r4567
parm2 r5 r5 r45 r45 - -
parm3 r3 r3 r23 r23 - -

Table 3-4: Register usage for parameter passing

After a long or float type argument, one more argument can be placed in r3/r23. Structures, unions and bits are never passed via registers.

The following examples clarify the parameter passing conventions:

Example with one argument:

- a is the first parameter and is passed in registers r6/r7.

Example with three arguments:

- b (first parameter) is passed in registers r6/r7.
- c (second parameter) is passed in registers r4/r5.
- d (third parameter) is passed in registers r2/r3.

Example with two long/float arguments:

- e (first parameter) is passed in registers r4/r5/r6/r7.
- f (second parameter) cannot be passed through registers
anymore; parameter is passed in static area using
naming convention.

Example with one long/float and one other argument:

- g (first parameter) is passed in registers r4/r5/r6/r7.
- h (second parameter) is passed in registers r3 (see the note above).

For auxpage/large functions, parameters passed as _cdecl functions result in faster parameter transport, using a static 'fast internal RAM' area for each register bank. This fast internal RAM is named __PARMx (x stands for the register bank used). Because it is very important to optimize parameter passing, parameters are placed in this area (4 bytes). Very often the parameter computation can be done directly into this area. The rest of the parameters are passed the conventional way: via the static area in external/PDAT memory of the function, using a naming convention.

All arguments which do not fit in the registers are passed in the same manner as for _cdecl declared functions. There is one exception to this rule, the fast parameter area is never used for _regparm functions.

The optimization for _cdecl functions is only done if the C program contains valid prototype declarations of the called functions and their parameters and the called functions do not have a variable argument list (ANSI notation of prototype declaration, using three dots, e.g.: void f(char *, ...);. Therefore, it is very important to use function prototypes and new style declarations, because this results in faster code.

A function that does not call any other function is called a 'leaf' function. If a function is a leaf function and the C code does not calculate the address of a parameter (via the & operator), the parameters of this function do not have to be copied to the static function parameter area. Thus, the parameters of such a function are left in fast internal RAM.

Non-leaf functions must copy the parameter registers in the static function parameter area at entry, as if they were placed there by the caller.

Note that run-time errors may appear if a C function is called using a valid prototype, while the declaration of the C function itself is not using this prototype (and vice versa).

For non-reentrant functions the parameter area of a function is allocated static too. This introduces a limit for functions with a variable argument list. Allocation is done by the compiler during the function definition. You are able to specify the size (in bytes) which must be allocated by the compiler for a variable argument list with the -asize option. The default is 20 bytes.

For example, in a single chip application using printf() 20 bytes is probably far too much. Therefore the printf() function is delivered in C source and can be recompiled using the -a option, and replaced in the library. There may also be another reason to replace the printf() function in the library, but this is explained in section 3.10, Strings.

Example:

replacing printf() of small library with a version using less RAM data as parameter area:

How to use the library manager (ar51) is described in the 8051 Cross-Assembler, Linker, Utilities User's Guide.

The other functions having a variable argument list (sprintf(), scanf() and sscanf()) are also delivered in C source.

Of course, a replacement as described above is likely to be done in small static models only, because RAM data is very scarce in this model. The reentrant model does not use static memory for parameter passing, so you never have to use the -a option with this model. Because parameters are passed on a (software) stack in external RAM, the only thing you have to do with this model is to be sure the system has enough stack space. With the auxpage/large (static) models the -a option applies, although more RAM space is available and there is less need to use it. Of course, when you use memory models on functions, using the _small, _aux or _large function specifier, the -a option can be useful again.

3.5 Function Overlay

When you use non-reentrant functions, overlaying is default done by cc51. cc51 allocates all automatics and function parameters in overlayable (BIT and DATA) segments.

link51 is capable of overlaying these segments with overlayable segments of other functions, when these functions have no (calling) reference to each other. Note that the function overlay control (FO) must be specified to link51. The overlaying mechanism deals with all RAM segments (BIT, DATA, IDAT, PDAT and XDAT).

3.6 Automatic Variables

In non-reentrant functions recursion is not possible. In these functions automatic variables are not allocated on a stack, but in a static area. In a reentrant function automatic variables are treated the conventional way: passed via the stack. In static functions it is possible to force an automatic to a specified memory by using a storage type specifier. The automatics are still overlayable with automatics of other functions. Automatics are subject to the memory model selected. In a static model this means static allocation in one of the RAM memory spaces. In the reentrant model this means dynamic allocation on the stack.

Although automatic variables are allocated in a static area with non-reentrant functions, they are not the same as local variables (within a function) which are declared to be static by means of the static keyword.

The difference is:

- (as in the 'normal' approach) it is not guaranteed that an automatic variable still has the same value as the previous time the function returned, because it may have been overlaid with another automatic variable of another module.

- (as in the 'normal' approach) it is guaranteed that the value of the static variable is the same as the previous time the function returned. Static variables are never overlaid.

To generate code which is as fast as possible, cc51 tries to place some automatic variables which are used the most into internal RAM, so these variables are treated as 'register variables' (for auxpage and large functions only). For leaf functions, automatic variables can be placed in registers also. See section 3.7, Register Variables.

cc51 does this only to variables which could be defined register by the programmer himself too (i.e. no address is taken from this variable).

The maximum amount of internal RAM that may be used for such local variables can be changed with the -xsize option or with the extend size pragma. The default size is 4 bytes. Note that these bytes can be allocated for each function. These bytes are overlayable.

When some variables are defined register in the C source, these variables are always placed in internal RAM and they reduce the number of automatics placed in internal RAM (see the example below).

The option FUNCTIONOVERLAY should be passed to the linker, otherwise you get the message "ADDRESS SPACE OVERFLOW". When you use this option but still get the linker error (SPACE 'DATA' or 'IDATA'), you can try to reduce the usage of internal RAM (use the -x option to cc51) or use less register/data variables in your program. Note also that the C library is compiled with the -x4 option (default).

Examples:

All examples assume the 'large' model is used and no objects can be placed in real processor registers.

Example 1:

results: 'c' is placed in 'xdat';
'i' is placed in 'xdat';
'l' is placed in 'data'.

Example 2:

results: 'c' is placed in 'data';
'i' is placed in 'data';
'l' is placed in 'xdat'.
'l' cannot be made a register variable because the address of it is used.

Example 3:

results: 'c' is placed in 'data', declared register;
'i' is placed in 'data', fits in register area next to 'c';
'l' is placed in 'xdat', not enough space in register area;

Example 4:

results: 'c', 'i' and 'l' are placed in 'data'. All variables called register are always placed in 'data'. Note that this routine takes 7 bytes of 'data' space, while the previous examples do not exceed the space defined by the -x option.

3.7 Register Variables

In C the register type qualifier tells the compiler that the variable will be used very often. So the code generator must try to reserve a register for this variable and use this register instead of the data location of this automatic variable. The 8051 has only eight registers (R0-R7, all eight bit), which are also needed by the code generator for normal code generation (indirection, intermediate results etc.). When possible, the compiler tries to allocate some automatic objects or parameter objects within these registers (or the B register). cc51 offers you the following implementation of register variables for the different memory models:

all models: The compiler tries to place parameters and automatic variables with the processor registers (R0-R7 and B). This is only done for functions not calling other functions (leaf functions). When allocating objects in registers, the code generator gives preference to objects declared with the register keyword. For every object not placed in registers, the next rules apply.

small: In this model automatic variables are allocated in data, which is directly addressable on-chip RAM. This memory is accessed by the 8051, the same way as certain registers (e.g. B, DPL, DPH, pushing/popping of registers: ACC, AR0- AR7). The code using this data memory has a very high execution speed, so in this model there is no need to treat a register variable in a special way, because all automatic variables are accessed with a speed comparable to a real register.

auxpage/large:
In order to increase execution speed and code density of register variables the register keyword causes the variable to be placed in fast internal data. These variables are overlayable with register variables and data automatics of other functions. Therefore, this optimization is default on with all static models. When no register variables are used, the compiler tries to use some automatic variables as register variables. See section 3.6, Automatic Variables.

reentrant: Register variables are recognized by the compiler and treated in a special way:

- a special area of 8 bytes in data memory is reserved by the compiler for register variables.

- this introduces a maximum of register variables: four integers or far pointers, eight characters, two longs or some combination.

3.8 Initialized Variables

Non automatic initialized variables use the same amount of space in both ROM and RAM (for all possible RAM memory spaces). This is because the initializers are stored in ROM and copied to RAM at start-up. This is completely transparent to the user. The only exception is an initialized variable residing in ROM, by means of the _rom storage type specifier.

Examples (large memory model) :

3.9 Type Qualifier volatile

You can use the volatile type qualifier when modifications on the object have undesired side effects when they are performed in the regular way. It may be undesired that the compiler attempts to optimize a memory update by keeping the value in a register (e.g., a hardware register). When a variable is declared with the volatile qualifier, the compiler disables such optimizations. The ANSI report describes that the updates of volatile objects follow the rules of the abstract machine (the target processor) and thus access to a volatile object becomes implementation defined.

Example:

3.10 Strings

In this section the word 'strings' means the separate occurrence of a string in a C program. So, array variables initialized with strings are just initialized character arrays, which can be allocated in any memory type, and are not considered as 'strings'. See section 3.8 , Initialized Variables, for more information on this topic.

cc51 places strings in both ROM and RAM. Where strings in RAM are placed depends on the specified memory model. If the -S option is used, the compiler places all strings in ROM only.

Library routines containing pointer arguments always expect the target memory of these pointers to be the default RAM of the memory model used to make this library.

For example:

In large memory model, this means printf() expects the address of the format string (the first argument) to have memory type _xdat. Therefore, the C startup code of the large memory model copies all strings from ROM to XDAT. So, the statement:

is executed correctly, because cc51 passes the address of the allocated XDAT area (filled at C startup time) to printf().

However, when using a microcontroller in a single chip application, you must be able to allocate strings in ROM only, and adapt your C source code to access these strings. The next example shows how strings can be placed in ROM only:

With the -S option on the command line:

The definition of pointer 'world' should change because it is pointing to ROM now instead of external RAM.

The third method is to use '#pragma romstring', for example:

See section 4.4, Pragmas in chapter Compiler Use, for more information about the pragmas romstring and ramstring.

These ROM typed strings can be accessed via a pointer to rom, so C functions can be made to manipulate (copy, print) these strings in a user friendly way. The standard library contains a number of memory copy/move functions from and to non default memory, which all are defined in the header file string.h.

Because standard library functions expect addresses in RAM only, these strings cannot be passed to these functions. Therefore we deliver the printf() function in C source, so you can recompile it, using a pointer to ROM as format string. The only thing you have to do is to define the following preprocessor symbol:

in both the _printf.c module and the header file stdio.h. How to compile the _printf.c module and replace the old _printf.obj module in the library has already been described in section 3.4 Function Parameters. If FORM_CONST is defined, the following prototype is used in stdio.h:

Be aware, that when using this version of printf(), all modules calling printf() must have target memory ROM as first argument. So when strings are used, the -S option must be on (or pragma romstring must be used). All other arguments of printf() always remain in the default RAM memory of the memory model used.

Everything explained above for printf() also applies to: fprintf(), sprintf(), vprintf(), vfprintf(), vsprintf(), scanf(), fscanf() and sscanf().

ANSI string concatenation is supported: adjacent strings are concatenated - only when they appear as primary expressions - to a single new one. The result may not be longer than the maximum string length (509 characters).

The Standard states that identical string literals need not be distinct, i.e. may share the same memory. Because memory can be very scarce with microcontroller applications, cc51 overlays identical strings within the same module.

In section 3.1.4 the Standard states that behavior is undefined if a program attempts to modify a string literal. Because it is a common extension to ANSI (A.6.5.5) that string literals are modifiable, there may be existing C source modifying strings at run-time. This can be done either with pointers, or even worse:

cc51 accepts this statement when strings are in both ROM and RAM. Of course, cc51 does not allow this statement when the -S option is used.

3.11 Pointers

Some objects have two types: a 'logical' type and a storage type. For example, a function is residing in ROM (storage type), but the logical type is the return type of this function. The most obvious C-51 type having different storage and logical type is a pointer. For example:

means p has storage type data (allocated in on chip RAM), but has logical type 'character in target memory space ROM'. The memory type specifier used left to the '*', specifies the target memory of the pointer, the memory specifier used right to the '*', specifies the storage memory of the pointer.

The memory type specifiers are treated like any other type specifier (like unsigned). This means the pointer above can also be declared (exactly the same) using:

If the target memory and storage memory of a pointer are not explicitly declared, cc51 uses the default of the memory model selected. For example, in the large model, the declaration:

is exactly the same as:

cc51 is very efficient in allocating pointers, because it recognizes far (2 byte) and near (1 byte) pointers. Pointers to DATA, IDAT and PDAT have a size of 1 byte, whereas pointers to ROM, XDAT and functions (in ROM) have a size of 2 bytes.

Another example:

The structure 's' resides in DATA and the compiler allocates 3 bytes for this structure, because 's.p' targets IDAT.

In pointer arithmetic cc51 checks, besides the type of each pointer, also the target memory of the pointers, which must be the same. For example, it is invalid (and has no use) to assign a pointer to data to a pointer to XDAT. Of course, an appropriate cast corrects the error, but in this case results remain unpredictable.

3.12 Function Pointers

In C-51 it is possible to use function pointers. You can pass parameters to indirectly called reentrant functions. You can also pass parameters with function pointers in static memory models as long as the parameters fit in registers.

You can specify the function model of the functions pointed to by a function pointer object. So, in the small memory model it is possible to have function pointers to _reentrant functions.

Note that a call to a function via a function pointer is not seen by the linker. When you use one of the static models of C-51, you must specify (in the linker control file) which function calls another function via a function pointer. Without this specification link51 would overlay data of functions that are indirectly called with other functions (when the FUNCTIONOVERLAY control is specified). This would result in run time errors.

You do not need to specify the function calls to _reentrant functions to the linker, because _reentrant functions do not allocate overlayable data.

3.13 Inline C Functions

With the _inline keyword, a C function can be defined to be inlined by the compiler. An inline function must be defined in the same source file before it is 'called'. When an inline function has to be called in several source files, each file must include the definition of the inline function. This is typically solved by defining the inline function in a header file.

Not using a function which is defined as an _inline function does not produce any code. Also during a debug session, the inlined function is not known.

Example (inline.c) mixed C and generated code:

; inline.c    1 _inline char *mystrcpy( char *s1, const char *s2 ) 
; inline.c    2 { 
; inline.c    3         register char *os1; 
; inline.c    4  
; inline.c    5         os1 = s1; 
; inline.c    6         while ( *s1 = *s2 ) 
; inline.c    7         { 
; inline.c    8                 s1++; 
; inline.c    9                 s2++; 
; inline.c   10         } 
; inline.c   11         return os1; 
; inline.c   12 } 
; inline.c   13  
; inline.c   14 _inline void nop ( int count ) 
; inline.c   15 { 
; inline.c   16         if ( count > 0 )
; inline.c   17         { 
; inline.c   18 #pragma asm 
; inline.c   19         NOP 
; inline.c   20 #pragma endasm 
; inline.c   21         nop( count - 1 ); 
; inline.c   22         } 
; inline.c   23 } 
; inline.c   24  
; inline.c   25 _inline long fib1 ( long n ) 
; inline.c   26 { 
; inline.c   27         return (n < 1 ? 1 : fib1( n - 1 ) + fib1( n - 2 )); 
; inline.c   28 } 
; inline.c   29  
; inline.c   30 int main ( void ) 
; inline.c   31 { 
        PUBLIC  _?main
INLINE_MAIN_DA  SEGMENT DATA OVERLAY( 0 )
        RSEG    INLINE_MAIN_DA
_10:    DS      100
; $mystrcpy#1$s1 = {R7} (register automatic)
; $mystrcpy#1$s2 = {R6} (register automatic)
; buf = _10 (automatic)
; $mystrcpy$os1 (unused automatic, no space allocated)
INLINE_MAIN_PR  SEGMENT CODE
        RSEG    INLINE_MAIN_PR
_?main:
        USING   0
; inline.c   32         char buf[100]; 
; inline.c   33  
; inline.c   34         mystrcpy( buf, buf+1 ); 
        MOV     R6,# LOW (_10+1)
        MOV     R7,# LOW (_10)
        SJMP    _9
_8:
        INC     R7
        INC     R6
_9:
        MOV     A,R6
        MOV     R1,A
        MOV     A,R7
        MOV     R0,A
        MOV     A,@R1
        MOV     @R0,A
        JNZ     _8
; inline.c   35  
; inline.c   36         nop(0); 
; inline.c   37         nop(1); 
        NOP
; inline.c   38         nop(3); 
        NOP
        NOP
        NOP
; inline.c   39  
; inline.c   40         return fib1( 10 ); 
        MOV     R7,#090H
        MOV     R6,#00H
; inline.c   41 } 
        RET

The pragmas asm and endasm are allowed in inline functions. This makes it possible to define inline assembly functions. See also section 3.14, Inline Assembly in this chapter.

3.14 Inline Assembly

cc51 supports inline assembly using the following pragmas:

#pragma asm Insert assembly text following this pragma.

#pragma asm_noflush As #pragma asm, but the peephole optimizer does not flush the code buffer.

#pragma endasm Switch back to the C language.

C modules containing inline assembly are not portable and are very hard to prototype in other environments.

The peephole optimizer in the compiler maintains a code buffer for optimizing sequences of assembly instructions before they are written in the output file. The compiler does not interpret the text of inline assembly. It passes inline assembly lines directly to the output file. To prevent that instructions in the peephole buffer, which belong to C code before the inline assembly lines, will be written in the output file after the inline assembly text, the compiler flushes the instruction buffer in the peephole optimizer. All instructions in the buffer are written to the output file. If this behavior is not desired the pragma asm_noflush starts inline assembly without flushing the code buffer.

See also section 7.9 , Assembly Language Interfacing in chapter Run-time Environment.

3.15 Built-in Functions

When you want to use some specific 8051 instructions, that have no equivalence in C, you would be forced to write assembly routines to perform these tasks. However, cc51 offers a way of handling this in C. Therefore, cc51 has a number of built-in functions, which are implemented as intrinsic functions:

The names of the built-in functions all have a leading underscore, because the ANSI specification states that public C names starting with an underscore are implementation defined.

Examples of the built-in functions are present in a file called inline.c, delivered with the package in the examples directory. It shows the way these functions can be used, and the 8051 instructions generated by cc51. You can compile the file with the options -Ms (small model) and -s (mixed C-source).

The following built-in functions are implemented (sample C source with generated assembly are given below):

_testclear

Read and clear semaphore using the JBC instruction.

Returns 0 if semaphore was not cleared by the JBC instruction, 1 otherwise.

_da

Decimal adjust operand after addition using the ADD and DA instructions.

Returns the result.

_nop

Generate NOP instructions.

Returns nothing.

_rol

Use the RL instruction to rotate (left) operand count times.

Returns the result.

_ror

Use the RR instruction to rotate (right) operand count times.

Returns the result.

_getbit

Returns the bit at bitoffset (range 0 - 7) of the bitaddressable operand for usage in bit expressions.

ICE denotes that the operand must be an Integral Constant Expression rather than any type of integral expression.

_putbit

Assign value to the bit at bitoffset (range 0 - 7) of the bitaddressable operand. ICE denotes that the operand must be an Integral Constant Expression rather than any type of integral expression.

Returns nothing.

3.16 Interrupt and Using

C-51 introduces two new reserved words: _interrupt and _using, which can be seen as special type qualifiers, only allowed with function declarations. A function can be declared to serve as an interrupt service routine. Interrupt functions cannot return anything and must have a void argument type list. For example, in:

An interrupt routine can also handle multiple interrupt numbers. Note that only one _using() is allowed. For example, in:

or:

cc51 places a long-jump instruction on the address of the vector of interrupt number 1, to the timer() routine, which switches the register bank to bank 2 and saves some more registers. When timer() is completed, the extra registers are popped, the bank is switched back to the original value and a RETI instruction is executed.

The relation between the interrupt number and the vector address is: interrupt_id = (vector_address - 3)/8.

For more details, see section 7.7, Interrupt Functions in chapter Run-time Environment.

Because the vector is filled by the compiler (unless disabled by _interrupt(-1) or by the -v option or by pragma novector), the interrupt number must be specified. To find out which interrupt number should be used, see section 7.7, Interrupt Functions.

You can call another C function from the interrupt C function. However, this function must be compiled with the same _using (register bank) attribute, because cc51 generates code which uses the addresses of the registers R0-R7. Therefore, the _using attribute is also possible with normal C functions (and their prototype declarations). Suppose timer() is calling get_number(). The function prototype (and definition) of get_number() should contain the correct _using:

cc51 checks if a function calls another function using another register bank, which is an error. The default register bank of a module is 0, or the bank number specified with the -b option.

When you want to call a function from within an interrupt function, the called function should have the same _using attribute. This has several reasons :

- cc51 generates fast code, i.e. it may address registers indirectly by use of their addresses in data. Because the register bank is switched, also the register addresses are changed.

- Each function uses a static allocated data space for its parameters and variables. When a function is called from the main C program and by an interrupt function, the values of the variables are overwritten.

For example, the function display is declared as :

This function uses the area '_display_BYTE' for its parameters. An interrupt routine calling this function immediately overwrites these values. You can solve this problem by calling a second function 'display' when in the interrupt routine. So you must create a function :

This function uses its own parameter area called '_display2_BYTE'. The example assumes you use register bank 2 for your interrupt routine. The interrupt routine may now call the created routine 'display2'.

In the reentrant model, register bank switching can be done also. We recommend using a _large, _small or _aux interrupt function and not to call a _reentrant function from the interrupt function. In that case you can use the standard library and there is no need to protect. When using interrupts, the same problems occur as described above. Thus the reentrant model provides recursion, but not reentrancy.

However, reentrancy is possible. To get real reentrancy you should not use the _using() qualifier. Then the compiler will automatically save all registers at every interrupt.

Besides this, you should not use static variables in your _reentrant routines called from the interrupt handler. Using these variables in your routine always overwrites the original contents of it.

In the reentrant model, a software stack pointer is being maintained. Because the instructions needed to update this software stack pointer are divisible, this stack pointer can be in an undefined state at the time of the interrupt. This introduces a problem, when calling another C function from the interrupt C function.

However, if interrupts are (temporarily) disabled, while updating the stack pointer, the problem does not occur. Therefore, we deliver a stack manager module, which disables interrupts during stack pointer updates. This module can replace the original stack manager module in the library. See section 7.10, Reentrant Model / _reentrant Functions in chapter Run-time Environment, for details on this subject.

Therefore, when using the reentrant model and the standard library, it is not possible to do any stack operations on the virtual stack; that is: access automatics, register variables of _reentrant functions or calling another _reentrant C function from the interrupt C function.

3.17 Register Bank Independent Code Generation

Option -noregaddr has been added to the compiler to switch to register bank independent code generation. In order to generate very efficient code the compiler uses absolute register addresses in its code generation. For example a register to register 'move'. Since there is no 'MOV register, register' instruction, the compiler will generate a 'MOV register, direct' with the absolute address of the source register as the second operand.

The absolute address of a register depends on the register bank, but sometimes this dependency is undesired. For example when a function is called from both the main thread and an interrupt thread. If both threads use different register banks, they cannot call a function that uses absolute register addresses. To overcome this, the compiler can be instructed to generate a register bank independent function that can be called from both threads.

Example:

3.18 MISRA C

Based upon the 'MISRA guidelines for the application of C language in vehicle based software', the TASKING MISRA C technology offers enhanced compiler error checking that will guide the programmer in writing better, more coherent and intrinsically safer applications. Through this configurable system of enhanced C language error checking, the use of error-prone C constructs can be prevented. A predefined configuration for compliance with the 'required rules' described in the MISRA guidelines is selectable through a single click in the C Compiler | MISRA C entry in the Project | Project Options dialog. A custom set of applicable MISRA C rules can be easily configured using the same menu. It is also possible to have a project team work with a MISRA C configuration common to the whole project. In this case the MISRA C configuration can be read from an external settings file. This too, is easily selected through the C Compiler | MISRA C entry in the Project | Project Options dialog.

Unfortunately it has not been possible to implement support for all 127 rules described in the MISRA guidelines. The reason for this is that a number of rules are beyond the scope of what can be checked in a C compiler environment. These unsupported rules are visible in the C Compiler | MISRA C | MISRA C Rules entry, but cannot be selected (grayed out).

MISRA is a registered trademark of MIRA Ltd. held on behalf of the Motor Industry Software Reliability Association.

Enabling MISRA C

From the command line MISRA C can be enabled by the following compiler option:

where n specifies the rule(s) which must be checked.

Error Messages

In case a MISRA C rule is violated, an error message will be generated,
for example:

See Appendix B, MISRA C, for the supported and unsupported MISRA C rules.

3.19 Structure Tags

A tag declaration is intended to specify the lay-out of a structure or union. If a memory type is specified, it is considered to be part of the declarator. A tag name itself, nor its members can be bound to any storage area, although members having type "... pointer to" do require one. A tag may then be used to declare objects of that type, and may allocate them in different memories (if that declaration is in the same scope). The following example illustrates this constraint.

struct S {
   _xdat int i;   /* referring to storage: not correct      */
   _idat char *p; /* used to specify target memory: correct */
   };

In the example above cc51 ignores the erroneous _xdat storage specifier (without displaying a warning message).

3.20 Typedef

Typedef declarations follow the same scope rules as any declared object. Typedef names may be (re-)declared in inner blocks but not at the parameter level. However, in typedef declarations, memory specifiers are allowed. A typedef declaration should at least contain one type specifier.

Examples:

typedef _idat int IDATINT;    /* storage type _idat: OK */
typedef int _data *DATAPTR;   /* logical type _data
                             storage type 'default' */

3.21 Switch Statement

cc51 supports three ways of code generation for a switch statement: a jump chain (linear switch), a jump table or a binary search table.

A jump chain is comparable with an if/else-if/else-if/else construction. A jump table is a table filled with JMP instructions for each possible switch value. The switch argument is used as an index to jump within this table. A binary search table is a table filled with a value to compare the switch argument with and a target address to jump to.

By default, the compiler will try to use the switch method which uses the least space in ROM (i.e. table size in ROMDATA plus code to do the indexing).

For a switch with a long type argument, only binary search table code is used. For an int type argument, a jump table switch is only possible when all case values are in the same 256 value range (i.e. the high byte value of all programmed cases are the same).

It is obvious that, especially for large switch statements, the jump table approach executes faster than the binary search table approach. Also the jump table has a predictable behavior in execution speed. No matter the switch argument, every case is reached in the same execution time.

With a small number of cases, the jump chain method can be faster in execution and shorter in size.

The compiler chosen switch method can be overruled by using:

The last one is also the default of the compiler. Using a pragma cannot overrule the restrictions as described earlier.

The _switch pragmas must be placed before the function body containing the switch statement. Nested switch statements use the same switch method, unless the nested switch is implemented in a separate function which is preceded by a different _switch pragma.

Example

3.22 Portable C Code

If you are developing C code for the 8051 using cc51, you might want to test some code on the host you are working on, using a C compiler for that host. Therefore we deliver the include file cc51.h. This header file checks if _CC51 is defined (cc51 only), and redefines the storage type specifiers if it is not defined.

When using this include file, you are able to use the storage type specifiers (when needed) and yet write 'portable C code'.

Furthermore an adapted prototype of each C-51 built-in function is present, because these functions are not known by another ANSI compiler. If you use these functions, you should write them in C, performing the same job as the 8051 and link these functions with your application for simulation purposes.

3.23 How to Program Smart in C-51

If you want to get the best code out of cc51, the following guidelines should be kept in mind:

1. If you are using the large model (because it is not possible to use the small model or auxpage model), try to declare the most frequently used variables (both static and automatic) with storage type data. If you want your code to remain portable, you can use the register keyword. See also section 3.22 Portable C Code and section 3.7 Register Variables. It is also possible to increase the internal automatics space (-x option), so the compiler places more variables in internal RAM.

Another approach may be even better: always use the small model, so parameter passing is always done via internal RAM. Specify the objects you want to be placed in XDAT.

2. Try to use the unsigned qualifier as much as possible (e.g. for (i = 0; i < 500; i++) ), because unsigned comparisons require less code than signed comparisons.

3. Try to use the smallest data type as possible: bit for boolean usage (flags), character for small loops and so on. See also section 3.3.3 , Character Arithmetic, and section 3.3.4, The _bit Type.

4. If execution speed is important (e.g. interrupt functions and time consuming loops), you must use the -Of option or #pragma optimize f or #pragma speed.

3.24 Some Examples of Complex Declarators

Because the cc51 has some extensions to support the various memory types of the 8051 processor family, declarations of objects may need some explanation.

First of all, declaration of simple objects is done exactly the same way as in standard C.

For example:

When programming portable C-code, declaration of pointers is also standard.

For example:

However, for code density it may be desired to place an object in another memory area, this can be done by preceding the object type by the requested data area specifier.

For example:

also correct is :

Now, pointers to another area than the default (specified by the memory model, see section 3.2.2 Memory Models) are declared as follows:

_data char * pdc; Pointer resides in default memory, points to a character in data.

_xdat int * pxi; Pointer resides in default memory, points to an integer in xdat.

_idat long * ppl; Pointer resides in default memory, points to a long in idat.

Even more difficult, these pointers may be placed in some other data area than the default.

For example:

_data char * _xdat xpdc; Pointer resides in xdata, points to a character in data.

_xdat int * _pdat ppxi; Pointer resides in pdata, points to an integer in xdat.

_idat long * _idat ippl; Pointer resides in idata, points to a long in idat.

Using objects located in data always produce less code than objects in xdata. So the smallest code size (and often the fastest execution speed) can be achieved by placing as many objects as possible in data. When it is not possible to place all objects in internal RAM, select the objects which are most referenced in the code.

Some examples of complex declarators are given below.

Now ppp is a pointer located in xdat, points to a pointer in default memory, this points to a pointer in idat, which is a pointer to a character in data.

Now func is a pointer located in data, points to a function with no arguments, returning a pointer to an integer in idat.

In static memory models it is not possible to call a function indirectly by a function pointer while passing parameters. An indirect call to a function with a void parameter list is still possible.


Copyright © 2002 Altium BV