4. Language

Dynamic C is based on the C language. The programmer is expected to know programming methodologies and the basic principles of the C language. Dynamic C has its own set of libraries, which include user-call­able functions. Please see the Dynamic C Function Reference Manual for detailed descriptions of these API functions. Dynamic C libraries are in source code, allowing the creation of customized libraries.

Before starting on your application, read through the rest of this chapter to understand the differences between standard C and Dynamic C.

For more information on the C language, see a reference book such as The C Programming Language by Kernighan and Ritchie (published by Prentice-Hall).

4.1 Storage Classes

Variable storage can be auto or static. The term “static” means the data occupies a permanent fixed location for the life of the program. The term “auto” refers to variables that are placed on the system stack for the life of a function call. The default storage class is auto, but can be changed by using #class static; however, using this compiler directive with “static” is deprecated starting with Dynamic C 10.44.

The default storage class can be superseded by the use of the keyword auto or static in a variable dec­laration. These keywords apply to local variables, that is, variables defined within a function. If a variable does not belong to a function, it is called a global variable—available anywhere in the program—but there is no keyword in C to represent this fact. Global variables always have static storage.

The register type is reserved, but is not currently implemented. Dynamic C will change a variable to be of type auto if register is encountered. Even though the register keyword is not implemented, it still can not be used as a variable name or other symbol name. Its use will cause unhelpful error mes­sages from the compiler.

4.2 Pointers

Pointer checking is a run-time option in Dynamic C. Use the Compiler tab on the Options | Project Options menu. Pointer checking will catch attempts to dereference a pointer to unallocated memory. However, if an uninitialized pointer happens to contain the address of a memory location that the compiler has already allocated, pointer checking will not catch this logic error. Because pointer checking is a run-time option, pointer checking adds instructions to code when pointer checking is used.

Pointer checking is not currently supported for far pointers.

4.3 Far Pointers and Far Data

This section examines the syntax of the far keyword, using examples from simple variables to complex aggregate types.

4.3.1  The far Qualifier

The far keyword allows a programmer to directly declare variables in xmem. Previous to this develop­ment, usage of xmem was limited to library routines such as root2xmem() and xmem2root() using memory allocated using xalloc. Now, the compiler will directly generate code to access xmem allocated through standard variable declarations with the addition of the far keyword.

4.3.2  Basic Declarations

In almost all respects, far behaves syntactically identically to the const qualifier.

The keyword far was added to use the same basic principles as const, with a few exceptions. The rea­son for this is that far and const both indicate the storage type for variables. In the case of const, the storage is in the flash device. Variables declared as far are stored in xmem in RAM (and can therefore be modified). A variable can also be declared as const far, which places the constant variable in the xmem space on the flash device.

far type var;             // Declares a variable “var” having far storage

We also allow

type far var;

which has the same meaning as the previous declaration. In other words, the far keyword may appear before or after the base type.

We do not allow

far type far var;

In this context, these are base type qualifiers. The far keyword can also qualify pointer types, such as in the following example:

type * far ptr;

This declares a variable, ptr, having far storage pointing to an object of type type. Pointer qualifiers are always found on the right-hand side of the ‘*’ token.

Here is a slightly more complex declaration:

far type * far ptr;

Here, the object type to which ptr points is qualified as having far storage.

4.3.3  Multi-Level Far Pointers

The semantics of the far qualifier can become quite complex if used with multi-level pointers. Some con­fusion arises when thinking about how to qualify different pointer levels in a more complex declaration such as the following basic pointer-to-pointer declaration:

type * * ptr;

This declares ptr as a variable which points to an object of type pointer to type, or simply, ptr is a pointer to pointer-to-type. What if we wanted to declare ptr to be a pointer to a pointer having far stor­age (the pointer to type is in xmem, but what it points to is in root)? This would have the following decla­ration:

type * far * ptr;

Here we see that pointer declarations are right-associative. Recalling that the far qualifier associates with the ‘*’ token to its left, we see that the nested pointer type is the left ‘*’ not the right one, illustrated using brackets:

[type * far] * ptr;

In the above example, the association of the ‘*’ and far is evident – the variable ptr is a pointer-in-root, and it points to a pointer-in-far.

For another example, a complex and infrequent declaration might be:

far type * far * * far ptr;

A succinct way of stating the type of ptr in this example would be: ptr is a pointer-in-far to a pointer-in-root to a pointer-in-far to a variable of type having far storage.

4.3.4  Arrays and Structures

