Skip to content

Including Assembly code in a C program

In this post, we’ll see one simple way to include assembly code in a C program. More specifically, we’ll see how to call functions written in assembly from C.

We’ll start by writing a simple program in C. Then, we’ll substitute one of the functions written in C with an equivalent one written in assembly in a different file. By including this file during compilation, we’ll be able to generate an executable that uses both C and assembly code.

C program

For our demonstration, we’ll use a simple program that checks whether 10 random numbers are odd or even.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int is_even(int n){
  return n % 2 == 0;
}

int main(){
  int n, i;
  time_t seed;

  srand((unsigned) time(&seed));

  for (i=0; i<10; i++) {
    n = rand() % 10;
    if (is_even(n))
      printf("%d is even\n", n);
    else
      printf("%d is odd\n", n);
  }

  return 0;
}

The rand function in C generates a pseudo-random number. To generate different values each time the program is executed, we need to vary the value of the seed, which is used by rand. We do that by calling the srand function and passing it a seed value. In this case, we use the actual time (returned by the time function).

After compilation and execution, we’ll get:

$ gcc c_assembly_source.c -o main
$ ./main
7 is odd
8 is even
7 is odd
1 is odd
7 is odd
5 is odd
9 is odd
6 is even
0 is even
6 is even

Assembly function

Now, we’ll write a function equivalent to is_even, but written in assembly. We can name our function however we want. We’ll name it is_even_assembly.

/* int is_even_assembly(int n) */
/* n in %rdi */
is_even_assembly:
        decl %edi            /* Subtract one to n */
        movl %edi, %eax      /* Move 1st argument to return register %rax */
        andl $1, %eax        /* Get least significant bit */
	ret                  /* return */

The assembly code is machine-dependent. Here, we’ll assume a x86 architecture which is the most common one. Following the x86 conventions, the first argument will be stored in the %rdi register when the function is called. In our case, we’ll have n stored in the low-order bytes of the %rdi register (%edi). Under the same conventions, the function return value is the one stored in the %rax register after calling the ret instruction.

We want to verify if n is even. To do that, we’ll check its least-significant bit. For even numbers, it will be 0 . For odd numbers, it will be 1. First, we decremented n by one. That’s the same as flipping the last bit. If it was 0, it will be 1 after decrementing n. If it was 1, it will be turned to 0. We do that because for C 0 represents false and 1 (or any non-zero value) represents true. After decrementing n, we use a mask using the and operator to get the least significant bit. Since we flipped the last bit before with the decoperator, it will be 1 for even values. That will correspond to true. For odd values, the last bit after decrementing will be 0, which corresponds to false.

Besides the function, we need to include a special instruction to make the function global so that our C program can call it. The complete assembly code will look as follows:

	.globl	is_even_assembly
/* int is_even_assembly(int n) */
/* n in %rdi */
is_even_assembly:
        decl %edi            /* Subtract one to n */
        movl %edi, %eax      /* Move 1st argument to return register %rax */
        andl $1, %eax        /* Get least significant bit */
        ret                  /* return */

Including the assembly function in C

To call our assembly function, we just need to declare it in our original C program. Next, we substitute all the prior function calls to is_even with is_even_assembly. The rest of the program remains unchanged.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int is_even_assembly(int n);

int main(){
  int n, i;
  time_t seed;
  
  srand((unsigned) time(&seed));

  for (i=0; i<10; i++) {
    n = rand() % 10;
    if (is_even_assembly(n))
      printf("%d is even\n", n);
    else
      printf("%d is odd\n", n);
  }

  return 0;
}

See that we have to call the function in the C program as it was named in the assembly code file.

Compiling assembly and C code

The final step is to include both files during compilation. Assuming the assembly code is stored in the file is_even_assembly.s and the main C program in c_assembly.c, we’ll have:

$ gcc c_assembly.c is_even_assembly.s -o main
$ ./main
9 is odd
3 is odd
2 is even
1 is odd
3 is odd
2 is even
5 is odd
8 is even
2 is even
3 is odd

And that’s it! Our C program called the function written in assembly successfully.

For further reference, you can check Combining Assembly Code with C programs.

Published inProgramming
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments