Skip to main content

Section 15.4 structs as Function Arguments

You probably recognize that the program in Listing 15.3.1 should have a subroutine to assign values to the fields in each struct. The general rules for passing arguments to functions are:

  • An input is passed by value.

  • An output is passed by reference.

While C++ supports pass by reference for output arguments, C does not. In C, a pass by reference is simulated by passing a pointer to the variable to the function. Then the function can change the variable, thus effecting an output. At the assembly language level, pass by reference is implemented in C++ by passing a pointer, so these two rules can be restated:

  • An input is a copy of the original value.

  • An output provides the address of the original value.

Some languages, e.g., ADA, also support passing an “update.” In this case the function replaces the original value with a new value that depends upon the original value. Passing an argument for update is also implemented by passing its address.

There is an important exception to the rule of passing a copy for inputs. When the amount of data is large, making a copy of it is inefficient. So we organize it into a single entity and pass the address of that entity.

The most common example of this is an array. In fact, it is so common that in C arrays are automatically passed by address. Thus, in C/C++

void f(int a, int b[]);

  ----
int x;
int y[100];
  ----
f(x, y);
  ----

will cause x to be passed by value and y to be passed by address.

Another common example of passing a possibly large amount of data as input to a function is a struct. Of course, not every struct is large. And it is possible to pass the value in a single struct field, but the main reason for organizing data into a struct is usually to treat several pieces of data as a more or less single unit.

Since a struct, unlike an array, is not automatically passed by address in C, we must use the address-of operator (&) on the name of the struct variable if we wish to avoid making a copy of the entire variable on the stack. The technique is exactly the same as passing an address of a simple variable.

Listing 15.4.1 shows a solution to the program in Listing 15.3.1 that uses a separate function to fill each struct.

/*structPass1.c
 * Demonstrates passing structs as arguments in C
 * 2017-09-29: Bob Plantz
 */

#include <stdio.h>
#include "loadStruct1.h"   /* includes struct theTag def. */

int main(void)
{
  struct theTag x;
  struct theTag y;
    
  loadStruct(&x, 'a', 123, 'b');
  loadStruct(&y, '1', 456, '2');

  printf("x: %c, %i, %c and y: %c, %i, %c\n",
           x.aChar, x.anInt, x.anotherChar,
           y.aChar, x.anInt, y.anotherChar);

  return 0;
}
Listing 15.4.1. Using a separate function to fill a struct. (C)
/* loadStruct1.h
 * Defines the fields of a theTag struct.
 * 2017-09-29: Bob Plantz
 */

#ifndef LOADSTRUCT_H
#define LOADSTRUCT_H
struct theTag {
  char aChar;
  int anInt;
  char anotherChar;
};

void loadStruct(struct theTag* aStruct, char firstChar,
         int aNumber, char secondChar);
#endif
Listing 15.4.2. Header file that defines the struct for the program in Listing 15.4.1. (C)
/* loadStruct1.c
 * Assigns values to the fields of a theTag struct.
 * 2017-09-29: Bob Plantz
 */

#include "loadStruct1.h"   /* includes struct theTag def. */

void loadStruct(struct theTag* aStruct, char firstChar,
         int aNumber, char secondChar)
{
  aStruct->aChar = firstChar;
  aStruct->anInt = aNumber;
  aStruct->anotherChar = secondChar;
}
Listing 15.4.3. The function that stores data in the struct for the program in Listing 15.4.1. (C)

The loadStruct function needs to specify that the first argument, aStruct, is a pointer to (address of) an entity that has the type struct theTag by using the dereferencing operator, ‘*’. Within the function, we need to dereference the pointer to the struct so we can access each field. This can be accomplished with:

(* aStruct).aChar = firstChar;
(* aStruct).anInt = aNumber;
(* aStruct).anotherChar = secondChar;

The parentheses are required here because the field selector operator, ‘.’, has higher precedence than the dereference operator, ‘*’. This is a clumsy syntax, so C/C++ provides a much nicer syntax:

aStruct->aChar = firstChar;
aStruct->anInt = aNumber;
aStruct->anotherChar = secondChar;

Both syntaxes are equivalent. They first dereference the pointer to the struct and then access the field by name.

When using a separate function to store values in the fields of the struct, the compiler generates code that uses a pointer to access the struct, as shown in Listing 15.4.4.

        .arch   armv6
        .file   "structPass1.c"
        .section  .rodata
        .align  2
.LC0:
        .ascii  "x: %c, %i, %c and y: %c, %i, %c\012\000"
        .text
        .align  2
        .global main
        .syntax unified
        .arm
        .fpu vfp
        .type   main, %function