The far qualifier can also be applied to arrays and structures, with the effect of the compiler allocating storage for those variables from xmem. The declarations for both structures and arrays (and pointers to those types) follow the same rules as basic type variable declarations. An example structure declaration might be:

struct S {
   int a;
   char b[20];
};

far struct S str;            // A structure of type S in xmem

Note that the far qualifier is applied only to the actual declaration of a variable with the structure type, not the structure definition itself. The far qualifier may not be applied to either a structure type definition or any member of a structure. If a structure instance variable is placed in xmem using the far keyword, then all members of that instance are in xmem – you can not mix xmem and root within a single structure.

Arrays can also be placed in xmem using far. The following is a possible declaration of an array in xmem:

far type array[5000];      // An array of 5000 elements of type type in xmem

Note that the size limit imposed on arrays in root memory (32,767 bytes) also apply to far arrays. You can declare an array as large as your largest contiguous free block of xmem available up to this limit. See Chapter 10 “Memory Management” for more information on how xmem is allocated and used by the com­piler.

4.3.5  Complex Declarations

All of the elements discussed so far can be applied in a single declaration to produce very complex types for variables. As an example, such a declaration may look like the following:

const type (* far const ptr)[c0][c1] = &const_array;

In this example, ptr is a constant pointer-in-far (xmem constant) to a 2-dimensional array of c0 x c1 ele­ments of type constant type. In other words, we have a pointer in xmem to a two-dimensional array of con­stant elements. The array the pointer is pointing to is in root memory, since the far qualifier only associates with the pointer variable itself. The pointer is constant, so it must be initialized, and the first const implies that we can not change the elements in the array since they represent constants (which are in flash and can not be modified). We assume that c0, c1, and const_array are all constant variables or literals defined previously.

4.3.6  Sample Programs

From the Dynamic C installation directory, look in /Samples/Rabbit4000/FAR/ for sample pro­grams demonstrating the use of the far keyword. The sample far_demo.c shows how to declare a local variable that will be stored in far memory (which means it must be declared static) and accessed just like any other local variable. The sample LinkedList.c demonstrates far pointers and includes a library, LinkedList.LIB, that creates and maintains a linked list in the far memory space.

4.4 The const Keyword

4.4.1  Simple Constants

The const keyword allows a programmer to tell the compiler that a particular "variable" should not be modified1  after the initial assignment in its declaration. If any code tries to assign a new value to that vari­able, the compiler will generate a warning or error indicating that the assignment operation should not occur. This allows a programmer to prevent unwanted modifications to variables that for some reason should not be changed. Note that const variables must be initialized; otherwise there is no other way to assign them values.

The following is an example of a simple declaration of a constant integer:

const int number = 42;

Note that the const in the above declaration can also come after the type, as in the following:

int const number = 42;

This may look a little strange, but it is consistent with the pointer syntax (described below) and may show up in code from time to time.

In a simple const variable declaration any storage type may be used. It is possible to have an auto const variable; it simply means that the value is stored on the stack and cannot be modified. In most cases, sim­ple const declarations should probably be static (this is not the case with pointers, as we will see below). Constants are non-modifiable so multiple accesses (say via a re-entrant function) will all be reads, thus making a single storage location preferable to allocating space for the constant on the stack for each call.

Prior to Dynamic C 10.64, the const keyword required static storage in all cases. This restriction has been removed and const now obeys the ANSI C89 semantics described above.

4.4.2  Const and Pointers

Simple variable declarations with const are straightforward and fairly easy to understand. However, const is much more interesting when applied to pointers. The rules are fairly simple, but the resulting behavior can be somewhat confusing.

To begin, let's look at the declaration for a simple pointer, in this case a string literal constant:

const char * string = "ABCD";

Here we see a const modifier on a pointer declaration. As in the simple declaration in the previous section, the declaration starts with const. However, since this is a pointer, the meaning may be slightly confusing. The const in this case actually refers to the character being pointed at (the 'A' in the string literal "ABCD") and not the variable string. The reason for this is that string is actually a pointer to a constant string, in this case our literal value. The pointer string is actually a variable that can be modified, but dereferencing string and assigning to it will result in an error:

string = &another_string; // This is OK
*string = 'X'; // WARNING

Now, it is possible to make string into a constant, and C provides a somewhat unintuitive syntax, as fol­lows:

char * const const_string = &another_string;

In this declaration, note that the const modifier comes after the "*" defining string to be a pointer. In this case, const_string itself is constant and cannot be modified, but what it points to can be, as in the follow­ing:

const_string = &yet_another_string; // WARNING

*const_string = 'X'; // This is OK

