You are on page 1of 29

22

C and Assembly
Introduction to Assembly Assembly Language Applications A Few Examples Looping and Comparison Shifting Bits Some More Assembly Programming the SVGA Checking for VESA Compatible Card Back to init( ) Getting Mode Information Changing the Display Mode Memory Banks and Bank Switch Routine Back to main( ) Drawing Pixel High Color and True Color SVGA Modes Exercise

185

186

Let Us C

hy a chapter on Assembly language in a C programming book? Two good reasons for this. For one assembly language instructions speed up applications by permitting direct access to hardware (ex: writing directly to I/O ports instead of doing a system call). For another at times there would be no choice but to use Assembly to accomplish certain operations that C language does not permit. Additionally, if you know assembly it would help you to learn how high-level language code gets translated into machine language, how the computers hardware works, how the processor works, and how the data and instructions are internally represented.

Introduction to Assembly
Not only does the assembly language code works faster than the C code, it ends up being even more compact than the equivalent C language code. Tall claims both! But can we verify them? Yes, by all means. Take a look at the following code.
main( ) { }

As you can guess, on execution this program doesnt do anything. On compiling this program using the Turbo C/C++ compiler an EXE file of size 6,040 bytes gets generated. Other C compilers would also produce EXE files of more or less the same size. Here is an assembly language program that does nothing.
.model small .stack .code

Chapter 22: C and Assembly


.exit end

187

On assembling (equivalent to compiling) this program it generates an EXE file of size 516. As of now you can ignore the directives like .model small that are present in the assembly language program. Crux is that equivalent assembly language program produces a smaller EXE file. You may be wondering why this happens. Reason is simple. When you are using assembly you can give precise instructions to the processor as to what it should do. Unlike this since a C compiler is a generic program, when it compiles a C program it emits generic instructions for the processor. For example, when we make a function call the compiler emits instructions to save the state of all Data registers (AX, BX, CX, DX) on the stack. Unlike this in an equivalent assembly language program the programmer can give specific instructions to push the value of only specific registers to the stack. Let us now see verify whether the assembly language code is more compact than the equivalent C language code. Consider the following code.
int i, j = 5 ; for ( i = 0 ; i <= 10 ; i++ ) j=j+1;

We can assemble this code using the following command at the command prompt
TCC S sample.c

This will generate a file sample.asm. It contains the following code.

188
ifndef ??version ?debug macro endm $comm macro name,dist,size,count comm dist name:BYTE:count*size endm else $comm macro name,dist,size,count comm dist name[size]:BYTE:count endm endif ?debug S "..\try.c" ?debug C E9EA44B22C082E2E5C7472792E63 _TEXT segment byte public 'CODE' _TEXT ends DGROUP group _DATA,_BSS assume cs:_TEXT,ds:DGROUP _DATA segment word public 'DATA' d@ label byte d@w label word _DATA ends _BSS segment word public 'BSS' b@ label byte b@w label word _BSS ends _TEXT segment byte public 'CODE' assume cs:_TEXT _main procnear push bp movbp,sp push si push di mov di,5 mov si,1 jmp short @1@114 @1@58: mov ax,di

Let Us C

Chapter 22: C and Assembly


inc ax mov di,ax inc si @1@114: cmp si,10 jle short @1@58 pop di pop si pop bp ret _main endp ?debug C E9 _TEXT ends _DATA segment word public 'DATA' s@ label byte _DATA ends _TEXT segment byte public 'CODE' _TEXT ends public _main _s@ equ s@ end

189

In actuality the sample.asm file contains C language instructions followed by their equivalent assembly instructions. For sake of clarity we have deleted the C language instructions Do not pay attention to the directives and macros used towards the beginning and end of the program. Our intention here is to merely compare the sizes of an assembly program generated through TCC and the one written directly in assembly. Here is the second variety.
.model small .data j db 5 .stack .code mov ax, @data mov ds, ax

190
mov cx, 1 startloop: inc j inc cx cmp cx, 10 jle startloop .exit end

Let Us C

