MOVING FROM ASSEMBLER TO "C": Part 2
by Jeff Robinson

Prerequisites: Completion of Part 1 in the last issue (including the exercises).

Environment: this course addresses the use of "C" in the ISO-C TPF environment.

In our last installment, we discussed the basic building block for "C" programming -- functions. In this issue, we'll take a look at how "C" handles data and look at the basic "C" building block for data manipulation.

DEFINING DATA IN "C"
In TPF, whenever you need work space for manipulating data, you either use the EBW or EBX work areas, use some predefined application work space, or allocate your own work area by getting a core block. In ISO-C, this task has been virtually eliminated because each "C" function is automatically given access to its own "auto-storage" area when it executes. This means that all a programmer has left to do is to define the data elements that he wants to use.

In Assembler when you defined data elements, you used the "DC" statement to declare constant data or "DS" to declare named storage areas. Thus, if you wanted to reserve two half-words for manipulating numbers and 32 bytes for a character string you would code:

    VALUE1 DS H

    VALUE2 DS H

    TITLE    DS CL32

In the preceding lines, the "H" and "C" fields in the third column are called type identifiers because they indicate how the data in named storage areas will be used. The "C" language provides equivalencies for most of the Assembler type codes you're familiar with. The table below lists some "C" equivalents to Assembler type codes.

Assembler Type

Usage

C Equivalent

C

character strings char

H

fixed-point halfword short

F

fixed-point fullword int or long

D

floating-point double

To use the equivalent "C" data types, place the name of the type in front of the field name (or fields) followed by a semicolon. So, our previous Assembler statements could be rewritten like this in "C":

    char title[32];

    short value1,value2;

Note that the character field is followed by "[32]". This notation indicates that the title field actually takes up 32 bytes; such a definition is called an "array". "C" also allows you to define data types as "signed" or "unsigned"; thus if you want to indicate that "value1" and "value2" can hold both positive and negative numbers, you would placed the "signed" data attribute in front of the declaration:

    signed short value1,value2;

On the other hand, if you wanted to indicated that "value1" and "value2" should only hold non-negative values, you would use the "unsigned" attribute with the data declaration like this:

    unsigned short value1,value2;

(For further discussion on signed/unsigned usage, consult a "C" instruction manual.) If you had wanted to reserve a fullword to hold the results of adding the "value1" to "value2", you would have declared it like this:

    unsigned int results;

In assembler, when you wanted to define a binary field for use in bitwise operations you did this:

    SWITCH DS BL2

In "C", you would use the "unsigned int" data declaration and follow the named field you chose with a colon and the number 16 to indicate that the field represented a 16-bit binary field -- the same as BL2. (The number is used to indicate the number of actual bits that the field name reserves for usage.)

    unsigned int switch:16;

"C" also allows you to declare the equivalency of a "DC" instruction; to do this, simply initialize a "C" field name when you declare. The following is an example:

    unsigned int value1=0,value2=0;

Any valid fullword number could have been used in place of the zeros above. Also, in "C", all of the named fields used in our example so far are called "variables". This is because the data being held in those named storage areas are subject to change. If you wanted to indicate that a field could not to be changed you could insert the "const" data attribute in front of it like this:

    const short block_size=4096;

If you declared a field in the preceding manner, any attempts to use it in statements where it would be modified at program execution would result in a compiler error.

MANIPULATING DATA IN "C"
Back in Assembler, when you wanted to do arithmetic operations, you generally had to load your data into one or more registers unless you were using "packed" data fields. This often meant that you had to code several extra statements before you could do the actual target arithmetic operation. "C" offers a higher level of control when it comes time to manipulate your data. In fact, all you have to do is code the use of the target operation and let the compiler find the registers to use and generate the required machine code under the covers. "C" provides "operators" as the means to get this done. An operator is a symbol that indicates that some arithmetic operation is to occur in a "C" statement.

Now, this course could not possibly introduce all of the "C" arithmetic operators; however, below we give examples using the most common ones. In the following examples, we'll use the variables declared in the preceding sections to demonstrate the usage of some "C" operators. Please read the associated comments along with each example for a description of how the operator is used.

Ex. 1: Add two variables and storing the sum in "result".

        result = value1 + value2; /* "+" is the C operator for adding */

Ex. 2: Subtract one variable from another.

        value1 = result - value2; /* "-" is the C operator for subtracting*/

Ex. 3: Multiply two variables together.

        result = value1 * value2; /* "*" is the C operator for multiplication*/

Ex. 4: Divide one variable by another and storing the quotient.

        result = value2 / value1; /* "/" is the C division operator */

Ex. 5: Divide one variable by another and storing the remainder.

        value1 = result % value21; /* "%" is the C remainder operator */

Ex. 6: Increment a variable by "1".

        value1++; /* "++" is a C unary operator used to add 1*/

        /* a variable. */

Ex. 7. Decrement a variable by "1".

        value1--; /* "--" is another C unary operator used */

        /* to subtract 1 from a variable. */