Furthermore, it is possible to declare both the pointer and what it points to as constant, with the resulting behavior:

const char * const really_const_string = "ABCD"; // Declaration
really_const_string = &yet_another_string; // WARNING
*really_const_string = 'X'; // WARNING

The const modifier can be applied in turn to each level of pointer, following the "*" for that level, as fol­lows:

const char * const * const const_ptr = &ptr_array;

Multiple-level pointers have some tricky semantics with regard to conversions, as we will see in the next section.

4.4.3  Const Conversions, Casting, and Parameter Passing

4.4.3.1  Conversions

The const modifier, when applied to variables in C, must obey a number of conversion rules similar to those for integer promotion. As a result, there are some tricky conversion subtleties that are good to know, though the compiler should catch them for you. Note that the conversions in this section apply only to assignments since the use of const in function parameters has some counterintuitive consequences.

We have already seen an implicit cast when assigning a constant to another non-const variable. In the fol­lowing, the cast occurs at the assignment of the constant to the variable, and the conversion effectively dis­cards the const:

const int const_num = 42;
int non_const_num;
non_const_num = const_num;

In this specific case, it does not really matter that the const was discarded since the non-const variable is actually assigned a copy of the value of the constant. However, when we look at pointers with const, there is a lot more complexity:

const int number = 42;
const int *const_ptr;
int *non_const_ptr;
const_ptr = &number; // OK
non_const_ptr = const_ptr; // WARNING

In this example, the variable non_const_ptr is a pointer to a non-const integer, so when the assign­ment occurs the const is discarded, but since const_ptr points at a value that should not be modified (because it is const), then the compiler will issue a warning. Remember that the pointers in this example are not const since there is no const following the "*" in either declaration. We will see later how const can be cast away explicitly to remove the warning.

The above example demonstrated a condition where the compiler will generate a warning, but this is due to the fact that const was being discarded possibly inadvertently. However, the same does not apply for the operation in reverse:

int non_const_number = 42;
const int *const_ptr;
int *non_const_ptr;
non_const_ptr = &non_const_number; // OK
const_ptr = non_const_ptr; // OK

This example demonstrates that we can often make something more const, that is, we can convert a pointer-to-non-const to a pointer-to-const without issue because the thing being pointed to can be modi­fied, and it is OK if the pointer to it restricts that modification. This can be thought of as being akin to the integer promotion rules - conversion to a bigger type is okay because there are enough bits to represent the smaller number, but conversion to a smaller type generates a warning because data is potentially being lost.

The above case applies to single-level pointers, but in the case of multi-level pointers there are restrictions on const because of some subtleties that multiple levels of pointers introduce. Consider the following example:

int const **const_ptr;
int **non_const_ptr;
const_ptr = non_const_ptr;  // WARNING

The above example shows a case where assigning a pointer to a "more const" pointer is a problem. The reason for this is that it is possible to accidentally discard a const when using two or more pointer levels. The compiler checks for this and issues a warning to prevent what can result in very subtle problems.

4.4.3.2  Casting

Though not usually recommended, we can get around const warnings through the use of explicit casting. This usually comes in handy when interfacing with code you do not have access to or the time to correctly implement const everywhere in your application. With casting, our former warnings go away:

const int number = 42;
const int *const_ptr;
int *non_const_ptr;
const_ptr = &number; // OK
non_const_ptr = (int *)const_ptr; // OK with cast

Note that in this example, accesses through the non_const_pointer variable will result in possible runtime errors depending on how the target variable number is stored - for example, if number was stored in flash (because it is a constant) then writes to it will not work.

4.4.3.3  Parameter Passing

Parameter passing is a special case for const, essentially because parameters in a function call are equiva­lent to initializers for the corresponding arguments in the function prototype. For this reason, the function call in this next example is correct even though the direct assignment to an equivalent variable is wrong:

void foo(const int foo_x) { // No initializer allowed
// foo_x cannot be modified
}

int non_const_num = 5;
const int x = 2; // equivalent declaration to parameter foo_x above
foo(non_const_num); // OK
x = non_const_num; // WARNING

The basic rule for non-pointer function parameter passing is that you can always pass something that is "less const" to a function expecting a parameter that is "more const". For pointers, it is a little trickier because of the multiple-level pointer issue mentioned previously. The following example illustrates the problem with multiple-level pointer parameters to functions:

void bad_const(const char **a, const char **b) {
   *b = *a;
}

main() {
   const char *foo = "hello";
   char *bar;

   bad_const(&foo, &bar);
   // bar now points at foo, discarding const
   bar[3] = 'g'; // !!! this modifies what foo points to
   printf("%s\n", foo);
   return;

}