Both programs run a loop that increments the value of j. However, the second program that we ourselves wrote in Assembly turns out to be more compact as compared to the one generated by TCC. This is exemplified through the two jump instructions used in TCC generated code (jmp and jle) as compared to the jle that we used in our code. In a bigger program the compaction factor can be appreciated more. You can try writing nested for loops and then do the comparison between two Assembly codes. A question might cross your mind as to why at all should one generate the Assembly equivalent of the C code using TCC. Instead you would prefer to straightaway generate the EXE file. If you observe carefully, even when you generate an EXE file an intermediate file with extension .OBJ also gets generated. This file is the machine language. Each instruction in it matches exactly one assembly language instruction. Instead of emitting the Assembly instructions, the compiler emits their corresponding machine language instructions. However, the OBJ file cannot be executed directly because of two reasons: (a) The code for standard library functions still remains to be linked. (b) Some more headers have to be added to the object code to make it executable under the OS in which the program has to be executed. For example, the header to be added for running under DOS would be different than the header to be added for running the same code under Windows

Chapter 22: C and Assembly

191

Assembly Language Applications


Application programs are rarely written completely in assembly language. Instead, only time-critical parts are written in Assembly. For example, an interface subroutine for direct hardware access can be written in Assembly. Assembly is also used for writing Device Driver programs and in Embedded systems programs stored in PROM chips, Micro-controllers (automobiles, industrial plants, etc.), telecommunication equipment, etc. Ex2: device drivers (called from the OS). The plus side of Assembly is speed, compactness and close association with hardware. The down side the steep learning curve and the processor-specific non-portable code. SO instead of replacing the complete C program with an equivalent Assembly program, it would make more sense to embed the Assembly statements inside a C program. Such embedded statements are known as inline assembly statements. We would adopt this strategy throughout the rest of the chapter.

A Few Examples
You can appreciate the usage of inline Assembly in two cases: (a) When you want to perform a specific task for which there are no equivalent C language instructions. For example, we want that while performing a specific job no interrupts should occur. For this job there is no C language instruction, hence to get it done we have no recourse but to use inline Assembly.

192

Let Us C

(b) When we want to speed up a specific task like drawing a pixel on the screen. We intend to write programs that demonstrate both these usages. But before that we need to get comfortable with inline Assembly instructions. Hence we would first write simple programs that show usage of inline Assembly. Here is the first one
#include <conio.h> void main( ) { int result ; clrscr( ) ; asm mov ax, 5 asm mov bx, 7 asm add ax, bx asm mov result, ax printf ( "addition result = %d\n", result ) ; asm mov ax, 10 asm mov bx, 4 asm sub ax, bx asm mov result, ax printf ( "Subtraction result = %d\n", result ) ; }

Let us understand this program. The statement mov ax, 5 stores (moves) the constant 5 into the AX register of the microprocessor. Similarly, the next statement stores 7 into the BX register. The statement add ax, bx instructs the processor to add the contents of the BX register with that of the AX register and place the result back in the AX register. To be able to print the result on the screen we have moved the result from the AX register to an integer variable result On similar lines the statement sub ax, bx instructs the processor to subtract the value contained in BX register from the value

Chapter 22: C and Assembly

193

contained in the AX register and place the result back in the AX register. Once again the result of the subtraction is moved from the AX register to the result variable and printed on the screen using printf( ). The statements add, sub, mov are often called mnemonics (abbreviations) and are used to represent an equivalent machine language instruction. Note the usage of the asm keyword before every Assembly statement. This keyword tells the compiler that the statement that follows it is an assembly language statement. This method of mixing Assembly code in a C language program is known as inline Assembly. On compilation of the above program the object code would get generated for the C language statements. Wherever there is Assembly code the machine language instructions equivalent to it are placed. Every inline Assembly statement consists of a mnemonic followed by one or more operands. For example, in the statement
sub ax, bx

sub is the mnemonic that represents actual machine language instruction, whereas, ax and bx are operands on which the preceding mnemonic operates. Some of the instructions require no operands. Let us now write another program. This one demonstrates the usage of mul keyword and the word ptr modifier.
#include <conio.h> void main( ) { unsigned char c = 5 ; int num = 6 ; long result = 0 ; clrscr( ) ;

194
asm mov al, 7 asm mul c printf ( "8 bit mul result = %d\n", _AX ) ; asm mov ax, 32767 asm mul num asm mov word ptr result + 2, dx asm mov word ptr result, ax printf ( "16 bit mul result = %lu\n", result ) ; }

Let Us C

Let us see how multiplication can be achieved. We can perform either 8-bit multiplication or 16-bit multiplication. This is decided by the size of the operand for the mul instruction. For 8-bit multiplication the first number should be placed in the AL register. This has been accomplished by the instruction asm mov al, 7. In place of 7 if we use a char (8-bit) variable then its value would be stored in AL. The statement mul c specifies that the contents of the variable c are to be multiplied with the contents of the AL register. The 16-bit result becomes available in the AX register. This result has been printed through printf( ). We have accessed the AX register by using the _AX variable provided by the Turbo C compiler. For 16-bit multiplication the first number is in the AX register The second number is specified in the mul instruction. The 32-bit result is available in the DX:AX pair, i.e. the high word of the 32bit result is available in the DX register and the low word is available in the AX register. To be able to print the result we have to combine the contents of these 2 registers into a single 32-bit long variable. This is achieved by using a pair of mov statements. We have first moved the contents of the DX register to location result + 2 (high word within the 4 bytes) and the contents of AX to result (low word). The word ptr modifier is similar to the typecast feature of the C language. If we do not use this modifier the C compiler complains that the result variable is 32-bit long whereas

Chapter 22: C and Assembly

195

the AX / DX registers are 16-bit long so the storage cannot take place. Once the result is obtained it is printed by the printf( ) function. Of the arithmetic operations what remains to be demonstrated is division. Here is a program to achieve it.
/* Demonstrates division */ #include <conio.h> void main( ) { unsigned char divisor = 5 ; unsigned char q, r ; clrscr( ) ; asm mov ax, 51 asm div divisor asm mov q, al asm mov r, ah printf ( "Quotient = %u Remainder = %u", q, r ) ; }

Division is achieved by the div instruction. The 16-bit dividend is placed into the AX register. The divisor is specified as an operand of the div instruction. Upon division the quotient is available in the AL register and the remainder is available in the AH register. To keep things simple we have moved a constant value 51 to the AX register. The next statement div divisor specifies the actual instruction for division. The operand in this case happens to be a 8-bit variable. The quotient and remainder are moved from the registers to 8-bit variables to be able to print them easily using printf( ).

Looping and Comparison

196

Let Us C

With arithmetic operations out of the way let us now see how we can implement the loop and the decision. Go through the following program carefully. I t contains several new instructions like int , inc, dec, jnz , cmp.
#include <stdio.h> #include <conio.h> void main( ) { clrscr( ) ; asm mov cl, 5 loop1 : asm mov dl, cl asm add dl, 48 asm mov ah, 2 asm int 21h asm dec cl asm jnz loop1 printf ( "\n" ) ; asm mov cl, 1 loop2 : asm mov ah, 2 asm mov dl, cl asm add dl, 48 asm int 21h asm inc cl asm cmp cl, 6 asm jnz loop2 }

/* initial value of loop counter */ /* put counter in dl */ /* convert number to ascii equivalent */ /* function number for character output */ /* generate interrupt to print character */ /* decrement counter */ /* still non-zero jump to loop */

Usually the CL register is used as a counter for looping. Here we have initialized the CL register to the value 5 by using the mov instruction. To be able to print characters on the screen we have used the MS-DOS interrupt 21h, Service 2. The ASCII value of the character to be printed must be specified in the DL register.

Chapter 22: C and Assembly

197

