Skip to main content

Section 11.2 The Assignment Operator

The C/C++ assignment operator, ‘=’, causes the expression on the right-hand side of the operator to be evaluated and the result to be associated with the variable that is named on the left-hand side. Subsequent uses of the variable name in the program will evaluate to this same value. For example,

int x;
.....
x = 123;

will assign the integer \(123\) to the variable x. If x is later used in an expression, the value assigned to x will be used in evaluating the expression. For example, the expression

2 * x;

would evaluate to \(246\text{.}\)

We now explore what assignment means at the assembly language level. The variable declaration,

int x;

causes a location to be allocated and that location to be given the name “x.” That is, other parts of the program can refer to the location where the value of x is stored by using the name “x.”

The type name in the declaration, int, tells the compiler how many bytes to allocate and the code used to represent the data stored at this location. The int type uses the two's complement code. So the assignment statement,

x = 123;

would set the bit pattern in the location named x to \(\hex{0x0000007b}\text{,}\) the two's complement code for the signed integer \(+123\text{.}\) Similarly, the assignment statement

x = -123;

would set the bit pattern in the location named x to \(\hex{0xffffff85}\text{,}\) the two's complement code for the signed integer \(-123\text{.}\)

The program in Listing 11.2.1 uses the assignment operator to store values in the x, y, and z variables. We use the register type modifier to “advise” the compiler to use a register for the x and y variables.

/* assignment1.c
 * Assign a 32-bit pattern to a register
 * 
 * 2017-09-29: Bob Plantz 
 */

#include <stdio.h>

int main(void)
{
  register int x, y;
  int z;

  x = 123;
  y = 4567;
  z = x + y;

  printf("%i + %i = %i\n", x, y, z);

  return 0;
}
Listing 11.2.1. Assignment to a register variable (C).

The compiler-generated assembly language shown in Listing 11.2.2 shows the assignment operation implemented in three different ways.

        .arch armv6
        .file   "assignment1.c"
        .section  .rodata
        .align  2
.LC0:
        .ascii  "%i + %i = %i\012\000"
        .text
        .align  2
        .global main
        .syntax unified
        .arm
        .fpu vfp
        .type   main, %function
main:
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {r4, r5, fp, lr}
        add     fp, sp, #12
        sub     sp, sp, #8      @@ local var in stack frame
        mov     r4, #123        @@ x = 123;
        ldr     r5, .L3         @@ y = 4567;
        add     r3, r4, r5      @@ x + y
        str     r3, [fp, #-16]  @@ z is in stack frame
        ldr     r3, [fp, #-16]  @@ load z
        mov     r2, r5          @@    y
        mov     r1, r4          @@    x
        ldr     r0, .L3+4       @@ address of format string
        bl      printf
        mov     r3, #0
        mov     r0, r3
        sub     sp, fp, #12
        @ sp needed
        pop     {r4, r5, fp, pc}
.L4:
        .align  2
.L3:
        .word   4567
        .word   .LC0
        .ident  "GCC: (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516"
Listing 11.2.2. Assignment to a register variable (gcc asm).

The compiler honored our request to use registers for both the x and y variables, and the z variable is allocated in the stack frame.

Listing 11.2.3 shows my assembly language solution. It is essentially the same as what the compiler generated, but I have used names for labels and constants that will help with the explanation of the code.

@ assignment2.s
@ Assignment three ways.
@ 2017-09-29: Bob Plantz

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

@ Useful source code constants
        .equ    z,-16
        .equ    local,8

@ Constant program data
        .section  .rodata
        .align  2
formatMsg:
        .asciz	 "%i + %i = %i\n"

@ Program code
        .text
        .align  2
        .global main
        .type   main, %function
main:
        sub     sp, sp, 12      @ space for saving regs
        str     fp, [sp, 0]     @ save fp
        str     lr, [sp, 4]     @      lr
        str     r5, [sp, 8]     @      r5
        str     r4, [sp, 12]    @   and r4
        add     fp, sp, 12      @ our frame pointer
        sub     sp, sp, local   @ allocate memory for local var

        mov     r5, 123         @ x = 123;
        ldr     r4, yValue      @ y = 4567;
        add     r3, r5, r4      @ x + y
        str     r3, [fp, z]     @ z = x + y;

        ldr     r0, formatMsgAddr  @ printf("%i + %i = %i\n",
        mov     r1, r5          @            x,
        mov     r2, r4          @            y,
        ldr     r3, [fp, z]     @            z);
        bl      printf

        mov     r0, 0           @ return 0;
        add     sp, sp, local   @ deallocate local var
        ldr     fp, [sp, 0]     @ restore fp
        ldr     lr, [sp, 4]     @      lr
        ldr     r5, [sp, 8]     @      r5
        ldr     r4, [sp, 12]    @      r4
        add     sp, sp, 12      @   and sp
        bx      lr              @ return

        .align  2
yValue:
        .word   4567
formatMsgAddr:
        .word   formatMsg
Listing 11.2.3. Assignment to a register variable (prog asm).

First, notice that the values in the r4 and r5 registers must be saved on the stack in the prologue:

stmfd   sp!, {r4, r5, fp, lr}  @ save caller's info

and restored in the epilogue:

ldmfd   sp!, {r4, r5, fp, lr}  @ restore caller's info

as is specified in Table 10.1.1.

After setting up our frame pointer, we move the stack pointer to allocate space on the stack for the local variable:

add     fp, sp, 12      @ our frame pointer
sub     sp, sp, local   @ allocate memory for local var

where the value of local was computed to (a) allow enough memory space for the int variable, and (b) make sure the stack pointer is always on an eight-byte addressing boundary, as required by the protocol when calling a public function (printf in this case).

You have already seen the first two assignment implementations:

mov     r5, 123         @ x = 123;
ldr     r4, yValue      @ y = 4567;

in Listing 10.1.4. The integer value, \(123\text{,}\) is within the range that can be moved directly into a register. However, \(4567\) cannot, so it is stored in memory and loaded into a register from memory.

The compiler honored our request to use registers for both the x and y variables. However, the z variable is allocated in the stack frame. So after the addition is performed, the sum is stored in memory at a location relative to the frame pointer:

str     r3, [fp, z]     @ z = x + y;

Recall from Section 9.2 that [fp, z] specifies the address obtained by adding the value of z to the value contained in the fp register. In this function z is an offset of \(-16\) bytes from the address in fp.

In Section 11.3 we discuss the machine code for the instructions that implement these assignment statements. In particular, we will be looking at how the location of each variable is encoded in the machine language.