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.
|fixed-point fullword||int or long|
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":
Note that the character field is followed by "". 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. */
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:
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:
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. Convert the following dsect to a "C" structure.
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
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