In the above example, foo is a pointer to a const and bar is a pointer to non-const. Even though the param­eter b in the function bad_const appears to be more const than bar, the problem is that the pointer derefer­ence and assignment in the function will allow the non-const bar to point at the const data pointed to by foo in the main function. The subsequent assignment to an element of bar actually modifies the string lit­eral "hello". Depending on the location of that string literal the assignment could cause a program failure, or at best a modification of a string the programmer explicitly declared as being const. The compiler checks for this type of parameter passing and warns to prevent this type of error.

4.4.4  Dynamic C Version Differences

The following table demonstrates some examples of const behavior prior to the ANSI-compliant const behavior in Dynamic C 10.64, and compares the old non-ANSI behavior with the new to give you an idea of how to port code from older versions of Dynamic C. Note that non-const initializers were added in Dynamic C 10.60 so there will be some subtle differences porting from that version as compared to porting from earlier versions.

Note that const used to explicitly define the storage of the associated value to be in flash. On later devices that store the program in flash but run in RAM, the const values were stored with the code in RAM following the startup flash-to-RAM copy. With const correctness, the storage is defined by the storage class (auto or static) and literals are stored in flash (or with the code for run-in-RAM devices).

Table 4-1.  

Const example

Dynamic C Behavior

Behavior in Dynamic C 10.64 and Later

const int x = 10;

x is stored in flashA

x is stored in the default stor­age class (auto in functions, otherwise extern) and is non-modifiable (const). The value x may be stored in flash.

auto const x = 10;

Error, auto const not alloweda

x is stored on the stack but is not modifiable

const char* s = "ABCD";

"ABCD" and s are both const and stored in flash.

Prior to version 10.60, a second const (for the pointer) was required and this code would have produced a warning.a

"ABCD" is stored in flash/constant space, but s is stored using the default stor­age class and is modifiable (non-const)

const char * const s = "ABCD";

Same as
'const char* s = "ABCD"'a

"ABCD" and s are both const and stored in flash/constant space

char * const ptr = &s;

Error (prior to Dynamic C 10.62) - some older versions would ask for const before the "char". More recently this would compile without error.a

The variable ptr is a non-mod­ifiable (const) pointer stored in flash/constant space to a modifiable character stored in RAM

int foo(const int x);

Error - const not allowed for function parametersa

The parameter x is stored on the stack but is not modifiable within the function foo.

int foo(char * str) { }
const char * const s = "ABCD";
foo(s);

OK, const property of variable s is ignored in function calla

Warning at function call - passing s to foo discards the const modifier of s

AVariable initializers were introduced in Dynamic C 10.60, which changed some of the behavior for initial­ized variables. Other const changes are new with Dynamic C 10.64. The table primarily refers to the behavior prior to version 10.60.

4.5 Pointers to Functions, Indirect Calls

Pointers to functions may be declared. When a function is called using a pointer to it, instead of directly, we call this an indirect call. The syntax for declaring a pointer to a function is different than for ordinary pointers. Standard syntax for a pointer to a function is:

returntype (*name)( [argument list] );

for example:

int (*func1)(int a, int b);
void (*func2)(char*);

You can pass arguments to functions that are called indirectly by pointers, and the compiler will check them for correctness if an argument list is provided in the function pointer declaration. This means that the auto promotions provided by Dynamic C type checking will automatically be applied. For example, if a function takes a long as a parameter, and you pass it a 16-bit integer value, it will be automatically cast to type long in order for 4 bytes to be put onto the stack, as would happen with a normal function call.

Prior to version 10.62, Dynamic C did not recognize the argument list in function pointer declarations and would generate an error. The syntax for the above examples would have looked like the following:

int (*func1)();
void (*func2)();

Note that in ANSI C and Dynamic C 10.62 (and later) these statements use valid syntax that indicates that any parameters passed to the function pointers will not be type-checked, essentially providing a wild-card for the parameter list. Using this syntax is dangerous since it can lead to stack imbalances (passing a 16-bit integer to a function taking a 32-bit long, for example) and it should be used with great care.

It is advisable that function pointers in older Dynamic C programs be updated to use parameter lists to catch potential errors, but those programs will continue to compile without changes in Dynamic C 10.62 and later.

The following program shows some examples of using function pointers:

int intfunc(int x, int y);
typedef int (*fnptr)(int, int);// create pointer to function that returns an integer

 

