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;
}
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.
