Skip to main content
\(\newcommand{\doubler}[1]{2#1} \newcommand{\binary}{\texttt} \newcommand{\hex}{\texttt} \newcommand{\octal}{\texttt} \newcommand{\prog}{\texttt} \newcommand{\lt}{<} \newcommand{\gt}{>} \newcommand{\amp}{&} \)

Section15.3Structs as Function Arguments

You probably recognize that the program in Listing 15.2.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.3.1 shows a solution to the program in Listing 15.2.1 that uses a separate function to fill each struct.

/*structPass1.c
 * Demonstrates passing structs as arguments in C
 * Bob Plantz - 29 July 2016
 */

#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;
}
/* loadStruct1.h
 * Defines the fields of a theTag struct.
 * Bob Plantz - 29 July 2016
 */

#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
/* loadStruct1.c
 * Assigns values to the fields of a theTag struct.
 * Bob Plantz - 29 July 2016
 */

#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;
}
Listing15.3.1Using a separate function to fill a struct. (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.3.2.

        .arch armv6
        .fpu vfp
        .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
        .type   main, %function
main:
        @ args = 0, pretend = 0, frame = 24
        @ frame_needed = 1, uses_anonymous_args = 0
        stmfd   sp!, {fp, lr}
        add     fp, sp, #4
        sub     sp, sp, #40
        sub     r3, fp, #16    @@ address of x struct
        mov     r0, r3         @@ args to loadStruct
        mov     r1, #97
        mov     r2, #123
        mov     r3, #98
        bl      loadStruct
        sub     r3, fp, #28    @@ address of y struct
        mov     r0, r3         @@ args to loadStruct
        mov     r1, #49
        mov     r2, #456
        mov     r3, #50
        bl      loadStruct
        ldrb    r3, [fp, #-16]        @ zero_extendqisi2
        mov     lr, r3
        ldr     r2, [fp, #-12]
        ldrb    r3, [fp, #-8]        @ zero_extendqisi2
        mov     ip, r3
        ldrb    r3, [fp, #-28]        @ zero_extendqisi2
        mov     r0, r3
        ldr     r3, [fp, #-12]
        ldrb    r1, [fp, #-20]        @ zero_extendqisi2
        str     r0, [sp]
        str     r3, [sp, #4]
        str     r1, [sp, #8]
        ldr     r0, .L3
        mov     r1, lr
        mov     r3, ip
        bl      printf
        mov     r3, #0
        mov     r0, r3
        sub     sp, fp, #4
        @ sp needed
        ldmfd   sp!, {fp, pc}
.L4:
        .align  2
.L3:
        .word   .LC0
        .ident  "GCC: (Raspbian 4.9.2-10) 4.9.2"
        .arch armv6
        .fpu vfp
        .file   "loadStruct1.c"
        .text
        .align  2
        .global loadStruct
        .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]    @@ pointer to struct
        str     r2, [fp, #-16]   @@ aNumber
        mov     r2, r3           @@ secondChar
        mov     r3, r1           @@ firstChar
        strb    r3, [fp, #-9]    @@ save firstChar
        mov     r3, r2
        strb    r3, [fp, #-10]   @@ save secondChar
        ldr     r3, [fp, #-8]    @@ retrieve pointer to struct
        ldrb    r2, [fp, #-9]    @@ and firstChar
        strb    r2, [r3]         @@ aStruct->aChar = firstChar;
        ldr     r3, [fp, #-8]
        ldr     r2, [fp, #-16]   @@ retrieve aNumber
        str     r2, [r3, #4]     @@ aStruct->anInt = aNumber;
        ldr     r3, [fp, #-8]
        ldrb    r2, [fp, #-10]   @@ retrieve secondChar
        strb    r2, [r3, #8]     @@ aStruct->anotherChar = secondChar;
        sub     sp, fp, #0
        @ sp needed
        ldr     fp, [sp], #4
        bx      lr
        .ident  "GCC: (Raspbian 4.9.2-10) 4.9.2"
Listing15.3.2Using a separate function to fill a struct. (gcc asm)

I have simplified the loadStruct funcion in my solution, as shown in Listing 15.3.3.

@ structPass2.s
@ Allocates two structs and assigns a value to each field
@ in each struct, then displays the values.
@ Bob Plantz - 6 August 2016

@ 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,-28           @ y struct
        .equ    x,-16           @ x struct
        .equ    locals,28       @ 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:
        stmfd   sp!, {r4, fp, lr}   @ save caller's info
        add     fp, sp, 8       @ 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
        ldmfd   sp!, {r4, fp, lr}   @ restore caller's info
        bx      lr              @ return
        
        .align  2
@ addresses of messages
displayXaddr:
        .word   displayX
displayYaddr:
        .word   displayY
dispACharAddr:
        .word   dispAChar
dispAnIntAddr:
        .word   dispAnInt
dispOtherCharAddr:
        .word   dispOtherChar
@ theTagStruct.s
@ field name definitions; requires 12 bytes
@ Bob Plantz - 6 August 2016

@ struct definition
        .equ    aChar,0
        .equ    anInt,4
        .equ    anotherChar,8
@ 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
@ Bob Plantz - 6 August 2016

@ 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:
        stmfd   sp!, {fp, lr}   @ save caller's info

        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;
        ldmfd   sp!, {fp, lr}   @ restore caller's info
        bx      lr              @ return
Listing15.3.3Using a separate function to fill a struct. (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.4.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.

Subsection15.3.1Exercises

1

Modify the program in Listing 15.3.3 so that it allows the user to input values for the two structs.

Hint Solution