C/Assembly Interfacing

We've discussed several reasons for wanting to write routines in assembly language:
(1) To increase speed and efficiency of a particular routine
(2) To perform some function that is machine specific and unavailable in the high level language being used
(3) We might want to use some third party routines
(4) Sometimes debugging C source in assembly is easier

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.


Problem: Assume that we have written the following C program that calls a function mul which simply multiplies two integer values together and returns the result to be printed. The C code is written but the mul routine is not and needs to be written in assembly language. Questions that need to be answered:

(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


Douglas J. Ryan/ryandj@pacificu.edu