3.6. Interfacing C and C++ With Assembly Language

The following are ways to use assembly language with C/C++ code:

3.6.1. Using Assembly Language Modules With C/C++ Code

Interfacing C/C++ with assembly language functions is straightforward if you follow the calling conventions defined in Function Structure and Calling Conventions, and the register conventions defined in Register Conventions. C/C++ code can access variables and call functions defined in assembly language, and assembly code can access C/C++ variables and call C/C++ functions.

Follow these guidelines to interface assembly language and C:

  • You must preserve any dedicated registers modified by a function. Dedicated registers include:

    • Save-on-entry registers (R4-R11 (alternate names are V1 to V8 and LR))

    • Stack pointer (SP or R13)

    If the SP is used normally, it does not need to be explicitly preserved. In other words, the assembly function is free to use the stack as long as anything that is pushed onto the stack is popped back off before the function returns (thus preserving SP).

    Any register that is not dedicated can be used freely without first being saved.

  • Interrupt routines must save all the registers they use. For more information, see Interrupt Handling.

  • When you call a C/C++ function from assembly language, load the designated registers with arguments and push the remaining arguments onto the stack as described in How a Function Makes a Call.

    Remember that a function can alter any register not designated as being preserved without having to restore it. If the contents of any of these registers must be preserved across the call, you must explicitly save them.

  • Functions must return values correctly according to their C/C++ declarations. Double values are returned in R0 and R1, and structures are returned as described in Step 2 of How a Function Makes a Call. Any other values are returned in R0.

  • No assembly module should use the .cinit section for any purpose other than autoinitialization of global variables. The C/C++ startup routine assumes that the .cinit section consists entirely of initialization tables. Disrupting the tables by putting other information in .cinit can cause unpredictable results.

  • The compiler assigns linknames to all external objects. Thus, when you write assembly language code, you must use the same linknames as those assigned by the compiler. See Disable Name Demangling (--no_demangle) for details.

  • Any object or function declared in assembly language that is accessed or called from C/C++ must be declared with the .def or .global directive in the assembly language modifier. This declares the symbol as external and allows the linker to resolve references to it.

    Likewise, to access a C/C++ function or object from assembly language, declare the C/C++ object with the .ref or .global directive in the assembly language module. This creates an undeclared external reference that the linker resolves.

3.6.2. Accessing Assembly Language Functions From C/C++

Functions defined in C++ that will be called from assembly should be defined as extern “C” in the C++ file. Functions defined in assembly that will be called from C++ must be prototyped as extern “C” in C++.

Example 1 below illustrates a C++ function called main(), which calls an assembly language function called asmfunc, which is shown in Example 2. The asmfunc function takes its single argument, adds it to the C++ global variable called gvar, and returns the result.

Example 1: Calling an Assembly Language Function From a C/C++ Program

extern "C" {
extern int asmfunc(int a); /* declare external asm function */
int gvar = 0; /* define global variable */
}

void main()
{
    int I = 5;

    I = asmfunc(I); /* call function normally */
}

Example 2: Assembly Language Program Called by Example 1

         .global asmfunc
         .global gvar