main(){
   int x,y;
   int (*fnc1)(int, int);      // declare var fnc1 as a pointer to an int function
   fnptr  fp2;                 // declare var fp2 as pointer to an int function
   fnc1 = intfunc;             // initialize fnc1 to point to intfunc()
   fp2 = intfunc;              // initialize fp2 to point to the same function
   x = (*fnc1)(1,2);           // call intfunc() via fnc1
   y = (*fp2)(3,4);            // call intfunc() via fp2
   printf("%d\n", x);
   printf("%d\n", y);
}

int intfunc(int x, int y){
   return x+y;
}

4.6 Function Chaining

Function chaining allows special segments of code to be distributed in one or more functions. When a named function chain executes, all the segments belonging to that chain execute. Function chains allow the software to perform initialization, data recovery, and other kinds of tasks on request. There are two direc­tives, #makechain and #funcchain, and one keyword, segchain that create and control function chains:

#makechain chain_name

Creates a function chain. When a program executes the named function chain, all of the functions or chain segments belonging to that chain execute. (No particular order of execution can be guaranteed.)

#funcchain chain_name name

Adds a function, or another function chain, to a function chain.

segchain chain_name { statements }

Defines a program segment (enclosed in curly braces) and attaches it to the named function chain.

Function chain segments defined with segchain must appear in a function directly after data declara­tions and before executable statements, as shown below.

my_function(){
/* data declarations */
segchain chain_x{
 
/* some statements which execute under chain_x  */
}
segchain chain_y{
/* some statements which execute under chain_y */
}
/* function body which executes when my_function is called */
}

A program will call a function chain as it would an ordinary void function that has no parameters. The fol­lowing example shows how to call a function chain that is named recover.

#makechain recover
...
recover();

4.7 Global Initialization

Various hardware devices in a system need to be initialized, not only by setting variables and control regis­ters, but often by complex initialization procedures. Dynamic C provides a specific function chain, _GLOBAL_INIT, for this purpose. Your program can add segments to the _GLOBAL_INIT function chain, as shown in the example below.

long my_func( char j );

main(){
my_func(100);
}

long my_func(char j){
static int i;
static long array[256];

// The GLOBAL_INIT section is automatically run once when the program starts up

#GLOBAL_INIT{     
for( i = 0; i < 100; i++ ){
array[i] = i*i;
}
}

return array[j];   // only this code runs when the function is called 
}

The special directive #GLOBAL_INIT{ } tells the compiler to add the code in the block enclosed in braces to the _GLOBAL_INIT function chain. Any number of #GLOBAL_INIT sections may be used in your code. The order in which they are called is indeterminate since it depends on the order in which they were compiled. The storage class for variables used in a global initialization section must be static. Since the default storage class is auto, you must define variables as static in your application.

The _GLOBAL_INIT function chain is always called when your program starts up, so there is nothing special to do to invoke it. In addition, it may be called explicitly at any time in an application program with the statement:

_GLOBAL_INIT();

Make this call this with caution. All costatements and cofunctions will be initialized. See Section 7.2 for more information about calling _GLOBAL_INIT().

4.8 Libraries

Dynamic C includes many libraries—files of useful functions in source code form. They are located in the \LIB directory where Dynamic C was installed. To support larger memories, some changes to the Dynamic C environment were made in version 10.21. One such change is that the \LIB directory now contains two separate directories, Rabbit2000_3000 and Rabbit4000. Each directory contains the same structure previ­ously used by \LIB, but the libraries have been updated for the Rabbit 4000 processor in the \Lib\Rabbit4000 directory.

The default library file extension is “LIB”. Dynamic C uses functions and data from library files and com­piles them with an application program that is then downloaded to a controller or saved to a .bin file.

An application program (the default file extension is .c) consists of a source code file that contains a main function (called main) and usually other user-defined functions. Any additional source files are consid­ered to be libraries (though they may have a .c extension) and are treated as such. The minimum applica­tion program is one source file, containing only:

main(){}

Libraries (those defined by you and those defined by Rabbit) are “linked” with the application through the #use directive. The #use directive identifies a file from which functions and data may be extracted. Files identified by #use directives are nestable, as shown below.

Note that as of Dynamic C 10.60, the standard #include C construct can be used.  In particular, this allows the organization of code into .c (code) and .h (header) files.

 

Figure 4.1  Nesting Files in Dynamic C

Nesting1.png

Most libraries needed by Dynamic C programs have #use statements in lib\..\default.h.

Section 4.10 explains how Dynamic C knows which functions and global variables in a library are avail­able for use.

Note that as of Dynamic C 10.60, the standard #include C construct can be used.  In particular, this allows the organization of code into .c (code) and .h (header) files.

