It is important to note that no matter how good a compiler is, it will never produce code as fast and efficient as an excellent assembly language programmer.
In order to interface assembly and C, we must know what the calling conventions are. By calling conventions, we are referring to the method that the implementors of the C compiler decided to use to pass information to and from functions. Typically information is passed one of two ways: (1) by the use of internal registers (2) through the system stack. In general we find that most C compilers use the system stack to pass arguments to functions and registers to hold the return values. In addition to defining what gets passed and how, we must also be careful to preserve certain registers if we want their values unchanged after the assembly routine is executed. More on this later. For ease of use, I will use the Turbo C/C++ calling conventions. It is important to note that the calling conventions of Turbo C/C++ are not the same as Turbo Pascal. Although the differences are minor, if the conventions are not exact, incorrect results are sure to follow.
Turbo C/C++ passes arguments to functions on the stack. Arguments are pushed onto the stack from right to left, so as an example if we have the following function call: swap(a,b,c) then c is first pushed on the stack followed by b and then a. How many bytes are occupied on the stack by these three variables? The answer lies in what type of variables a,b, and c are. Another factor involves what memory model is being used (tiny, small, compact, ...). This affects whether the segment and address of a reference variable is passed via the stack. The following table will be useful when solving this question.
Type (# of bytes)
char (2)
short (2)
signed char (2)
signed short (2)
unsigned char (2)
unsigned short (2)
int (2)
signed int (2)
unsigned int (2)
long (4)
unsigned long (4)
float (4)
double (8)
long double (10)
near pointer (2)
far pointer (4)
Some questions of interest:
(1) Why is a near pointer two bytes?
(2) Why is a far pointer four bytes?
(3) What are the range of values for an int?
(4) What are the range of values for an unsigned long?
(5) What are the range of values for a short?
(6) Why is a char passed as two bytes?
(7) How would a string be passed and why?
When entering an assembly language routine:
(1) The contents of the BP are saved on the stack
(2) Current value of the SP is place into the BP
(3) Registers that are not to be modified are saved
As for return values, structures that are 1 or 2 bytes in length are returned in the AX register while structures that are 4 bytes in length are returned in the DX:AX register pair. For a far pointer the offset is returned in the AX and the segment is in the DX.
(1) What is the best way to proceed in writing the assembly language
routine(s)?
(2) How do we hook all of this information together?
(3) What is the best way to test this program?
(4) Pictorially, what does the stack and various stack frames
look like during program execution?
/* C source code "mult.c" to multiply 2 times 5 */ #include <stdio.h> int mul(int a,int b); main(void) { printf("%d",mul(2,5)); return 0; }
I would like to write this function mul and we will start with the following shell.
_TEXT segment byte public 'CODE' assume cs:_TEXT public _mul _mul proc near ret _mul endp _TEXT ends end
How do we fill in the rest of the code?
Once we have filled in the code, we can assemble mul.asm (MASM mul.asm) and then link it with the object module produced from compiling the driver program "mult.c" listed above using the following command line from dos:
tlink \tc\lib\c0s mult mul,mult.exe,,\tc\lib\cs