Chapter 2 discussed the basic format for data in memory. Chapter 3 covered how a computer system physically organizes that data in memory. This chapter finishes the discussion by connecting the concept of data representation to its actual physical representation. As the title implies, this chapter concerns itself with three main topics: constants, variables, and data structures. This chapter does not assume that you've had a formal course in data structures, though such experience would be useful.
This chapter discusses how to declare and use constants, scalar variables, integers, data types, pointers, arrays, records/structures, unions, and namespaces. You must master these subjects before going on to the next chapter. Declaring and accessing arrays, in particular, seems to present a multitude of problems to beginning assembly language programmers. However, the rest of this text depends on your understanding of these data structures and their memory representation. Do not try to skim over this material with the expectation that you will pick it up as you need it later. You will need it right away, and trying to learn this material along with later material will only confuse you more.
This chapter introduces arrays and other concepts that will require the expansion of your 80x86 instruction set knowledge. In particular, you will need to learn how to multiply two values; hence the first instruction we will look at is the intmul
(integer multiply) instruction. Another common task when accessing arrays is to check to see if an array index is within bounds. The 80x86 bound
instruction provides a convenient way to check a register's value to see if it is within some range. Finally, the into
(interrupt on overflow) instruction provides a quick check for signed arithmetic overflow. Although into
isn't really necessary for array (or other data type) access, its function is very similar to bound
; hence the presentation of it at this point.
The intmul
instruction takes one of the following forms:
// The following computedestreg
=destreg
*constant
intmul(constant, destreg16
); intmul(constant, destreg32
); // The following computedest
=src
*constant
intmul(constant
,srcreg16
,destreg16
); intmul(constant
,srcmem16
,destreg16
); intmul(constant
,srcreg32
,destreg32
); intmul(constant
,srcmem32
,destreg32
); // The following computedest
=src
*constant
intmul(srcreg16
,destreg16
); intmul(srcmem16
,destreg16
); intmul(srcreg32
,destreg32
); intmul(srcmem32
,destreg32
);
Note that the syntax of the intmul
instruction is different from that of the add
and sub
instructions. In particular, the destination operand must be a register (add
and sub
both allow a memory operand as a destination). Also note that intmul
allows three operands when the first operand is a constant. Another important difference is that the intmul
instruction allows only 16-bit and 32-bit operands; it does not multiply 8-bit operands.
intmul computes the product of its specified operands and stores the result into the destination register. If an overflow occurs (which is always a signed overflow, because intmul
multiplies only signed integer values), then this instruction sets both the carry and overflow flags. intmul
leaves the other condition code flags undefined (so, for example, you cannot meaningfully check the sign flag or the zero flag after executing intmul
).
The bound
instruction checks a 16-bit or 32-bit register to see if it is between two values. If the value is outside this range, the program raises an exception and aborts. This instruction is particularly useful for checking to see if an array index is within a given range. The bound
instruction takes one of the following forms:
bound(reg16
,LBconstant
,UBconstant
); bound(reg32
,LBconstant
,UBconstant
); bound(reg16
,Mem16
[2] ); bound(reg32
,Mem32
[2] );
The bound
instruction compares its register operand against an unsigned lower bound value and an unsigned upper bound value to ensure that the register is in the range:
lower_bound
<=register
<=upper_bound
The form of the bound
instruction with three operands compares the register against the second and third parameters (the lower bound and upper bound, respectively).[47] The bound
instruction with two operands checks the register against one of the following ranges:
Mem16
[0] <=register16
<=Mem16
[2]Mem32
[0] <=register32
<=Mem32
[4]
If the specified register is not within the given range, then the 80x86 raises an exception. You can trap this exception using the HLA try..endtry
exception-handling statement. The excepts.hhf header file defines an exception, ex.BoundInstr
, specifically for this purpose. The program in Example 4-1 demonstrates how to use the bound
instruction to check some user input.
Example 4-1. Demonstration of the bound
instruction
program BoundDemo; #include( "stdlib.hhf" ); static InputValue:int32; GoodInput:boolean; begin BoundDemo; // Repeat until the user enters a good value: repeat // Assume the user enters a bad value. mov( false, GoodInput ); // Catch bad numeric input via the try..endtry statement. try stdout.put( "Enter an integer between 1 and 10: " ); stdin.flushInput(); stdin.geti32(); mov( eax, InputValue ); // Use the BOUND instruction to verify that the // value is in the range 1..10. bound( eax, 1, 10 ); // If we get to this point, the value was in the // range 1..10, so set the boolean GoodInput // flag to true so we can exit the loop. mov( true, GoodInput ); // Handle inputs that are not legal integers. exception( ex.ConversionError ) stdout.put( "Illegal numeric format, re-enter", nl ); // Handle integer inputs that don't fit into an int32. exception( ex.ValueOutOfRange ) stdout.put( "Value is *way* too big, re-enter", nl ); // Handle values outside the range 1..10 (BOUND instruction). exception( ex.BoundInstr ) stdout.put ( "Value was ", InputValue, ", it must be between 1 and 10, re-enter", nl ); endtry; until( GoodInput ); stdout.put( "The value you entered, ", InputValue, " is valid.", nl ); end BoundDemo;
The into
instruction, like bound
, also generates an exception under certain conditions. Specifically, into
generates an exception if the overflow flag is set. Normally, you would use into
immediately after a signed arithmetic operation (e.g., intmul
) to see if an overflow occurs. If the overflow flag is not set, the system ignores into
; however, if the overflow flag is set, then the into
instruction raises the ex.IntoInstr
exception. The program in Example 4-2 demonstrates the use of the into
instruction.
Example 4-2. Demonstration of the into
instruction
program INTOdemo; #include( "stdlib.hhf" ); static LOperand:int8; ResultOp:int8; begin INTOdemo; // The following try..endtry checks for bad numeric // input and handles the integer overflow check: try // Get the first of two operands: stdout.put( "Enter a small integer value (-128..+127):" ); stdin.geti8(); mov( al, LOperand ); // Get the second operand: stdout.put( "Enter a second small integer value (-128..+127):" ); stdin.geti8(); // Produce their sum and check for overflow: add( LOperand, al ); into(); // Display the sum: stdout.put( "The eight-bit sum is ", (type int8 al), nl ); // Handle bad input here: exception( ex.ConversionError ) stdout.put( "You entered illegal characters in the number", nl ); // Handle values that don't fit in a byte here: exception( ex.ValueOutOfRange ) stdout.put( "The value must be in the range −128..+127", nl ); // Handle integer overflow here: exception( ex.IntoInstr ) stdout.put ( "The sum of the two values is outside the range −128..+127", nl ); endtry; end INTOdemo;
[47] This form isn't a true 80x86 instruction. HLA converts this form of the bound instruction to the two-operand form by creating two readonly
memory variables initialized with the specified constants.