4.8.1  Libraries and File Scope

Starting in Dynamic C 10.64, C files now each have their own scope. Previously, all C files and libraries shared the same global scope as the BIOS. The new scoping functionality places all symbols defined in the BIOS into the global scope, and each C file scope inherits the symbols from that scope. Libraries similarly inherit their scopes from the C files in which the first #use of that library is encountered. The library then has access to all the symbols defined in the global scope and all of the symbols defined in the enclosing C file scope. All symbols defined in the header sections in the .LIB file will be included in all files that #use that library. All symbols defined in the module bodies in the .LIB file will be added only to the first C file that is compiled that contains a #use of that library.

As an example, consider an application with the 3 files shown below: File_1.C, File_2.C, and Lib_1.LIB. Both C files contain the line "#use Lib_1.LIB", but whichever C file is compiled first will define the file scope for Lib_1.LIB; in this case File1.C is compiled first. If a symbol is defined in File_2.C that is used by Lib_1.LIB (in this case the function foo), that symbol must be declared as extern in either File_1.C or in Lib_1.LIB and not be declared as static in File_2.C, even if the definition of that symbol comes before the #use. The symbols defined in the header sections of Lib_1.LIB are added to both C files, while the sym­bols defined in the module bodies in Lib_1.LIB are added only the scope of File_1.C.

// File1.C

#use "Lib1.LIB"

void main(void) { // main is visible in File1.C and Lib1.LIB, but not File2.C

  global_var = 2; // From the module foo

  bar();

}

 

// File2.C

void foo(void) { … }  // Cannot be static

#use "Lib1.LIB" // foo is not visible in Lib1.LIB since Lib1.LIB has the same scope as File1.C

 

// Lib1.LIB

/*** BeginHeader bar ***/

extern void foo(void); // This is required for foo to be visible within this LIB file

void bar(void);

/*** EndHeader ***/

 

int global_var; // Visible within File1.C's scope, but not within File2.C

void bar(void) { foo(); } // bar is in the same scope as main in File1.C and not in the scope of File2.C

Note that the behavior of #use is different than that of #include since #include merely includes the same text (the contents of the .H file) in-line at the point of the #include.

4.8.2  LIB.DIR

Any library that is to be #use’d in a Dynamic C program must be listed in the file LIB.DIR, or another *.DIR file specified by the user. LIB.DIR is in the root directory of the Dynamic C installation.

The lib.dir strategy allows naming a folder with optional mask(s). Having no mask implies “*.*” and mul­tiple masks are separated by “;” so that “lib” and “lib\*.*” both include all files and “lib\*.lib;*.c;*.h*” includes all files with extensions of .lib, .c and .h. Dynamic C gener­ated files (e.g., .mdl, .hxl, etc.) are not parsed, which means they are excluded when using the wildcard mask.

Dynamic C enforces unique file extension names regardless of path, so that “#use myfile.lib” can not use an unintended copy of myfile.lib as the list of pathnames included in lib.dir is searched for the first occurrence of that file extension. An error message naming both full paths will come up when trying to compile ANY program alerting the user of the infraction.

A new feature introduced in Dynamic C10.21 is the ability to define masks that use exclusion criteria that will exclude specified folders and files from the “lib.dir” file. Such a lib.dir entry is just like a normal one except the it starts with “>”, a character that Windows does not allow in a folder name. Once exclusions are defined, they persist throughout all entries that follow. To make this clear, look at the following exam­ples of “lib.dir entries:

Example 1:

The following “lib.dir” entries:

>CVS
Lib\Rabbit4000
Samples\*.Lib

excludes CVS folder trees throughout following entries, includes all files in Lib\Rabbit4000 and its subfolders (except for CVS subfolders), and includes all “.lib” files in Samples and its subfolders (except for CVS subfolders).

Thus, the ordering of the LIB.DIR entries is meaningful, as shown in the next example.

Example 2: 

As dictated by the following three entries, the file Lib\Rabbit4000\BiosLib\Stdbios.c is excluded from LIB.DIR.

>*.c
Samples
Lib\Rabbit4000

Reordering the entries as shown below results in the file Lib\Rabbit4000\BiosLib\Stdbios.c being included in LIB.DIR.

Lib\Rabbit4000
>*.c
Samples

Example 3: 

Dynamic C will not correctly process lines that include spaces:

\Lib\ MyLibs \*.Lib             // WRONG, because of spaces in path
\Lib\MyLibs\*.Lib               // CORRECT, spaces removed

4.9 Headers

The following table describes two kinds of headers used in Dynamic C libraries.