USING PARENTHESES
When you have multiple operations that you wish to include in one statement, "C" allows you to do so and to use parentheses to group together operations that should be done prior to other operations. To illustrate, lets' suppose you wanted to add two numbers and then divide the result by "2". You could code the following:

        result = value1 + value2 / 2; /*Add value1, value2; then divide by 2 */

However, in the statement above, "C" would use its own rules of precedence and determine the order that arithmetic operations should be executed in. In "C", the division operator is of a higher precedence than the addition so it would be done first. Thus "result" would reflect the division of "value2" by "2" and the addition of "value1" to the result! That's definitely not what you expected. To solve this, you would make use of parentheses to group together the operation that should be done first as in the following example:

        result = (value1 + value2) / 2; /*Add value1 to value2; then divide by 2*/

The use of the parentheses overrides the normal precedence rules "C" would follow. When in doubt to which precedence "C" will follow, use as many parentheses as necessary to produce the desired result. (See a "C" manual for further information on precedence rules.)

THE BASIC "C" BUILDING BLOCK FOR DATA
In TPF, we grouped together named storage declaration in "dsects" (or dummy control sections) that map the layout of storage without actually reserving the storage space. A typical dsect might look as follows:

MI0MI          DSECT

MI0BID       DS H

MI0CHK       DS X

MI0CTL        DS X

MI0PGM       DS F

MI0FCH        DS F

MI0BCH        DS F

MI0CCT    DS CL2

MI0ADR DS CL3

MI0ACC DS CL2

"C" provides the use of the structure data type which allows us to group together variable names without actually reserving storage just like dsects did. You declare a structure by using the "struct" keyword followed by a field name and a list of field names grouped within left and right parentheses all followed by a semicolon. Thus, "C" structure for the preceding dsect could look like the following:

        struct mi0mi
{
        short mi0bid;

        char mi0chk;

        char mi0ctl;

        char mi0pgm[4];

        int mi0fch;

        int mi0bch;

        char mi0cct[2];

        char mi0adr[3];

        char mi0acc[2];

};

You can code the declarations for "C" structures in a header file or within the body of your "C" programs. Like with Assembler, the "C" structure would map to a core block or other storage area and would be "based" accordingly. You can also reserve space for the "C" structure within your functions' auto-storage work area by declaring a variable which uses the previously defined structure as its type. The following is an example:

        struct mi0mi input_data;

ADDRESSES VS. POINTERS
You've probably heard with dread the topic of "pointers" in "C". A "pointer" in "C" is actually no more than what an address is in Assembler with the addition that "pointers" are also named storage areas. Just be aware that the data stored at a pointer is simply an address to another location. Thus, using a pointer variable is almost the same as using registers in Assembler (.i.e., registers hold addresses to other locations and so do pointer variables). Thus, in the last example, you could re-declare the "input_data" variable to be only a pointer to type struct mi0mi. This is done with the "*" operator to indicate that input_data or now a pointer(or address holder).

        struct mi0mi *input_data;

The above declaration means that input_data is declared as a variable that holds an address to a storage space mapped to struct mi0mi. If that's still confusing, simply think of "*input_data" as being a register. You could even rename the variable to remind you that it acts like a register in Assembler:

        struct mi0mi *input_data_register;

QUIZ - Part 2

  1. To define a fixed-point fullword in "C", use the "short" data type identifier followed by the name of the field. True or False?
  2. You must allocate your own core blocks before using variables in "C" programs. True or False?
  3. The basic data building block in "C" is the structure (keyword "struct"). True or False?
  4. The _________ data attribute is used to indicate that a variable's data will not change. Fill in the blank.
  5. If you want a variable to contain only non-negative values, use the _________ data attribute with the data type. Fill in the blank.
  6. A field name is called a variable when its data may change during program usage. True or False.
  7. Use ____________ to group arithmetic statements in order to override "C" precedence rules. Fill in the blank.
  8. The ____ and ____ are the two "C" unary operators used as examples in this article.
  9. A pointer is a variable which holds _________ to other areas of storage.
  10. The semicolon is not required at the end of data declaration statements. True or False?

EXERCISES

1. Convert the following dsect to a "C" structure.

CT0CT DSECT

CT0ACT    DS CL1        ACTION CODE

CT0LST    DS CL20 LAST NAME

CT0FST    DS CL10 FIRST NAME

CT0PHN    DS     CL12    PHONE NUMBER

CT0VL1    DS    H             VALUE #1

CT0VL2    DS    H             VALUE #2

CT0SP0    DS    CL40      SPARES

CSECT

2. Convert the following to a single "C" arithmetic statement.

        L    R5,CT0VL1 Load R5 with value #1

        AH    R5,CT0VL2 Add value #2 to R5

        STH    R5,CT0VL1 Save result into value #1

3. Declare a pointer variable named customer_data to the structure you created in Exercise #1.

Answers to the Quiz and Exercizes can be found at http://www.robisoft.com