main:
        @ args = 0, pretend = 0, frame = 24
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {fp, lr}
        add     fp, sp, #4
        sub     sp, sp, #40
        sub     r0, fp, #16   @@ address of x struct
        mov     r3, #98       @@ args to loadStruct
        mov     r2, #123
        mov     r1, #97
        bl      loadStruct
        sub     r0, fp, #28   @@ address of y struct
        mov     r3, #50       @@ args to loadStruct
        mov     r2, #456
        mov     r1, #49
        bl      loadStruct
        ldrb    r3, [fp, #-16]  @ zero_extendqisi2
        mov     ip, r3
        ldr     r2, [fp, #-12]
        ldrb    r3, [fp, #-8]   @ zero_extendqisi2
        mov     lr, r3
        ldrb    r3, [fp, #-28]  @ zero_extendqisi2
        mov     r1, r3
        ldr     r3, [fp, #-12]
        ldrb    r0, [fp, #-20]  @ zero_extendqisi2
        str     r0, [sp, #8]
        str     r3, [sp, #4]
        str     r1, [sp]
        mov     r3, lr
        mov     r1, ip
        ldr     r0, .L3
        bl      printf
        mov     r3, #0
        mov     r0, r3
        sub     sp, fp, #4
        @ sp needed
        pop     {fp, pc}
.L4:
        .align  2
.L3:
        .word   .LC0
        .ident  "GCC: (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516"
Listing 15.4.4. A pointer is used to pass the location of the entire struct to the loadStruct funtion. (gcc asm)
        .arch   armv6
        .file   "loadStruct1.c"
        .text
        .align  2
        .global loadStruct
        .syntax unified
        .arm
        .fpu    vfp
        .type   loadStruct, %function
loadStruct:
        @ args = 0, pretend = 0, frame = 16
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        str     fp, [sp, #-4]!
        add     fp, sp, #0
        sub     sp, sp, #20
        str     r0, [fp, #-8]
        str     r2, [fp, #-16]
        mov     r2, r3
        mov     r3, r1
        strb    r3, [fp, #-9]
        mov     r3, r2
        strb    r3, [fp, #-10]
        ldr     r3, [fp, #-8]
        ldrb    r2, [fp, #-9]
        strb    r2, [r3]
        ldr     r3, [fp, #-8]
        ldr     r2, [fp, #-16]
        str     r2, [r3, #4]
        ldr     r3, [fp, #-8]
        ldrb    r2, [fp, #-10]
        strb    r2, [r3, #8]
        nop
        add     sp, fp, #0
        @ sp needed
        ldr     fp, [sp], #4
        bx      lr
        .ident  "GCC: (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516"
Listing 15.4.5. The loadStruct function accesses the struct field through the pointer passed to it. (gcc asm)

I have simplified the loadStruct function in my solution, as shown in Listing 15.4.6.

@ structPass2.s
@ Allocates two structs and assigns a value to each field
@ in each struct, then displays the values.
@ 2017-09-29: Bob Plantz

@ Define my Raspberry Pi
        .cpu    cortex-a53
        .fpu    neon-fp-armv8
        .syntax unified         @ modern syntax

@ Constants for assembler
        .include "theTagStruct.s"  @ theTag struct defs.
        .equ    y,-36           @ y struct
        .equ    x,-24           @ x struct
        .equ    locals,24       @ space for the structs

@ Constant program data
        .section .rodata
        .align  2
displayX:
        .asciz        "x fields:\n"
displayY:
        .asciz        "y fields:\n"
dispAChar:
        .asciz        "         aChar = "
dispAnInt:
        .asciz        "         anInt = "
dispOtherChar:
        .asciz        "   anotherChar = "

@ The program
        .text
        .align  2
        .global main
        .type   main, %function
main:
        sub     sp, sp, 16      @ space for saving regs
                                @ (keeping 8-byte sp align)
        str     r4, [sp, 4]     @ save r4
        str     fp, [sp, 8]     @      fp
        str     lr, [sp, 12]    @      lr
        add     fp, sp, 12      @ set our frame pointer
        sub     sp, sp, locals  @ for the structs
        
@ fill the x struct
        add     r0, fp, x       @ address of x struct
        mov     r1, '1
        mov     r2, 456
        mov     r3, '2
        bl      loadStruct

@ fill the y struct
        add     r0, fp, y       @ address of y struct
        mov     r1, 'a
        mov     r2, 123
        mov     r3, 'b
        bl      loadStruct

@ display x struct
        add     r4, fp, x         @ address of x struct
        ldr     r0, displayXaddr
        bl      writeStr
        ldr     r0, dispACharAddr @ display aChar
        bl      writeStr
        ldrb    r0, [r4, aChar]
        bl      putChar
        bl      newLine
        ldr     r0, dispAnIntAddr @ display anInt
        bl      writeStr
        ldr     r0, [r4, anInt]
        bl      putDecInt
        bl      newLine
        ldr     r0, dispOtherCharAddr @ display anotherChar
        bl      writeStr
        ldrb    r0, [r4, anotherChar]
        bl      putChar
        bl      newLine

@ display y struct
        add     r4, fp, y         @ address of y struct
        ldr     r0, displayXaddr
        bl      writeStr
        ldr     r0, dispACharAddr @ display aChar
        bl      writeStr
        ldrb    r0, [r4, aChar]
        bl      putChar
        bl      newLine
        ldr     r0, dispAnIntAddr @ display anInt
        bl      writeStr
        ldr     r0, [r4, anInt]
        bl      putDecInt
        bl      newLine
        ldr     r0, dispOtherCharAddr @ display anotherChar
        bl      writeStr
        ldrb    r0, [r4, anotherChar]
        bl      putChar
        bl      newLine

        mov     r0, 0           @ return 0;
        add     sp, sp, locals  @ deallocate local var
        ldr     r4, [sp, 4]     @ restore r4
        ldr     fp, [sp, 8]     @         fp
        ldr     lr, [sp, 12]    @         lr
        add     sp, sp, 16      @         sp
        bx      lr              @ return
        
        .align  2
@ addresses of messages
displayXaddr:
        .word   displayX
displayYaddr:
        .word   displayY
dispACharAddr:
        .word   dispAChar
dispAnIntAddr:
        .word   dispAnInt
dispOtherCharAddr:
        .word   dispOtherChar
Listing 15.4.6. A pointer is used to pass the location of the entire struct to the loadStruct funtion. (prog asm)
@ theTagStruct.s
@ field name definitions; requires 12 bytes
@ 2017-09-29: Bob Plantz

@ struct definition
        .equ    aChar,0
        .equ    anInt,4
        .equ    anotherChar,8
Listing 15.4.7. struct field definitions for the program in Listing 15.4.6. (prog asm)
@ loadStruct2.s
@ Stores values in a theTag struct
@ Calling sequence:
@        r0 <- address of the struct
@        r1 <- aChar
@        r2 <- anInt
@        r3 <- anotherChar
@        bl  loadStruct
@ Returns 0
@ 2017-09-29: Bob Plantz

@ Define my Raspberry Pi
        .cpu    cortex-a53
        .fpu    neon-fp-armv8
        .syntax unified         @ modern syntax

@ Constants for assembler
        .include "theTagStruct.s"  @ theTag struct defs.

@ The program
        .text
        .align  2
        .global loadStruct
        .type   loadStruct, %function
loadStruct:
        sub     sp, sp, 8       @ space for fp, lr
        str     fp, [sp, 0]     @ save fp
        str     lr, [sp, 4]     @   and lr
        add     fp, sp, 4       @ set our frame pointer

        strb    r1, [r0, aChar] @ aStruct->aChar = firstChar;
        str     r2, [r0, anInt] @ aStruct->anInt = aNumber;
        strb    r3, [r0, anotherChar] @ aStruct->anotherChar = secondChar;

        mov     r0, 0           @ return 0;
        ldr     fp, [sp, 0]     @ restore caller fp
        ldr     lr, [sp, 4]     @       lr
        add     sp, sp, 8       @   and sp
        bx      lr              @ return
Listing 15.4.8. The loadStruct function accesses each struct field through the overall struct pointer passed to it, so it needs to include the field definition file to access each field. (prog asm)

Notice that I have defined the struct field names in a separate file, theTagStruct.s. I use the .include assembler directive to include this file wherever I need to use the names.

It is tempting at this point to develop an expression to automate the computation of the value of locals in the main function. As pointed out in Section 10.5.2 this would probably be done in a production environment. But I found it to be a little tricky to do it in such a way that easily allowed for changes to this program, which is the only reason to automate the computation. I still had to draw a picture of the stack frame, and at that point I had the numbers I needed.

The prologue and epilogue in loadStruct are not really needed in this simple function. But it is good to get in the habit of coding them into all your functions. It certainly has a negligible effect on execution time, and they help establish a structure to the function if it is ever changed.