Since we want to print the value of the counter held inside the CL register we have moved the contents of the CL register to the DL register. To obtain the ASCII value of the counter we have added the number 48 to the DL register since the ASCII value of 0 is 48. Next we have issued an int instruction. The only operand this instruction takes is the interrupt number, which we have specified as 21h. Doing this for the first time would print 5 on the screen because of the initial value 5 in the CL register. We want to print the numbers from 5 to 1. So the next statement dec cl decrements the value in the CL register. If the count falls to 0 a bit called zero-bit in the flags register is changed to one. We have checked this zero-bit in the flags register by using the jnz instruction. The j in the jnz loop1 instruction stands for jump, and the complete instruction means that the control should be taken to the label loop1 if the zero-bit in the flags register is set to nonzero. Once the control reaches the label loop1 the instructions below it get executed once more, thereby forming a loop. Note that labels mark the places in the program to which other instructions and directives refer. To print numbers in increasing order we have initialized CL register to 1. The same set of statements is used to print the value of the counter in the CL register. To increment the value of the counter we have used the inc cl instruction which increases the value of CL register by 1. (Note that we could also have used add cl, 1 but inc instruction works much faster than the add instruction). Next we have to check whether the printing of numbers is over or not. For this we have compared the value of CL register with 6 using cmp instruction. This instruction compares two operands. If the operands are different then the zero-bit in the flags register is set to 1. We have checked this flag using the jnz instruction as above to jump back to the label loop2.

Shifting Bits

198

Let Us C

The speed of integer multiplication and division can be increased by shifting bits instead of using the mul and div instructions. The following program demonstrates the usage of shr and shl instructions for shifting bits.
#include <conio.h> void main( ) { int num = 5 ; int r ; asm mov ax, num asm shl ax, 1 asm mov r, ax printf ( "result = %d\n", r ) ; num = 20 ; asm mov ax, num asm shr ax, 2 asm mov r, ax printf ( "result = %d\n", r ) ; }

The integer variable num having an initial value 5 is moved into the AX register. Next the bits of the AX register are shifted to left by 1 position through the instruction shl ax, 1. This would effectively multiply the contents of the AX register (5) by 2. The result 10 is moved to a variable r and then printed on the screen. Next the num variable is initialized to 20. This value is then moved to the AX register. Next we have shifted the contents of the AX register to the right by 2 using the shr ax, 2 instruction. This is equivalent to division by 4. The result is once again moved to the variable r and then printed on the screen using printf( ). Note that shifting the register contents by more than 1 position requires 80286 instruction set. To be able to use this instruction set make a setting in Turbo C by selecting the menu option Options |

Chapter 22: C and Assembly

199

Compiler | Advanced Code Generation and then changing the instruction set to 80286.

Some More Assembly


Shifting of bits is not the only operation that you may want to do on bits. At times you may want to turn them on or off. This is achieved using the instructions and, or and xor. Also, we would like to know how to push values in the register on the stack and how to pop them back. The following program demonstrates all this while turning off the Caps-Lock and Num-Lock.
#include <conio.h> void main( ) { clrscr( ) ; asm push ds asm mov ax, 40h asm mov ds, ax asm mov al, [ 17h ] asm and al, 9Fh asm mov [ 17h ], al asm pop ds }

As we know, under DOS the status of Caps-Lock, Num-Lock and Scroll-Lock toggle keys is kept at the address 40:17. This status is stored in the form of on/off flags. To be able to address the data beginning at segment address 40h we have to change the segment address in the DS register to 40h. But before we do that we have to preserve the current value of DS register. This is especially important if the code that follows accesses variables in the program. To achieve this we have pushed the contents of the DS register onto the stack. Next we have moved the value 40h to the AX register. Then the contents of the AX register are moved to the DS register. Effectively we have stored 40h in DS register.

200

Let Us C

However, we have to do this in two steps because moving a constant value directly to a segment register is not permitted. Next we have collected the byte at 40:17 in the AL register. This has been accomplished by mov al, [ 17h ]. The brackets indicate that the value present at address 17h, and not the value 17h itself, should be moved to AL register. Next to turn off the Caps-Lock and Num-Lock keys without affecting the status of other toggle keys we have to mask off the 2 bits representing Caps-Lock and Num-Lock to zero. This is done by ANDing the contents of AL register with the value 9Fh. This ANDing has been accomplished by the use of and instruction. This instruction takes two operands the AL register and the bit mask, 9Fh. The modified value is moved back to the memory location 40:17. Note the reversed positions of operands while reading and writing. The first operand of mov instruction is the always destination, whereas, the second operand is always the source. Before exiting we pop the value of the DS segment register from the stack.

Programming the SVGA


As emphasized earlier, Assembly is useful in following situations: (a) Increasing speed of critical operations (b) Performing operations that cannot be done through C (c) Make the code compact All these three facets of using Assembly would be demonstrated in the following program. It displays several rectangles drawn in 640 x 480 resolution, 256-color SVGA mode. Here is the program
#include <stdio.h> #include <conio.h> typedef struct { unsigned short modeattributes ;