Table 4-2.  Dynamic C Library Headers

Header Name

Description

Module headers

Make functions and global variables in the library known to Dynamic C.

Function Description headers

Describe functions. Function headers form the basis for function lookup help.

You may also notice some “Library Description” headers at the top of library files. These have no special meaning to Dynamic C, they are simply comment blocks.

4.10 Modules

A Dynamic C library typically contains several modules. Modules must be understood to write efficient custom libraries. Modules provide Dynamic C with the names of functions and variables within a library that may be referenced by files that have a #use directive for the library somewhere in the code.

Modules organize the library contents in such a way as to allow for smaller code size in the compiled application that uses the library. To create your own libraries, write modules following the guidelines in this section.

The scope of modules is global, but indeterminate compilation order makes the situation less than straight­forward. Read this entire section carefully to understand module scope.

4.10.1  The Parts of a Module

A module has three parts: the key, the header, and the body. The structure of a module is:

/*** BeginHeader func1, var2, .... */
prototype for func1
extern var2
/*** EndHeader */
definition of func1
declaration for var2
possibly other functions and data

A module begins with its BeginHeader comment and continues until either the next BeginHeader comment or the end of the file is encountered.

 4.10.1.1 Module Key

The module key is usually contained within the first line of the module header. It is a list of function and data names separated by commas. The list of names may continue on subsequent lines.

/*** BeginHeader [name1, name2, ....] */

It is important to format the BeginHeader comment correctly, otherwise Dynamic C cannot find the contents of the module. The case of the word “beginheader” is unimportant, but it must be preceded by a forward slash, 3 asterisks and one space (/*** ). The forward slash must be the first character on the line. The BeginHeader comment must end with an asterisk and a forward slash ( */).