asmfunc:
         LDR r1, gvar_a
         LDR r2, [r1, #0]
         ADD r0, r0, r2
         STR r0, [r1, #0]
         MOV pc, lr
gvar_a .field gvar, 32

In the C++ program in Example 1, the extern “C” declaration tells the compiler to use C naming conventions (that is, no name mangling). When the linker resolves the .global _asmfunc reference, the corresponding definition in the assembly file will match.

The parameter i is passed in R0, and the result is returned in R0. R1 holds the address of the global gvar. R2 holds the value of gvar before adding the value i to it.

3.6.3. Accessing Assembly Language Variables From C/C++

It is sometimes useful for a C/C++ program to access variables or constants defined in assembly language. There are several methods that you can use to accomplish this, depending on where and how the item is defined: a variable defined in the .bss section, a variable not defined in the .bss section, or a linker symbol.

3.6.3.1. Accessing Assembly Language Global Variables

Accessing variables from the .bss section or a section named with .usect is straightforward:

  1. Use the .bss or .usect directive to define the variable.

  2. Use the .def or .global directive to make the definition external.

  3. Use the appropriate linkname in assembly language.

  4. In C/C++, declare the variable as extern and access it normally.

Example 3 and Example 4 show how you can access a variable defined in .bss.

Example 3: Assembly Language Variable Program

.bss     var,4,4  ; Define the variable
.global  var      ; Declare the variable as external

Example 4: C Program to Access Assembly Language From Example 3

extern int var;       /* External variable */
var = 1;              /* Use the variable  */

3.6.3.2. Accessing Assembly Language Constants

You can define global constants in assembly language by using the .set directive in combination with either the .def or .global directive, or you can define them in a linker command file using a linker assignment statement. These constants are accessible from C/C++ only with the use of special operators.

For variables defined in C/C++ or assembly language, the symbol table contains the address of the value contained by the variable. When you access an assembly variable by name from C/C++, the compiler gets the value using the address in the symbol table.

For assembly constants, however, the symbol table contains the actual value of the constant. The compiler cannot tell which items in the symbol table are addresses and which are values. If you access an assembly (or linker) constant by name, the compiler tries to use the value in the symbol table as an address to fetch a value. To prevent this behavior, you must use the & (address of) operator to get the value (_symval). In other words, if x is an assembly language constant, its value in C/C++ is &x. See Using Linker Symbols in C/C++ Applications for more examples that use _symval.

For more about symbols and the symbol table, refer to Symbols.

You can use casts and #defines to ease the use of these symbols in your program, as in Example 5 and Example 6.

Example 5: Accessing an Assembly Language Constant From C

extern int table_size; /*external ref */
#define TABLE_SIZE ((int) (&table_size))
    . /* use cast to hide address-of */
    .
    .
for (I=0; i<TABLE_SIZE; ++I) /* use like normal symbol */

Example 6: Assembly Language Program for Example 5

_table_size  .set10000           ; define the constant
             .global _table_size ; make it global

Because you are referencing only the symbol’s value as stored in the symbol table, the symbol’s declared type is unimportant. In Example 5, int is used. You can reference linker-defined symbols in a similar manner.

3.6.4. Sharing C/C++ Header Files With Assembly Source

Sharing C/C++ header files with assembly source is not supported.

3.6.5. Using Inline Assembly Language

Within a C/C++ program, you can use the asm statement to insert a single line of assembly language into the assembly language file created by the compiler. A series of asm statements places sequential lines of assembly language into the compiler output with no intervening code. For more information, see naked.

The asm statement is useful for inserting comments in the compiler output. Simply start the assembly code string with a semicolon (;) as shown below:

asm(";*** this is an assembly language comment");

Note

Using the asm Statement Keep the following in mind when using the asm statement:

  • Be extremely careful not to disrupt the C/C++ environment. The compiler does not check or analyze the inserted instructions.

  • Avoid inserting jumps or labels into C/C++ code because they can produce unpredictable results by confusing the register-tracking algorithms that the code generator uses.

  • Do not change the value of a C/C++ variable when using an asm statement. This is because the compiler does not verify such statements. They are inserted as is into the assembly code, and potentially can cause problems if you are not sure of their effect.

  • Do not use the asm statement to insert assembler directives that change the assembly environment.

  • Avoid creating assembly macros in C code and compiling with the --symdebug:dwarf (or -g) option. The C environment’s debug information and the assembly macro expansion are not compatible.

3.6.6. Modifying Compiler Output

You can inspect and change the compiler’s assembly language output by compiling the source and then editing the assembly output file before assembling it. Specify the -S option on the compiler command line to capture the compiler generated assembly.