Chapter 22: C and Assembly


unsigned char unused1[10] ; void ( far *switchbank )( ) ; /* pointer to bank switch function */ unsigned char unused2[240] ; } modeinfo ; void ( far * switchbank )( ) ; int bank ; /* bank number */ int getvesainfo( ) { unsigned char vesainfo[256] ; asm mov ax, 04F00h asm les di, vesainfo /* ES:DI -> vesainfo */ asm int 10h /* call interrupt and get vesainfo */ asm cmp al, 4Fh /* if AX is 0x4f then vesa is detected */ asm jz done return 0 ; done: return 1 ; }

201

int getvesamdeinfo ( int mode, modeinfo far *mi ) { asm mov ax, 4F01h asm mov cx, mode asm les di, mi asm int 10h return ( mi -> modeattributes & 1 ) ; /*return 1 if successful, else 0 */ } void setvesamode ( int mode ) { asm mov ax, 4F02h asm mov bx, mode asm int 10h } void init ( void )

202
{ modeInfo mi ; if ( ! getvesainfo( ) ) { printf ( "No VESA compatible card detectecd" ) ; exit ( 1 ) ; } if ( ! getvesamdeinfo ( 0x101, &mi ) ) { printf ( "Mode not supported" ) ; exit ( 2 ) ; } switchbank = mi.switchbank ; setvesamode ( 0x101 ) ; }

Let Us C

/* The adress is calculated with 640 * y + x a mul works great because it puts the high 16 bits of a mul in dx and the low 16 bits in ax after the mul, dx will contain the bank number and ax will be the offset into that bank */ void putpixel256 ( int x, int y, char col ) { int off ; asm mov ax, 640 asm mov bx, y asm mul bx asm add ax, x asm jnc noc asm inc dx noc : asm mov off, ax asm cmp dx, bank /* screen width */ /* ax = ( 640 * y ) % 65535, dx = bank */ /* ax = ( 640 * y + x ) % 65535 */ /* if the add goes into a new bank (carry set) */ /* then bank = bank + 1 */ /* mov ax (offset into bank) to off */ /* compare this pixel's bank to the current bank*/

Chapter 22: C and Assembly


asm jz same asm mov bank, dx asm xor bx, bx switchbank( ) ; /* if it's the same, don't set the bank*/ /* else update bank*/ /* call the bankswitch routine (got it from getVesaModeInfo)*/

203

same : asm push di /* save di or else the program can crash */ asm mov ax, 0A000h /* and put the pixel into a000:offset */ asm mov es, ax asm mov di, off asm mov al, col asm mov [es:di], al asm pop di /* restore di */ } void setmode ( int mode ) { asm mov ax, mode asm int 10h } void main ( void ) { int x, y ; int color, xoff = 0, yoff = 0 ; init( ) ; for ( color = 0 ; color < 256 ; color++ ) { for ( y = 0 ; y < 32 ; y++ ) { for ( x = 0 ; x < 32 ; x++ ) putpixel256 ( xoff + x, yoff + y, color ) ; } xoff += 32 ;

204
if ( xoff >= 639 ) { xoff = 0 ; yoff += 32 ; } } getch( ) ; SetMode ( 3 ) ; }

Let Us C

The program begins by calling a user-defined function init( ). This function checks to see whether the graphics card is VESAcompatible or not by calling a user-defined function getvesainfo( ).

Checking for VESA Compatible Card


int getvesainfo( ) { unsigned char vesainfo [256] ; asm mov ax, 4F00h asm les di, vesainfo /* es:di -> vesainfo */ asm int 10h /* call interrupt and get vesainfo */ asm cmp al, 4Fh /* if ax is 0x4f then vesa is detected */ asm jz done return 0 ; done: return 1 ; }

This function makes use of inline assembly to call interrupt 10h service 4Fh. This service is the VESA BIOS extensions for working with an SVGA card. We have stored a value of 4Fh in the AH register and a value of 0 in the AL register by using mov ax, 4F00h. The value 0 in AL specifies one of the various sub-services of VESA extensions. Sub-service 0 is used to obtain information

