Section 15.4 struct
s 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; }
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
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; }
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"
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"
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
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
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
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.