The key tells the compiler which functions exist in the module so the compiler can exclude the module if names in the key are not referenced. Data declarations (constants, structures, unions and variables) as well as macros and function chains (both #makechain and #funchain statements) do not need to be named in the key if they are completely defined in the header, i.e, no extern declaration. They are fully known to the compiler by being completely defined in the module header. An important thing to remember is that variables declared in a header section will be allocated memory space unless the declaration is pre­ceded with extern.

 4.10.1.2 Module Header

Every line between the BeginHeader and EndHeader comments belongs to the header of the module. When a library is linked to an application (i.e., the application has the statement: #use “library_name”), Dynamic C precompiles every header in the library, and only the headers.

With proper function prototypes and variable declarations, a module header ensures proper type checking throughout the application program. Prototypes, variables, structures, typedefs and macros declared in a header section will always be parsed by the compiler if the library is #used, and everything will have global scope.

It is even permissible to put function bodies in header sections, but it is not recommended for two reasons. First, because the function will be compiled with any application that #uses the library and since variables declared in a header section will be allocated memory space unless the declaration is preceded with extern, the variable declaration should be in the module body instead of the header to save data space. Second, auto (local) variables are not visible to the debugger when a function is defined (not prototyped) within a module header. This means that attempting to set a watch or evaluate expression on such a vari­able will result in an “out of scope / not declared” error or the variable’s stated value will not be correct.

The scope of anything inside the module header is global; this includes compiler directives. Since the headers are compiled before the module bodies, the last one of a given type of directive encountered will be in effect and any previous ones will be forgotten.

Using compiler directives like #class or #memmap inside module headers is inadvisable. If it is impor­tant to set, for example, “#class auto” for some library modules and “#class static” for others, the appropri­ate directives should be placed inside the module body, not in the module header. Furthermore, since there is no guaranteed compilation order and compiler directives have global scope, when you issue a compiler directive to change default behavior for a particular module, at the end of the module you should issue another compiler directive to change back to the default behavior. For example, if a module body needs to have its storage class as static, have a “#class static” directive at the beginning of the module body and “#class auto” at the end.

NOTE: The compiler directive “#class static” is deprecated starting with Dynamic C 10.44.

 4.10.1.3 Module Body

Every line of code after the EndHeader comment belongs to the body of the module until (1) end-of-file or (2) the BeginHeader comment of another module. Dynamic C compiles the entire body of a module if any of the names in the key or header are referenced anywhere in the application. So keep modules small, don’t put all the functions in a library into one module. If you look at the Dynamic C libraries you’ll notice that many modules consist of one function. This saves on code size, because only the functions that are called are actually compiled into the application.

To further minimize waste, define code and data only in the body of a module. It is recommended that a module header contain only prototypes and extern declarations because they do not generate any code by themselves. That way, the compiler will generate code or allocate data only if the module is used by the application program.

4.10.2  Module Sample Code

There are many examples of modules in the Lib directory of Dynamic C. The following code will illus­trate proper module syntax and show the scope of directives, functions and variables.

/*** BeginHeader ticks*/
extern unsigned long ticks;
/*** EndHeader */

unsigned long ticks;

/*** BeginHeader Get_Ticks */
unsigned long Get_Ticks();
/*** EndHeader */

unsigned long Get_Ticks(){
...
}

/*** BeginHeader Inc_Ticks */
void Inc_Ticks( int i );
/*** EndHeader */

#asm
Inc_Ticks::
or   a
ipset 1
...
ipres
ret
#endasm

There are three modules defined in this code. The first one is responsible for the variable ticks, the sec­ond and third modules define functions Get_Ticks() and Inc_Ticks that access the variable. Although Inc_Ticks is an assembly language routine, it has a function prototype in the module header, allowing the compiler to check calls to it.

If the application program calls Inc_Ticks or Get_Ticks() (or both), the module bodies corre­sponding to the called routines will be compiled. The compilation of these routines triggers compilation of the module body corresponding to ticks because the functions use the variable ticks.

/*** BeginHeader func_a */

int func_a();

#ifdef SECONDHEADER
#define XYZ
#endif

/*** EndHeader */

int func_a(){
#ifdef SECONDHEADER
printf ("I am function A.\n");
#endif
}

/*** BeginHeader func_b */

int func_b();
#define SECONDHEADER

/*** EndHeader */

#ifdef XYZ
#define FUNCTION_B
#endif

int func_b() {
#ifdef FUNCTION_B
printf ("I am function B.\n");
#endif
}

Let’s say the above file is named mylibrary.lib. If an application has the statement #use “mylibrary.lib” and then calls func_b(), will the printf statement be reached? The answer is no. The order of compilation for module headers is sequential from the beginning of the file, therefore, the macro SECONDHEADER is undefined when the first module header is parsed.

If an application #uses this library and then makes a call to func_a(), will that function’s print state­ment be reached? The answer is yes. Since all the headers were compiled first, the macro SECONDHEADER is defined when the first module body is compiled.

4.10.3  Important Notes

Remember that in a Dynamic C application there is only one file that contains main(). All other source files used by the file that contains main() are regarded as library files. Each library must be included in a  LIB.DIR (or a user defined replacement for it). Although Dynamic C uses .LIB as the library extension, you may use anything you like as long as the complete path is entered in your LIB.DIR file.

There is no way to define file scope variables in Dynamic C libraries.

4.11 Function Description Headers

Each user-callable function in a Dynamic C library has a descriptive header preceding the function to describe the function. Function headers are extracted by Dynamic C to provide on-line help messages.

The header is a specially formatted comment, such as the following example.

/* START FUNCTION DESCRIPTION **********************
WrIOport                      <IO.LIB>
SYNTAX: void WrIOport(int portaddr, int value);
DESCRIPTION:
Writes data to the specified I/O port.
PARAMETER1:  portaddr - register address of the port.
PARAMETER2:  value - data to be written to the port.

RETURN VALUE:  None
KEY WORDS: parallel port

SEE ALSO:  RdIOport
END DESCRIPTION ***********************************/

If this format is followed, user-created library functions will show up in the Function Lookup <Ctrl+H> feature if the library is listed in lib.dir or its replacement. Note that these sections are scanned in when Dynamic C starts and changed libraries are rescanned with every Ctrl+H.

4.12 Support Files

Dynamic C has several support files that are necessary in building an application. These files are listed below.

 

Table 4-3.  Dynamic C Support Files

File Name

Purpose of File

DCW.CFG

Contains configuration data for the target controller.

DC.HH

Contains prototypes, basic type definitions, #define, and default modes for Dynamic C. This file can be modified by the programmer.

DEFAULT.H

Contains a set of #use directives for each control product that Rabbit ships. This file can be modified.

LIB.DIR

Contains pathnames for all libraries that are to be known to Dynamic C. The programmer can add to, or remove libraries from this list. The factory default is for this file to contain all the libraries on the Dynamic C distribution disk. Any library that is to be used in a Dynamic C program must be listed in the file LIB.DIR, or another *.DIR file specified by the user.

PROJECT.DCP
DEFAULT.DCP

These files hold the default compilation environment that is shipped from the factory. DEFAULT.DCP may be modified, but not PROJECT.DCP. See Chapter 18 for details on project files.

 

1.   Technically, a const-modified variable is no longer "variable" but most C documentation on the subject does not make this distinction.