Chapter 22: C and Assembly

205

about the VESA-compatible graphics card. The information returned is of 256 bytes. So a buffer vesainfo[256] is created and the address of this buffer is supplied in the ES:DI register pair. That is, the segment address of the buffer is stored in the ES register and the offset address of the buffer is stored in the DI register. This is achieved by using the les (Load Extra Segment) instruction. This instruction takes two operands the first specifies the offset register and the second specifies the address of the buffer. Next interrupt 16 is issued by using the int instruction. If the function 4Fh is supported (confirming that the card is VESA-complaint), then the AL register contains a value of 4Fh. We have checked this by comparing the value of AL register with 4Fh using the cmp instruction. If the value is same the zero-bit in the flags register is set. We have used the jz instruction to jump to the done label if the zero-bit in the flags register is set and then returned 1 to indicate success. If the functions 4Fh is not supported a value 0 is returned indicating failure. Note that the actual information returned is a structure. Since we are not interested in the breakup of this information we have defined the buffer as an array.

Back to init( )
The value returned by getvesainfo( ) is checked in the init( ) function. If the value returned is zero then we have flashed an error message about the absence of a VESA-compatible graphics card and ended the program execution using the exit( ) standard library function.

Getting Mode Information


Next we have collected information about a particular display mode 101h ( Resolution 640 x 480 in 256 colors) by calling a

206

Let Us C

user-defined function getvesamodeinfo( ). This function accepts two parameters. The first is the display mode number and the second is the address of the structure variable in which the returned information would be stored. This structure is defined as follows:
typedef struct { unsigned short modeattributes ; unsigned char unused1[10] ; void ( far *switchbank )( ) ; /* pointer to bank switch function */ unsigned char unused2[240] ; } modeinfo ;

A lot of information regarding the mode is returned. But since we require only a portion of this information later, other members of the structure have been combined into two arrays unused1[ ] and unused2[ ].
int getvesamdeinfo ( int mode, modeinfo far *mi ) { asm mov ax, 4F01h asm mov cx, mode asm les di, mi asm int 10h return ( mi -> modeattributes & 1 ) ; /*return 1 if successful, else 0 */ }

The VESA service for obtaining information about a particular display mode is 1. This number is stored in AL register. The mode numbers goes into the CX register, and address of the structure variable goes into the ES:DI pair. The least significant bit of the first byte of the buffer tells whether the service was successful or not. We have checked whether the zeroth bit is on or off and returned the appropriate value.

Chapter 22: C and Assembly

207

Back into init( ) we have flashed an error message and terminated the program if the mode isnt supported. On the other hand, if the mode is supported then we have collected information about the address of the bank switch routine (explained later) in a global variable switchbank. This has been done for easy access to bank switch routine later on. Next we have changed the display mode to 101h by calling a user-defined function setvesamode( ).

Changing the Display Mode


void setvesamode ( int mode ) { asm mov ax, 4f02h asm mov bx, mode asm int 10h }

The function makes use of VESA service number 4Fh, sub-service number 2 to change the display mode. The mode number has to be supplied in the BX register.

Memory Banks and Bank Switch Routine


Although the graphics cards has several MB of memory, the PC while working under real mode lets you write to only 64 KB of graphics memory, starting at segment address A000h. This causes a problem for graphics modes that require more than 64 KB of memory for display. This problem is solved using a concept called bank switching. In this the video memory is split into several banks of 64 KB each. To access the video memory you first need to tell the graphics card which bank you want to access. For doing this VESA BIOS provides a service. This service ultimately ends up calling the bank switch function. But we have already obtained the address of this function in getvesamodeinfo( ). Hence instead

208

Let Us C

of issuing an interrupt we have straightaway called the bank switch function, as we would see later.

Back to main( )
To display all the 256 colors we have drawn filled squares of 32 by 32 pixels. The filled squares are drawn using nested for loops and repeatedly calling a user-defined function putpixel256( ) to draw a pixel on the screen.

Drawing Pixel
The putpixel256( ) function accepts 3 parametersthe x coordinate, the y co-ordinate and the color of the pixel. The formulae for plotting a pixel are as follows:
offset = ( 640 * y + x ) % 65535 ; bank = ( 640 * y + x ) / 65535 ;

