Part III • Working with Others
Listing 11.5. continued
; ; Line 55 |
{ |
|
|
|
; ; |
Line 56 |
|
return(nVar1); |
|
*** |
0000a7 |
8b |
46 04 |
mov |
ax,WORD PTR 4[bp] |
|
*** |
0000aa |
e9 |
00 00 |
jmp |
L00386 |
; ; |
Line 57 |
} |
|
|
|
L00388:
;; Line 58}
;Line 58 (end of the function, cleanup:)
|
|
L00386: |
|
*** 0000ad |
5f |
|
pop di |
*** 0000ae |
5e |
|
pop si |
*** 0000af |
8b |
e5 |
mov sp,bp |
*** 0000b1 |
5d |
|
pop bp |
*** 0000b2 |
c3 |
|
ret OFFSET 0 |
Local Size: 2
; Line 0
Next, Microsoft C’s /Ox (maximum optimize) switch was turned on so that you could see what a compiler can do using maximum optimization. The following output was produced:
|
|
|
_maximum: |
|
*** |
00007a |
55 |
push |
bp |
*** |
00007b |
8b |
ec |
mov bp,sp |
;nVar1 = 4
;nVar2 = 6
***00007d 8b 56 04 mov dx,WORD PTR [bp+4] ;nVar1
***000080 8b 5e 06 mov bx,WORD PTR [bp+6] ;nVar2
;|*** |
if (nVar1 < nVar2) |
|
; Line 50 |
|
|
|
|
*** 000083 |
3b |
da |
cmp bx,dx |
*** 000085 |
7e |
05 |
jle $I433 |
;|*** |
{ |
|
|
|
;|*** |
|
return(nVar2); |
|
; Line 52 |
|
|
|
*** 000087 |
8b |
c3 |
mov ax,bx |
*** 000089 |
5d |
|
pop bp |
*** |
00008a |
c3 |
|
ret |
*** |
00008b |
90 |
|
nop |
C and Other Languages |
C C C |
|
11C |
|
C C C |
|
C C |
;|*** |
} |
|
|
;|*** |
else |
|
|
; Line 54 |
|
|
|
|
|
$I433: |
|
;|*** |
{ |
|
|
;|*** |
|
return(nVar1); |
|
; Line 56 |
|
|
|
*** 00008c |
8b c2 |
mov ax,dx |
;|*** |
} |
|
|
;|*** } |
|
|
|
; Line 58 |
|
|
|
*** 00008e |
5d |
pop bp |
*** 00008f |
c3 |
ret |
_maximum ENDP _TEXT ENDS
END ;|***
;|*** // #endif
Notice that the optimized code is slightly smaller and a bit more complex. This code might be more difficult to debug, but this drawback is unimportant because code is seldom debugged at the machine-language level after optimization.
Calling Assembly from C
Can a programmer using assembly language write a smaller, faster function than the compiler? To test this, I wrote the maximum() function in assembly. So that I would not be biased by looking at what the compiler produced for optimized code, I wrote the assembly function before I produced the two listings of maximum() shown in the preceding section.
Listing 11.6 is my version of maximum(). It is a bit shorter and faster than the version from the compiler, even when the compiler version is fully optimized.
Listing 11.6. MAXIMUM.ASM.
;
;A hand optimized assembly function for C.
;cmacros.inc are a handy group of macros that make
continues
Part III • Working with Others
Listing 11.6. continued
; |
writing C-compatible functions easier. |
; |
|
include e:\windev\include\cmacros.inc
;
;int maximum(int, int);
;This version was optimized by hand for both
;minimum size and fastest execution. The
;compiler’s code is twice as long.
; |
|
|
; |
|
|
|
NAME |
MAXIMUM |
DGROUP |
GROUP |
_DATA |
_TEXT |
SEGMENT |
WORD PUBLIC ‘CODE’ |
|
ASSUME |
CS:_TEXT,DS:DGROUP,SS:DGROUP |
|
PUBLIC |
_maximum |
_maximum: |
enter |
0000H,00H |
|
mov |
ax,word ptr +6H[bp] |
|
cmp |
word ptr +4H[bp],ax |
|
jle |
short L6 |
|
mov |
ax,word ptr +4H[bp] |
L6: |
nop |
;Handy label target... |
|
leave |
|
|
ret |
|
_TEXT |
ENDS |
|
_DATA |
SEGMENT |
WORD PUBLIC ‘DATA’ |
_DATA |
ENDS |
|
|
END |
|
|
|
|
C and Other Languages |
C C C |
|
11C |
|
C C C |
|
C C |
In my version of maximum(), I used the enter and leave operands, which are useful for creating functions called by higher level languages such as C.
One of the tested values is returned in AX. If the value in AX is the larger of the two values, AX does not need to be reloaded and can just return that value. Otherwise, the other value is placed in AX and then returned.
Because this version is optimized by hand, it executes faster and makes better use of memory. For more complex functions, however, this improvement is more difficult to obtain. By breaking your assembly code into smaller and smaller parts and analyzing the resultant code, you can be confident that it will be faster without performing some extensive benchmarks.
Calling FORTRAN and Pascal from C
Writing maximum() in FORTRAN is an easy task, as shown in Listing 11.7. Optimization is less of a concern in FORTRAN than in assembly because FORTRAN is a high-level language. Not all C compilers support FORTRAN functions.
Listing 11.7. The maximum() function in FORTRAN.
*
* The C function maximum() written in FORTRAN
*
integer function maximum(nVar1, nVar2) integer*2 nVar1, nVar2
maximum = nVar2
if (nVar1 .gt. nVar2) maximum = nVar1
end
When this function is called from C, the calling C program file must tell the C compiler that the function is written in FORTRAN. This is accomplished by properly declaring the maximum() function:
int __fortran maximum(int, int);
Part III • Working with Others
The call fails if the function is not declared correctly (using the __fortran keyword) because of the way that the arguments are passed to a FORTRAN program’s functions. Except for the differences in languages, functions written in FORTRAN and in Pascal are handled the same way.
Calling C Functions from Other Languages
The opposite of calling from C a function written in another language is calling from a second language a function written in C. There are several problems in doing this. For example, many C library functions rely on the execution of C initialization code, which may not be present when a program written in another language calls a C function. You can partially alleviate this problem in assembly by writing the program’s main function in C, thereby forcing the assembly program to behave like a C program.
A C program can grow quite large when it is linked, so many programmers write in assembly to keep the program smaller. If the C function called from another language does not make any calls to C library functions, no library functions are included in the executable program. This makes the program smaller and eliminates the task of ensuring that C has properly initialized itself.
This section presents a few examples of other languages calling a C function. These examples use CALLNOTC.C (Listing 11.5), which was used also in the previous section. The maximize() function is written in C (see Listing 11.8), and the main, calling program is written in another language. Because maximize() does not call any other functions, I did not have to address the issue of calling C’s startup code. However, I have included the necessary mechanism to initialize C, in case you need it.
Listing 11.8. MAXIMUM.C.
/* MAXIMUM.C function, written 1992 by Peter D. Hipson */
/* This is a C function called by FORTRAN or assembly */
#include <stdio.h> #include <stddef.h>
C and Other Languages
#include <stdlib.h> |
#include |
<string.h> |
#include |
<time.h> |
int cdecl maximum(int nVar1, int nVar2); |
int |
maximum( |
int |
nVar1, |
int |
nVar2) |
{ |
|
if (nVar1 < nVar2)
{
return(nVar2);
}
else
{
return(nVar1);
}
}
The maximum() function could be written in assembly, but some other function that must do special things such as interact with hardware might have to be written in assembly.
Calling C from Assembly
An assembly program can call a C function, subject to either of two conditions. One, the C library must be linked with the assembly program. Any C functions called (CALLNOTC, in Listing 11.5, calls several) are processed by library functions, whose references must be resolved.
Two, the C library’s startup code must be included and called. With Microsoft C, this is accomplished by linking with the necessary library (I used SLIBCE.LIB in developing CALLNOTC.ASM, in Listing 11.9) and defining the external symbol, _acrtused. When the startup code from the C library is executed, it properly initializes the C environment.
Part III • Working with Others
Listing 11.9. CALLNOTC.ASM.
;/* CALLNOTC program, written 1992 by Peter D. Hipson */
;
;/* This is an assembly program that calls FORTRAN or assembly */
;
;#include <stdio.h> ;#include <stddef.h> ;#include <stdlib.h> ;#include <string.h> ;#include <time.h>
;
;int cdecl maximum(int nVar1, int nVar2);
;
; Enable 386 instruction set
.386
NAME callnotc
; maximum(), sscanf(), gets(), and printf() are called
EXTRN _maximum:BYTE
EXTRN _sscanf:BYTE
EXTRN _gets:BYTE
EXTRN _printf:BYTE
; _acrtused is used to initialize C at runtime
EXTRN _ _acrtused:BYTE
;The datagroup is _DATA (misc data), CONST (constants),
;and _BSS (uninitialized data)
DGROUP |
GROUP |
_DATA,CONST,_BSS |
_TEXT |
SEGMENT |
WORD PUBLIC USE16 ‘CODE’ |
|
ASSUME |
CS:_TEXT,DS:DGROUP,SS:DGROUP |
;The program’s main function, called by C’s startup code.
;Microsoft C naming conventions require the underscore
Part III • Working with Others
Listing 11.9. continued
|
mov |
ax,offset DGROUP:szScanFormat |
|
push |
ax |
|
|
|
lea |
ax,-102H[bp] |
; szBuffer (auto var on stack) |
|
push |
ax |
|
|
|
call |
near ptr _sscanf |
|
|
|
add |
sp,0008H |
|
|
|
push |
word ptr -106H[bp] |
; nVar2 |
|
push |
word ptr -104H[bp] |
; nVar1 |
|
call |
near ptr _maximum |
|
|
add |
sp,0004H |
|
|
|
push |
ax |
|
|
|
push |
word ptr -106H[bp] |
; nVar2 |
|
push |
word ptr -104H[bp] |
; nVar1 |
|
mov |
ax,offset DGROUP:szPrintFormat |
|
push |
ax |
|
|
|
call |
near ptr _printf |
|
|
|
add |
sp,0008H |
|
|
|
mov |
ax,word ptr -106H[bp] |
|
cmp |
word ptr -104H[bp],ax |
|
je |
short AllDone |
|
; We are finished |
|
jmp |
near ptr Loop1 |
|
; Go around another time |
AllDone: |
mov |
ax,0000H |
|
; A zero return code |
|
jmp |
near ptr Dummy |
|
|
Dummy: |
pop |
di |
|
; Clean up and go home |
|
pop |
si |
|
|
|
mov |
sp,bp |
|
|
|
pop |
bp |
|
|
|
ret |
|
|
|
_TEXT |
ENDS |
|
|
|
_DATA |
SEGMENT |
WORD PUBLIC USE16 ‘DATA’ |
|
|
C and Other Languages |
C C C |
|
|
|
11C |
|
|
|
C C C |
szMsg1 |
LABEL |
BYTE |
C C |
|
|
DB |
“Enter the same number twice to end”, 0AH, 00H |
szMsg2 |
LABEL |
BYTE |
|
|
DB |
“Enter two integers, separated with blanks”, 00H |
szScanFormat |
LABEL |
BYTE |
|
|
DB |
“%d %d”, 00H |
|
szPrintFormat |
LABEL |
BYTE |
|
|
DB |
“The values entered are %d and %d, “ |
|
|
DB |
“the larger is %d”,0AH, 00H |
|
_DATA |
ENDS |
|
|
CONST |
SEGMENT |
WORD PUBLIC USE16 ‘CONST’ |
|
CONST |
ENDS |
|
|
_BSS |
SEGMENT |
WORD PUBLIC USE16 ‘BSS’ |
|
_BSS |
ENDS |
|
|
END
The program calls not only the C function (maximum()), but also a number of other library functions, including printf(), gets(), and scanf(). These functions form the basic I/O for the program. DOS I/O interrupt routines could have been called directly, but because they do not do formatted I/O, the program would have to convert the typed characters to integer numbers and convert the numbers back to characters for output.
To summarize, write the main program in C (or another high-level language) if possible. Then write in assembly the routines whose speed or size is critical. When using an advanced compiler (such as Microsoft’s C 7.0), you can control how much of the library code is included, the arrangement of the program’s segments (whether there are separate data and code segments or a single segment for both), and the allocation and use of memory.
When programming on a PC (under DOS), do not forget the available DOS services. These services, listed in Table 11.3, are accessed using the Int 21 instruction. If you are using a different operating system, it should have similar functions.