Consider the following expression from a Pascal program:
b := ((x = y) and (a <= c)) or ((z - a) <> 5);
b
is a boolean variable and the remaining variables are all integers.
How do we represent boolean variables in assembly language? Although it takes only a single bit to represent a boolean value, most assembly language programmers allocate a whole byte or word for this purpose (thus, HLA also allocates a whole byte for a boolean variable). With a byte, there are 256 possible values we can use to represent the two values true and false. So which two values (or which two sets of values) do we use to represent these boolean values? Because of the machine's architecture, it's much easier to test for conditions like zero or not zero and positive or negative rather than to test for one of two particular boolean values. Most programmers (and, indeed, some programming languages like C) choose 0 to represent false and anything else to represent true. Some people prefer to represent true and false with 1 and 0 (respectively) and not allow any other values. Others select all 1 bits ($FFFF_FFFF, $FFFF, or $FF) for true and 0 for false. You could also use a positive value for true and a negative value for false. All these mechanisms have their advantages and drawbacks.
Using only 0 and 1 to represent false and true offers two very big advantages: (1) The setcc instructions produce these results, so this scheme is compatible with those instructions; (2) the 80x86 logical instructions (and
, or
, xor
, and, to a lesser extent, not
) operate on these values exactly as you would expect. That is, if you have two boolean variables A
and B
, then the following instructions perform the basic logical operations on these two variables:
// c = a AND b; mov( a, al ); and( b, al ); mov( al, c ); // c = a OR b; mov( a, al ); or( b, al ); mov( al, c ); // c = a XOR b; mov( a, al ); xor( b, al ); mov( al, c ); // b = NOT a; mov( a, al ); // Note that the NOT instruction does not not( al ); // properly compute al = NOT al by itself. and( 1, al ); // I.e., (NOT 0) does not equal one. The AND mov( al, b ); // instruction corrects this problem. mov( a, al ); // Another way to do b = NOT a; xor( 1, al ); // Inverts bit 0. mov( al, b );
Note, as pointed out above, that the not instruction will not properly compute logical negation. The bitwise not
of 0 is $FF and the bitwise not
of 1 is $FE. Neither result is 0 or 1. However, by and
ing the result with 1 you get the proper result. Note that you can implement the not
operation more efficiently using the xor( 1, ax );
instruction because it affects only the L.O. bit.
As it turns out, using 0 for false and anything else for true has a lot of subtle advantages. Specifically, the test for true or false is often implicit in the execution of any logical instruction. However, this mechanism suffers from a very big disadvantage: You cannot use the 80x86 and
, or
, xor
, and not
instructions to implement the boolean operations of the same name. Consider the two values $55 and $AA. They're both nonzero so they both represent the value true. However, if you logically and
$55 and $AA together using the 80x86 and
instruction, the result is 0. True and
true should produce true, not false. Although you can account for situations like this, it usually requires a few extra instructions and is somewhat less efficient when computing boolean operations.
A system that uses nonzero values to represent true and 0 to represent false is an arithmetic logical system. A system that uses the two distinct values like 0 and 1 to represent false and true is called a boolean logical system, or simply a boolean system. You can use either system, as convenient. Consider again the boolean expression
b := ((x = y) and (a <= d)) or ((z - a) <> 5);
The simple expressions resulting from this expression might be:
mov( x, eax ); cmp( y, eax ); sete( al ); // al := x = y; mov( a, ebx ); cmp( ebx, d ); setle( bl ); // bl := a <= d; and( al, bl ); // bl := (x = y) and (a <= d); mov( z, eax ); sub( a, eax ); cmp( eax, 5 ); setne( al ); or( bl, al ); // al := ((x = y) and (a <= d)) or ((z - a) <> 5); mov( al, b );
When working with boolean expressions don't forget that you might be able to optimize your code by simplifying those boolean expressions. You can use algebraic transformations to help reduce the complexity of an expression. In the chapter on control structures, you'll also see how to use control flow to calculate a boolean result. This is generally quite a bit more efficient than using complete boolean evaluation as the examples in this section teach.