We can avoid using these formulae and instead take advantage of the way multiplication results are returned in Assembly. This would improve the performance of the putpixel256( ) function. Since this function is called several times, the performance of the program is also bound to improve. The routine firstly multiplies the value 640 by y. The result is placed into the DX:AX pair. Since the AX register is 16-bits in size, if the multiplication result exceeds 65535, then AX would hold result % 65535, whereas DX would hold result / 65535. Since each bank is of 64 KB, effectively DX holds the bank number, whereas AX holds the offset into the bank. Now we add the x to the AX register. If the result of addition turns out to be greater than 65535, then the carry-bit in the flags register gets set. If this so happens then we have incremented the bank number in the DX register by 1. Additionally, we have also copies the offset value present in the AX register to a local variable off for later use.

Chapter 22: C and Assembly

209

In 256-color mode each pixel occupies 8 bits. Hence once into a particular bank the bank number would not change for further 65536 / 8, i.e. 8192 pixels. We can exploit this fact by avoiding the calling of bank switch routine till the bank number does not change. The bank number computed (present in DX register) is compared with a global variable bank. If the values are different then the bank number in bank is updated from the value in DX register. Next, the BX register is cleared (set to zero) by using the xor bx, bx instruction and then the bank switch routine is called. This routine switches the bank. On doing so the new bank would be mapped into the A block starting at address A0000h. To be able to write the color information of the pixel to the A block we have to make the ES:DI pair point to the appropriate memory location. That is ES should hold the segment address A000h and DI should hold the offset within the current bank. So the value A000h is moved to the ES register via AX. The offset value is loaded into the DI register from the off variable. The color is first stored in the AL register and then moved to the memory location pointed to by ES:DI. As soon as this is done the pixel with its color gets displayed on the screen. We have preserved the value of DI register and later restored it because routines like bank switch routine do not expect it to change. This is not true about general-purpose registers like AX, BX, CX and DX. Hence we have not bothered to preserver their values. Lastly, from main we have called getch( ) standard library function to wait for a key press and then switched back to text mode by using the user-defined setmode( ) function.
void setmode ( int mode ) { asm mov ax, mode asm int 10h }

210

Let Us C

This function is responsible for switching to text mode. The AH register should contain service number 0, the AL register should contain the mode number (3 in our case). We have moved the mode number directly to the AX register, instead of AL register. As the mode number is less than 255 the AH register will automatically be set to 0. Next we have issued the interrupt 10h, which restores the display mode to text.

High Color and True Color SVGA Modes


In addition to the 256-color mode, SVGA also supports a high color (65536 colors) and a true color (16777216 colors) mode. Visit www.funducode.com for downloading the source code of the programs that show how to display pixels in these two modes.

Exercise
[A] State True or False:

(a) The method of mixing Assembly code in a C language program is known as inline Assembly. (b) A program written in assembly is always smaller than its C equivalent. (c) Some instructions in assembly have no counterparts in the C language. (d) Moving a value directly to a segment register is permitted. (e) The zero-bit in the flags register is set to 1 if the result is nonzero. (f) A 16-bit mul instruction places the result in AX:DX pair. (g) Application programs are rarely written completely in Assembly language. (h) Any offset register can be combined with any segment register to reference a memory location.

Chapter 22: C and Assembly


[B] Pick up the correct alternative for each of the following:

211

(a) Assembly code is mixed with C code to (1) Increase the speed for critical routines (2) Work closely with hardware (3) Both 1 and 2 (4) Only 1 (b) The instruction sub ax, bx places the result into (1) AX register (2) BX register (3) Depends upon flag register setting (4) Memory (c) The instruction mov [bx], ax moves the contents of (1) AX into BX (2) BX into AX (3) AX into DS:BX (4) None of the above (d) The video memory in a graphics card is divided into banks of (1) 64 KB (2) 64 bytes (3) 16 KB (4) None of the above (e) If the DS register contains 40h and SI register contains 17h then the DS:SI pair referes to memory location (1) 417h (2) 4017h (3) 40017h (4) None of the above

212

Let Us C

Chapter 22: C and Assembly

213