Computer Systems Fundamentals

Course Resources

Administrivia: Course Outline | COMP1521 Handbook
Administrivia: Course Timetable | Help Sessions
Meet the Team: Our Team
Platforms: Lecture Recordings | Lecture Chat | Online Tut-Labs and Help Sessions (via BbCollaborate) | Course Forum
Style Guides: COMP1521 C Style Guide | Assembly Style Guide
MIPS Resources: MIPS Documentation | Text Editors for Assembly
mipsy: mipsy-web | mipsy source code | Debugging with mipsy (video)
Revision: Linux Cheatsheet | C Reference
Assessment: Autotests, Submissions, Marks | Give online: submission | Give online: sturec
Assignments: Assignment 1

Course Content Week-by-Week

Tutorial
Laboratory
Monday Week 1 Lecture Topics
Wednesday Week 1 Lecture Topics
Tutorial
Laboratory
Monday Week 2 Lecture Topics
Tutorial
Laboratory
Weekly Test
Wednesday Week 3 Lecture Topics
Tutorial
Laboratory
Weekly Test
Monday Week 4 Lecture Topics
Wednesday Week 4 Lecture Topics
Tutorial
Laboratory
Weekly Test
Monday Week 5 Lecture Topics
Wednesday Week 5 Lecture Topics

Course Content Topic-by-Topic

Course Intro
Mips Basics

Print hello world in MIPS.
	.text
main:

	li	$v0, 4			# syscall 4: print_string
	la	$a0, hello_world_msg	#
	syscall				# printf("Hello world\n");


	li	$v0, 0
	jr	$ra			# return 0;

	.data

hello_world_msg:
	.asciiz	"Hello world\n"

Download hello_world.s


Perform some basic arithmetic.

#include <stdio.h>

int main(void) {
    int x = 17;
    int y = 25;

    printf("%d\n", 2 * (x + y));

    return 0;
}

Download math.c


Perform some basic arithmetic.

#include <stdio.h>

int main(void) {
    int x = 17;
    int y = 25;

    int z = x + y;
    z = 2 * z;

    printf("%d", z);
    putchar('\n');

    return 0;
}

Download math.simple.c


Do some basic arithmetic in MIPS.
main:
	# Locals:
	# - $t0: int x
	# - $t1: int y
	# - $t2: int z

	li	$t0, 17		# int x = 17;
	li	$t1, 25		# int y = 25;

	add	$t2, $t0, $t1	# int z = x + y;
	mul	$t2, $t2, 2	# z = z * 2;

	li	$v0, 1		# syscall 1: print_int
	move	$a0, $t2	#
	syscall			# printf("%d", z);

	li	$v0, 11		# syscall 11: print_char
	li	$a0, '\n'	#
	syscall			# putchar('\n');

	li	$v0, 0
	jr	$ra		# return 0;

Download math.s


Do some basic arithmetic in MIPS, but with one less register.

main:
	# Locals:
	# - $t0: int x
	# - $t1: int y

	li	$t0, 17		# int x = 17;
	li	$t1, 25		# int y = 25;

	li	$v0, 1		# syscall 1: print_int
	add	$a0, $t0, $t1	# (x + y)
	mul	$a0, $a0, 2	# * 2
	syscall			# printf("%d", 2 * (x + y));

	li	$v0, 11		# syscall 11: print_char
	li	$a0, '\n'	#
	syscall			# putchar('\n');

	li	$v0, 0
	jr	$ra		# return 0;

Download math.fewer_registers.s


Square two numbers and sum their squares.

#include <stdio.h>

int main(void) {
    int a, b;

    printf("Enter a number: ");
    scanf("%d", &a);

    printf("Enter another number: ");
    scanf("%d", &b);

    printf("The sum of the squares of %d and %d is %d\n", a, b, a*a + b*b);

    return 0;
}

Download square_and_add.c


Square two numbers and sum their squares.

#include <stdio.h>

int main(void) {
    int a, b;

    printf("Enter a number: ");
    scanf("%d", &a);

    printf("Enter another number: ");
    scanf("%d", &b);


    printf("The sum of the squares of ");
    printf("%d", a);
    printf(" and ");
    printf("%d", b);
    printf(" is ");

    a = a * a;
    b = b * b;
    printf("%d", a + b);
    putchar('\n');

    return 0;
}

Download square_and_add.simple.c


Square and add two numbers and print the result.

	.text
main:
	# Locals:
	# - $t0: int a
	# - $t1: int b

	li	$v0, 4			# syscall 4: print_string
	la	$a0, prompt1_msg	#
	syscall				# printf("Enter a number: ");

	li	$v0, 5			# syscall 5: read_int 
	syscall				#
	move	$t0, $v0		# scanf("%d", &a);

	li	$v0, 4			# syscall 4: print_string
	la	$a0, prompt2_msg	#
	syscall				# printf("Enter another number: ");

	li	$v0, 5			# syscall 5: read_int
	syscall				#
	move	$t1, $v0		# scanf("%d", &b);


	li	$v0, 4			# syscall 4: print_string
	la	$a0, result_msg_1	# 
	syscall				# printf("The sum of the squares of ");

	li	$v0, 1			# syscall 1: print_int
	move	$a0, $t0		#
	syscall				# printf("%d", a);

	li	$v0, 4			# syscall 4: print_string
	la	$a0, result_msg_2	# 
	syscall				# printf(" and ");

	li	$v0, 1			# syscall 1: print_int
	move	$a0, $t1		#
	syscall				# printf("%d", b);

	li	$v0, 4			# syscall 4: print_string
	la	$a0, result_msg_3	#
	syscall				# printf(" is ");

	mul	$t0, $t0, $t0		# a = a * a;
	mul	$t1, $t1, $t1		# b = b * b;

	li	$v0, 1			# syscall 1: print_int
	add	$a0, $t0, $t1		# 
	syscall				# printf("%d", a + b);

	li	$v0, 11			# syscall 11: print_char
	la	$a0, '\n'		# 
	syscall				# putchar('\n');


	li	$v0, 0
	jr	$ra			# return 0;

	.data
prompt1_msg:
	.asciiz	"Enter a number: "
prompt2_msg:
	.asciiz	"Enter another number: "
result_msg_1:
	.asciiz	"The sum of the squares of "
result_msg_2:
	.asciiz	" and "
result_msg_3:
	.asciiz	" is "

Download square_and_add.s

Mips Control

Print a message only if a number is even.

#include <stdio.h>

int main(void) {
    int n;
    printf("Enter a number: ");
    scanf("%d", &n);

    if (n % 2 == 0) {
        printf("even\n");
    }

    return 0;
}

Download print_if_even.c


Print a message only if a number is even.

#include <stdio.h>

int main(void) {
    int n;
    printf("Enter a number: ");
    scanf("%d", &n);

    if (n % 2 != 0) goto epilogue;
        printf("even\n");

epilogue:
    return 0;
}

Download print_if_even.simple.c


Calculate 1*1 + 2*2 + ... + 99*99 + 100*100

#include <stdio.h>

int main(void) {
    int sum = 0;

    for (int i = 1; i <= 100; i++) {
        sum += i * i;
    }

    printf("%d\n", sum);

    return 0;
}

Download sum_100_squares.c


Calculate 1*1 + 2*2 + ... + 99*99 + 100*100.

#define UPPER_BOUND 100
#include <stdio.h>

int main(void) {
    int sum = 0;

loop_i_to_100__init:;
    int i = 0;
loop_i_to_100__cond:
    if (i > UPPER_BOUND) goto loop_i_to_100__end;
loop_i_to_100__body:
    sum += i * i;
loop_i_to_100__step:
    i++;
    goto loop_i_to_100__cond;
loop_i_to_100__end:

    printf("%d", sum);
    putchar('\n');

    return 0;
}

Download sum_100_squares.simple.c


Calculate 1*1 + 2*2 + ... + 99*99 + 100*100

UPPER_BOUND = 100


	.text
main:
	# Locals:
	# - $t0: int sum
	# - $t1: int i
	# - $t2: temporary value

	li	$t0, 0					# int sum = 0;

loop_i_to_100__init:
	li	$t1, 1					# int i = 0;
loop_i_to_100__cond:
	bgt	$t1, UPPER_BOUND, loop_i_to_100__end	# while (i < UPPER_BOUND) {
loop_i_to_100__body:
	mul	$t2, $t1, $t1				#   sum = (i * i) +
	add	$t0, $t0, $t2				#         sum;
loop_i_to_100__step:
	addi	$t0, $t0, 1				#   i++;
	b	loop_i_to_100__cond			# }

loop_i_to_100__end:
	li	$v0, 1					# syscall 1: print_int
	move	$a0, $t0				# 
	syscall						# printf("%d", sum);

	li	$v0, 11					# syscall 11: print_char
	li	$a0, '\n'				#
	syscall						# putchar('\n');

	li	$v0, 0
	jr	$ra					# return 0;

Download sum_100_squares.s

Mips Data
#include <stdio.h>

int global_counter = 0;

int main(void) {
    // Increment the global counter.
    // The following is the same as global_counter = global_counter + 1 (generally)
    global_counter++;
    
    printf("%d", global_counter);
    putchar('\n');
}

Download global_increment.c


Increment a global variable.
	.text
main:
	# Locals:
	# - $t0: int *global_counter

	# Method 1: Implicitly load from the 
	# address of global_counter.
	# mipsy will automatically load the address
	# into a register behind the scenes by
	# generating multiple real instructions.
	lw	$t1, global_counter
	addi	$t1, $t1, 1
	sw	$t1, global_counter	# global_counter = global_counter + 1;

	# Method 2: Explicitly load the address of
	# global_counter into a register.
	li	$v0, 1			# syscall 1: print_int
	la	$t0, global_counter	#
	lw	$a0, ($t0)
	syscall				# printf("%d", global_counter);

	li	$v0, 11			# syscall 11: print_char
	li	$a0, '\n'
	syscall				# putchar('\n');

	li	$v0, 0
	jr	$ra			# return 0;

	.data
global_counter:
	.word	0			# int global_counter = 0;

Download global_increment.s

#include <stdio.h>

int x, y, z;

int main(void) {
    x = 17;
    y = 25;
    z = x + y;
    printf("%d", z);
    printf("\n");
    return 0;
}

Download add_memory.c


Add 17 and 25 using variables stored in memory and print result.
main:
	li	$t0, 17		
	la	$t1, x
	sw	$t0, ($t1)	# x = 17;

	li	$t0, 25		
	la	$t1, y
	sw	$t0, ($t1)	# y = 25;

	la	$t0, x
	lw	$t1, ($t0)
	la	$t0, y
	lw	$t2, ($t0)
	add	$t3, $t1, $t2
	la	$t0, z
	sw	$t3, 0($t0)	# z = x + y;

	li	$v0, 1		# syscall 1: print_int		
	la	$t0, z		#
	lw	$a0, 0($t0)	#
	syscall			# printf("%d", z);

	li	$v0, 11		# syscall 11: print_char
	li	$a0, '\n'	#
	syscall			# putchar('\n');

	li	$v0, 0		
	jr	$ra		# return 0;

	.data
x:
	.space	4
y:	
	.space	4
z:
	.space	4

Download add_memory.s


Add 17 and 25 using variables stored in memory and print result.
main:
	la	$t0, x
	lw	$t1, ($t0)
	la	$t0, y  
	lw	$t2, ($t0)

	add	$t3, $t1, $t2
	la	$t0, z
	sw	$t3, 0($t0)	# z = x + y;

	li	$v0, 1		# syscall 1: print_int		
	la	$t0, z		#
	lw	$a0, 0($t0)	#
	syscall			# printf("%d", z);

	li	$v0, 11		# syscall 11: print_char
	li	$a0, '\n'	#
	syscall			# putchar('\n');

	li	$v0, 0		
	jr	$ra		# return 0;

	.data
x:
	.word	17
y:	
	.word	25
z:
	.space	4

Download add_memory_initialized.s

#include <stdio.h>

int x[] = {17, 25, 0};
int main(void) {
    x[2] = x[0] + x[1];
    printf("%d", x[2]);
    printf("\n");
    return 0;
}

Download add_memory_array.c


Add 17 and 25 using variables stored in an array, and print the result.
main:
	la	$t0, x
	lw	$t1, 0($t0)
	lw	$t2, 4($t0)
	add	$t3, $t1, $t2	# x[2] = x[0] + x[1];
	sw	$t3, 8($t0)

	li	$v0, 1		# syscall 1: print_int
	lw	$a0, 8($t0)	#
	syscall			# printf("%d", x[2]);

	li	$v0, 11		# syscall 11: print_char
	li	$a0, '\n'	#
	syscall			# putchar('\n');

	li	$v0, 0
	jr	$ra		# return 0;

	.data

x:	.word 17, 25, 0		# int x[] = {17, 25, 0}

Download add_memory_array.s


Simple example of accessing an array element
#include <stdio.h>

int array[10];

int main(void) {
    array[3] = 17;
}

Download store_array_element.c

main:
	li	$t0, 3			# (3 *
	mul	$t0, $t0, 4		#  sizeof(int)
	la	$t1, x			#  
	add	$t2, $t1, $t0		#  + x) = &x[3]

	li	$t3, 17
	sw	$t3, ($t2)		# x[3] = 17;

	.data
x:	.space	4*10			# int x[10];

Download store_array_element.s

#include <stdio.h>
#include <stdint.h>

int main(void) {
    char bytes[32];
    int *i = (int *)&bytes[1];
    // illegal store - not aligned on a 4-byte boundary
    *i = 42;
    printf("%d\n", *i);
}

Download unalign.c

main:
    li   $t0, 1

    sb   $t0, v1  # will succeed because no alignment needed
    sh   $t0, v1  # will fail because v1 is not 2-byte aligned
    sw   $t0, v1  # will fail because v1 is not 4-byte aligned

    sh   $t0, v2  # will succeeed because v2 is 2-byte aligned
    sw   $t0, v2  # will fail because v2 is not 4-byte aligned

    sh   $t0, v3  # will succeeed because v3 is 2-byte aligned
    sw   $t0, v3  # will fail because v3 is not 4-byte aligned

    sh   $t0, v4  # will succeeed because v4 is 2-byte aligned
    sw   $t0, v4  # will succeeed because v4 is 4-byte aligned

    sw   $t0, v5  # will succeeed because v5 is 4-byte aligned

    sw   $t0, v6  # will succeeed because v6 is 4-byte aligned

    li   $v0, 0
    jr   $ra   # return

    .data
    # data will be aligned on a 4-byte boundary
    # most likely on at least a 128-byte boundary
    # but safer to just add a .align directive
    .align 2
    .space 1
v1: .space 1
v2: .space 4
v3: .space 2
v4: .space 4
    .space 1
    .align 2 # ensure e is on a 4 (2**2) byte boundary
v5: .space 4
    .space 1
v6: .word 0  # word directive aligns on 4 byte boundary

Download unalign.s

access fields of a simple struct
struct details { uint16_t postcode; // Size = 2 bytes, Offset = 0 bytes uint8_t wam; // Size = 1 byte , Offset = 2 bytes // Hidden 1 byte of "padding" // Becase the Offset of each field must be a multiple of the Size of that field uint32_t zid; // Size = 4 bytes, Offset = 4 bytes }; // Total Size = 8 // The Total Size must be a multiple of the Size of the largest field in the struct // More padding will be added to the end of the struct to make this true // (not needed in this example)
offset in bytes of fields of struct details
OFFSET_POSTCODE   = 0
OFFSET_WAM        = 2
OFFSET_ZID        = 4 # unused padding byte before zid field to ensure it is on a 4-byte boundary

main:

    ### Save values into struct ###

    la   $t0, student           # student.postcode = 2052;
    addi $t1, $t0, OFFSET_POSTCODE
    li   $t2, 2052
    sh   $t2, ($t1)

    la   $t0, student           # student.wam = 95;
    addi $t1, $t0, OFFSET_WAM
    li   $t2, 95
    sb   $t2, ($t1)

    la   $t0, student           # student.zid = 5123456
    addi $t1, $t0, OFFSET_ZID
    li   $t2, 5123456
    sw   $t2, ($t1)

    ### Load values from struct ###

    la   $t0, student           # printf("%d", student.zid);
    addi $t1, $t0, OFFSET_ZID
    lw   $a0, ($t1)
    li   $v0, 1
    syscall

    li   $a0, ' '               #   putchar(' ');
    li   $v0, 11
    syscall

    la   $t0, student           # printf("%d", student.wam);
    addi $t1, $t0, OFFSET_WAM
    lbu  $a0, ($t1)
    li   $v0, 1
    syscall

    li   $a0, ' '               #   putchar(' ');
    li   $v0, 11
    syscall

    la   $t0, student           # printf("%d", student.postcode);
    addi $t1, $t0, OFFSET_POSTCODE
    lhu  $a0, ($t1)
    li   $v0, 1
    syscall

    li   $a0, '\n'              #   putchar('\n');
    li   $v0, 11
    syscall

    li   $v0, 0                 # return 0
    jr   $ra

.data

student:                 # struct details student;
     .space 8			 # 1 unused padding byte included to ensure zid field alligned on 4-byte boundary

Download student.s


An example program making use of structs.
#include <stdio.h>

struct student {
    int zid;
    char first[20];
    char last[20];
    int program;
    char alias[10];
};

struct student abiram = {
    .zid = 5308310,
    .first = "Abiram",
    .last = "Nadarajah",
    .program = 3778,
    .alias = "abiramn"
};

struct student xavier = {
    .zid = 5417087,
    .first = "Xavier",
    .last = "Cooney",
    .program = 3778,
    .alias = "xavc"
};

int main(void) {
    struct student *selection = &abiram;

    printf("zID: z%d\n", selection->zid);
    printf("First name: %s\n", selection->first);
    printf("Last name: %s\n", selection->last);
    printf("Program: %d\n", selection->program);
    printf("Alias: %s\n", selection->alias);

    // What's the size of each field of this struct,
    // as well as the overall struct?

    printf("sizeof(zid) = %zu\n", sizeof(selection->zid));
    printf("sizeof(first) = %zu\n", sizeof(selection->first));
    printf("sizeof(last) = %zu\n", sizeof(selection->last));
    printf("sizeof(program) = %zu\n", sizeof(selection->program));
    printf("sizeof(alias) = %zu\n", sizeof(selection->alias));

    // What's the size of the overall struct?
    printf("sizeof(struct student) = %zu\n", sizeof(struct student));

    // We can see that two extra padding bytes were added to the end
    // of the struct, to ensure that the next struct in memory is aligned
    // to a word boundary.

    return 0;

}

Download struct.c


A demo of accessing fields of structs in MIPS.

Offsets for fields in `struct student`
STUDENT_OFFSET_ZID = 0
STUDENT_OFFSET_FIRST = 4
STUDENT_OFFSET_LAST = 20 + STUDENT_OFFSET_FIRST
STUDENT_OFFSET_PROGRAM = 20 + STUDENT_OFFSET_LAST
STUDENT_OFFSET_ALIAS = 4 + STUDENT_OFFSET_PROGRAM

# sizeof the struct - note that there are 2 padding
# bytes at the end of the struct.
SIZEOF_STRUCT_STUDENT = 10 + STUDENT_OFFSET_ALIAS + 2

	.text
main:
	# Locals:
	#  - $t0: struct student *selection

	la	$t0, xavier

	li	$v0, 4				# syscall 4: print_string
	la	$a0, zid_msg			# 
	syscall					# printf("zID: z");

	li	$v0, 1				# syscall 1: print_int
	lw	$a0, STUDENT_OFFSET_ZID($t0)	#
	syscall					# printf("%d", selection->zid);

	li	$v0, 11				# syscall 11: print_char
	li	$a0, '\n'			#
	syscall					# putchar('\n');

	li	$v0, 4				# syscall 4: print_string
	la	$a0, first_name_msg		# 
	syscall					# printf("First name: ");

	li	$v0, 4				# syscall 4: print_string
	la	$a0, STUDENT_OFFSET_FIRST($t0)	#
	syscall					# printf("%s", selection->first);

	li	$v0, 11				# syscall 11: print_char
	li	$a0, '\n'			#
	syscall					# putchar('\n');

	li	$v0, 4				# syscall 4: print_string
	la	$a0, last_name_msg		# 
	syscall					# printf("Last name: ");

	li	$v0, 4				# syscall 4: print_string
	la	$a0, STUDENT_OFFSET_LAST($t0)	#
	syscall					# printf("%s", selection->last);

	li	$v0, 11				# syscall 11: print_char
	li	$a0, '\n'			#
	syscall					# putchar('\n');

	li	$v0, 4				# syscall 4: print_string
	la	$a0, program_msg		# 
	syscall					# printf("Program: ");

	li	$v0, 1				# syscall 1: print_int
	lw	$a0, STUDENT_OFFSET_PROGRAM($t0)#
	syscall					# printf("%d", selection->program);

	li	$v0, 11				# syscall 11: print_char
	li	$a0, '\n'			#
	syscall					# putchar('\n');

	li	$v0, 4				# syscall 4: print_string
	la	$a0, alias_msg			#
	syscall					# printf("Alias: ");

	li	$v0, 4				# syscall 4: print_string
	la 	$a0, STUDENT_OFFSET_ALIAS($t0)	#
	syscall					# printf("%s", selection->alias);

	li	$v0, 11				# syscall 11: print_char
	li	$a0, '\n'			#
	syscall					# putchar('\n');


	li	$v0, 0				#
	jr	$ra				# return 0;
	.data
abiram:						# struct student abiram {
	.word	5308310				#  int zid;
	.asciiz	"Abiram"			#  char first[20];
	.space	20 - 7
	.asciiz "Nadarajah"			#  char last[20];
	.space	20 - 10
	.word	3778				#  int program;
	.asciiz	"abiramn"			#  char alias[10];
	.space	10 - 8
	.align	2
						# }

xavier:						# struct student xavier {
	.word	5417087				#  int zid;
	.asciiz	"Xavier"			#  char first[20];
	.space	20 - 7
	.asciiz "Cooney"			#  char last[20];
	.space	20 - 7
	.word	3778				#  int program;
	.asciiz	"xavc"				#  char alias[10];
	.space	10 - 5
						# }

zid_msg:
	.asciiz "zID: z"

first_name_msg:
	.asciiz "First name: "

last_name_msg:
	.asciiz "Last name: "

program_msg:
	.asciiz "Program: "

alias_msg:
	.asciiz	"Alias: "

Download struct.s

#include <stdio.h>
#include <stdint.h>

struct s1 {
    uint32_t   i0;
    uint32_t   i1;
    uint32_t   i2;
    uint32_t   i3;
};

struct s2 {
    uint8_t    b;
    uint64_t   l;
};

int main(void) {
    struct s1 v1;

    printf("&v1      = %p\n", &v1);
    printf("&(v1.i0) = %p\n", &(v1.i0));
    printf("&(v1.i1) = %p\n", &(v1.i1));
    printf("&(v1.i2) = %p\n", &(v1.i2));
    printf("&(v1.i3) = %p\n", &(v1.i3));

    printf("\nThis shows struct padding\n");

    struct s2 v2;
    printf("&v2      = %p\n", &v2);
    printf("&(v2.b)  = %p\n", &(v2.b));
    printf("&(v2.l)  = %p\n", &(v2.l));
}

Download struct_address.c

$ dcc struct_packing.c -o struct_packing
$ ./struct_packing
sizeof v1 = 32
sizeof v2 = 20
alignment rules mean struct s1 is padded
&(v1.c1) = 0x7ffdfc02f560
&(v1.l1) = 0x7ffdfc02f564
&(v1.c2) = 0x7ffdfc02f568
&(v1.l2) = 0x7ffdfc02f56c
struct s2 is not padded
&(v2.c1) = 0x7ffdfc02f5a0
&(v2.l1) = 0x7ffdfc02f5a4
$


#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

void print_bytes(void *v, int n);

struct s1 {
    uint8_t    c1;
    uint32_t   l1;
    uint8_t    c2;
    uint32_t   l2;
    uint8_t    c3;
    uint32_t   l3;
    uint8_t    c4;
    uint32_t   l4;
};

struct s2 {
    uint32_t   l1;
    uint32_t   l2;
    uint32_t   l3;
    uint32_t   l4;
    uint8_t    c1;
    uint8_t    c2;
    uint8_t    c3;
    uint8_t    c4;
};

int main(void) {
    struct s1 v1;
    struct s2 v2;

    printf("sizeof v1 = %lu\n", sizeof v1);
    printf("sizeof v2 = %lu\n", sizeof v2);

    printf("alignment rules mean struct s1 is padded\n");

    printf("&(v1.c1) = %p\n", &(v1.c1));
    printf("&(v1.l1) = %p\n", &(v1.l1));
    printf("&(v1.c2) = %p\n", &(v1.c2));
    printf("&(v1.l2) = %p\n", &(v1.l2));

    printf("struct s2 is not padded\n");

    printf("&(v1.l1) = %p\n", &(v1.l1));
    printf("&(v1.l2) = %p\n", &(v1.l2));
    printf("&(v1.l4) = %p\n", &(v1.l4));
    printf("&(v2.c1) = %p\n", &(v2.c1));
    printf("&(v2.c2) = %p\n", &(v2.c2));
}

Download struct_packing.c

Mips Functions

C Function with No Parameters or Return Value
#include <stdio.h>

void f(void);

int main(void) {
    printf("calling function f\n");
    f();
    printf("back from function f\n");
    return 0;
}

void f(void) {
    printf("in function f\n");
}

Download call_return.c

simple example of returning from a function loops because main does not save return address
main:
    la   $a0, string0   # printf("calling function f\n");
    li   $v0, 4
    syscall

    jal  f              # set $ra to following address

    la   $a0, string1   # printf("back from function f\n");
    li   $v0, 4
    syscall

    li   $v0, 0         # fails because $ra changes since main called
    jr   $ra            # return from function main


f:
    la   $a0, string2   # printf("in function f\n");
    li   $v0, 4
    syscall
    jr   $ra            # return from function f


    .data
string0:
    .asciiz "calling function f\n"
string1:
    .asciiz "back from function f\n"
string2:
    .asciiz "in function f\n"

Download call_return.broken.s

simple example of placing return address on stack note stack grows down
main:
    addi $sp, $sp, -4    # move stack pointer down to make room
    sw   $ra, 0($sp)    # save $ra on $stack

    la   $a0, string0   # printf("calling function f\n");
    li   $v0, 4
    syscall

    jal  f              # set $ra to following address

    la   $a0, string1   # printf("back from function f\n");
    li   $v0, 4
    syscall

    lw   $ra, 0($sp)    # recover $ra from $stack
    addi $sp, $sp, 4    # move stack pointer back to what it was

    li   $v0, 0         # return 0 from function main
    jr   $ra            #


f:
    la   $a0, string2   # printf("in function f\n");
    li   $v0, 4
    syscall
    jr   $ra            # return from function f


    .data
string0:
    .asciiz "calling function f\n"
string1:
    .asciiz "back from function f\n"
string2:
    .asciiz "in function f\n"

Download call_return_raw.s

simple example of placing return address on stack begin, end, push pop are pseudo-instructions provided by mipsy but not spim
main:
    push $ra            # save $ra on $stack

    la   $a0, string0   # printf("calling function f\n");
    li   $v0, 4
    syscall

    jal  f              # set $ra to following address

    la   $a0, string1   # printf("back from function f\n");
    li   $v0, 4
    syscall

    pop $ra             # recover $ra from $stack

    li   $v0, 0         # return 0 from function main
    jr   $ra            #


# f is a leaf function so it doesn't need an epilogue or prologue
f:
    la   $a0, string2   # printf("in function f\n");
    li   $v0, 4
    syscall
    jr   $ra            # return from function f


    .data
string0:
    .asciiz "calling function f\n"
string1:
    .asciiz "back from function f\n"
string2:
    .asciiz "in function f\n"

Download call_return.s

simple example of returning a value from a function
#include <stdio.h>

int answer(void);

int main(void) {
    int a = answer();
    printf("%d\n", a);
    return 0;
}

int answer(void) {
    return 42;
}

Download return_answer.c

simple example of returning a value from a function note storing of return address $ra
code for function main
main:
    begin               # move frame pointer
    push  $ra           # save $ra onto stack

    jal   answer        # call answer(), return value will be in $v0

    move  $a0, $v0      # printf("%d", a);
    li    $v0, 1        #
    syscall             #

    li    $a0, '\n'     # printf("%c", '\n');
    li    $v0, 11       #
    syscall             #

    pop   $ra           # recover $ra from stack
    end                 # move frame pointer back

    li    $v0, 0        # return
    jr    $ra           #


# code for function answer
answer:
    li   $v0, 42        # return 42
    jr   $ra            #

Download return_answer.s

example of function calls
#include <stdio.h>

int sum_product(int a, int b);
int product(int x, int y);

int main(void) {
    int z = sum_product(10, 12);
    printf("%d\n", z);
    return 0;
}

int sum_product(int a, int b) {
    int p = product(6, 7);
    return p + a + b;
}

int product(int x, int y) {
    return x * y;
}

Download more_calls.c

example of function calls note storing of return address $a0, $a1 and $ra on stack
main:
    begin                # move frame pointer
    push  $ra            # save $ra onto stack

    li   $a0, 10         # sum_product(10, 12);
    li   $a1, 12
    jal  sum_product

    move $a0, $v0        # printf("%d", z);
    li   $v0, 1
    syscall

    li   $a0, '\n'       # printf("%c", '\n');
    li   $v0, 11
    syscall

    pop   $ra            # recover $ra from stack
    end                  # move frame pointer back

    li   $v0, 0          # return 0 from function main
    jr   $ra             # return from function main



sum_product:
    begin                # move frame pointer
    push  $ra            # save $ra onto stack
    push  $s0            # save $s0 onto stack
    push  $s1            # save $s1 onto stack

    move  $s0, $a0       # preserve $a0 for use after function call
    move  $s1, $a1       # preserve $a1 for use after function call

    li    $a0, 6         # product(6, 7);
    li    $a1, 7
    jal   product



    add   $v0, $v0, $s0  # add a and b to value returned in $v0
    add   $v0, $v0, $s1  # and put result in $v0 to be returned

    pop   $s1            # recover $s1 from stack
    pop   $s0            # recover $s0 from stack
    pop   $ra            # recover $ra from stack
    end                  # move frame pointer back

    jr    $ra            # return from sum_product


product:                # product doesn't call other functions
                        # so it doesn't need to save any registers
    mul  $v0, $a0, $a1  # return argument * argument 2
    jr   $ra            #

Download more_calls.s

recursive function which prints first 20 powers of two in reverse
#include <stdio.h>

void two(int i);

int main(void) {
    two(1);
}

void two(int i) {
    if (i < 1000000) {
        two(2 * i);
    }
    printf("%d\n", i);
}

Download two_powerful.c

simple example of placing return address ($ra) and $a0 on the stack recursive function which prints first 20 powers of two in reverse
main:
    begin               # move frame pointer
    push  $ra           # save $ra onto stack

    li    $a0, 1
    jal   two           # two(1);

    pop   $ra           # recover $ra from stack
    end                 # move frame pointer back

    li    $v0, 0        # return 0
    jr    $ra           #


two:
    begin               # move frame pointer
    push  $ra           # save $ra onto stack
    push  $s0           # save $s0 onto stack

    move  $s0, $a0

    bge   $a0, 1000000, two_end_if
    mul   $a0, $a0, 2
    jal   two
two_end_if:

    move  $a0, $s0
    li    $v0, 1        # printf("%d");
    syscall

    li    $a0, '\n'     # printf("%c", '\n');
    li    $v0, 11
    syscall

    pop   $s0           # recover $s0 from stack
    pop   $ra           # recover $ra from stack
    end                 # move frame pointer back

    jr    $ra           # return from two

Download two_powerful.s

example of function where frame pointer useful because stack grows during function execution
#include <stdio.h>

void f(int a) {
    int length;
    scanf("%d", &length);
    int array[length];
    // ... more code ...
    printf("%d\n", a);
}

Download frame_pointer.c

example stack growing during function execution breaking the function return
f:
    addi $sp, $sp, -8    # move stack pointer down to make room
    sw   $ra, 4($sp)     # save $ra on $stack
    sw   $a0, 0($sp)     # save $a0 on $stack

    li   $v0, 5          # scanf("%d", &length);
    syscall

    mul  $v0, $v0, 4     # calculate array size
    sub  $sp, $sp, $v0   # move stack_pointer down to hold array

    # ...

                        # breaks because stack pointer moved down to hold array
                        # so we won't restore the correct value
    lw   $ra, 4($sp)    # restore $ra from $stack
    addi $sp, $sp, 8    # move stack pointer back up to what it was when main called

    jr   $ra            # return from f

Download frame_pointer.broken.s

using a frame pointer to handle stack growing during function execution
f:
    begin                # move frame pointer
    push  $ra            # save $ra on stack

    li    $v0, 5         # scanf("%d", &length);
    syscall

    mul   $v0, $v0, 4    # calculate array size
    sub   $sp, $sp, $v0  # move stack_pointer down to hold array

    # ... more code ...

    pop  $ra             # restore $ra
    end
    jr   $ra             # return

Download frame_pointer.s

Integers
print digits from an integer one per line, reverse order
#include <stdio.h>

int main(int argc, char *argv[]) {
    int num;
    int rem;

    while (1) {  // forever
        // get the number
        printf("Integer? ");
        if (scanf("%d", &num) != 1) break;

        // extract the digits
        rem = num;
        do {
            printf("%d\n", rem % 10);
            rem = rem / 10;
        } while (rem != 0);
    }
    return 0;
}

Download digits.c

print bits from an integer one per line, reverse order
#include <stdio.h>

#define MAXBITS 32

int main(int argc, char *argv[]) {
    int num;
    unsigned int rem;
    int bits[MAXBITS];
    int nbits;

    while (1) {  // forever
        // get the number
        printf("Integer? ");
        if (scanf("%d", &num) != 1) break;

        // extract the digits
        rem = num;
        nbits = 0;
        do {
            bits[nbits] = rem % 2;
            nbits++;
            rem = rem / 2;
        } while (rem != 0);

        printf("%d = %08x = ", num, num);
        for (int i = nbits-1; i >= 0; i--) {
            printf("%d", bits[i]);
        }
        putchar('\n');
    }
    return 0;
}

Download bits.c



Print size and min and max values of integer types
#include <stdio.h>
#include <limits.h>

int main(void) {

    char c;
    signed char sc;
    unsigned char uc;
    short s;
    unsigned short us;
    int i;
    unsigned int ui;
    long l;
    unsigned long ul;
    long long ll;
    unsigned long long ull;

    printf("%18s %5s %4s\n", "Type", "Bytes", "Bits");

    printf("%18s %5lu %4lu\n", "char",               sizeof c,   8 * sizeof c);

    printf("%18s %5lu %4lu\n", "signed char",        sizeof sc,  8 * sizeof sc);
    printf("%18s %5lu %4lu\n", "unsigned char",      sizeof uc,  8 * sizeof uc);

    printf("%18s %5lu %4lu\n", "short",              sizeof s,   8 * sizeof s);
    printf("%18s %5lu %4lu\n", "unsigned short",     sizeof us,  8 * sizeof us);

    printf("%18s %5lu %4lu\n", "int",                sizeof i,   8 * sizeof i);
    printf("%18s %5lu %4lu\n", "unsigned int",       sizeof ui,  8 * sizeof ui);

    printf("%18s %5lu %4lu\n", "long",               sizeof l,   8 * sizeof l);
    printf("%18s %5lu %4lu\n", "unsigned long",      sizeof ul,  8 * sizeof ul);

    printf("%18s %5lu %4lu\n", "long long",          sizeof ll,  8 * sizeof ll);
    printf("%18s %5lu %4lu\n", "unsigned long long", sizeof ull, 8 * sizeof ull);

    printf("\n");

    printf("%18s %20s %20s\n", "Type", "Min", "Max");

#ifdef __CHAR_UNSIGNED__
    printf("%18s %20hhu %20hhu\n", "char",               (char)CHAR_MIN,         (char)CHAR_MAX);
#else
    printf("%18s %20hhd %20hhd\n", "char",               (char)CHAR_MIN,         (char)CHAR_MAX);
#endif

    printf("%18s %20hhd %20hhd\n", "signed char",        (signed char)SCHAR_MIN, (signed char)SCHAR_MAX);
    printf("%18s %20hhu %20hhu\n", "unsigned char",      (unsigned char)0,       (unsigned char)UCHAR_MAX);

    printf("%18s %20hd %20hd\n",   "short",              (short)SHRT_MIN,        (short)SHRT_MAX);
    printf("%18s %20hu %20hu\n",   "unsigned short",     (unsigned short)0,      (unsigned short)USHRT_MAX);

    printf("%18s %20d %20d\n",     "int",                INT_MIN,                INT_MAX);
    printf("%18s %20u %20u\n",     "unsigned int",       (unsigned int)0,        UINT_MAX);

    printf("%18s %20ld %20ld\n",   "long",               LONG_MIN,               LONG_MAX);
    printf("%18s %20lu %20lu\n",   "unsigned long",      (unsigned long)0,       ULONG_MAX);

    printf("%18s %20lld %20lld\n", "long long",          LLONG_MIN,              LLONG_MAX);
    printf("%18s %20llu %20llu\n", "unsigned long long", (unsigned long long)0,  ULLONG_MAX);

    return 0;
}

Download integer_types.c


example declarations of the most commonly used fixed width integer types found in stdint.h
#include <stdint.h>

int main(void) {

                 // range of values for type
                 //             minimum               maximum
    int8_t   i1; //                 -128                  127
    uint8_t  i2; //                    0                  255
    int16_t  i3; //               -32768                32767
    uint16_t i4; //                    0                65535
    int32_t  i5; //          -2147483648           2147483647
    uint32_t i6; //                    0           4294967295
    int64_t  i7; // -9223372036854775808  9223372036854775807
    uint64_t i8; //                    0 18446744073709551615

    return 0;
}

Download stdint.c


#include <stdio.h>

int main(void) {

    // Common C bug:

    char c;  // c should be declared int   (int16_t would work, int is better)
    while ((c = getchar()) != EOF) {
        putchar(c);
    }

    // Typically `stdio.h` contains:
    // ```c
    // #define EOF -1
    // ```
    //
    // - most platforms: char is signed (-128..127)
    //   - loop will incorrectly exit for a byte containing 0xFF
    //
    // - rare platforms: char is unsigned (0..255)
    //   - loop will never exit

    return 0;
}

Download char_bug.c

two useful functions that we will use in a number of following programs
#include <stdio.h>
#include <stdint.h>

#include "print_bits.h"

// extract the nth bit from a value
int get_nth_bit(uint64_t value, int n) {
    // shift the bit right n bits
    // this leaves the n-th bit as the least significant bit
    uint64_t shifted_value = value >> n;

    // zero all bits except the the least significant bit
    int bit = shifted_value & 1;

    return bit;
}

// print the bottom how_many_bits bits of value
void print_bits(uint64_t value, int how_many_bits) {
    // print bits from most significant to least significant

    for (int i = how_many_bits - 1; i >= 0; i--) {
        int bit = get_nth_bit(value, i);
        printf("%d", bit);
    }
}

Download print_bits.c


print the bits of an int, for example:
```
$ dcc print_bits_of_int.c print_bits.c -o print_bits_of_int
$ ./print_bits_of_int

Enter an int: 42 00000000000000000000000000101010 $ ./print_bits_of_int
Enter an int: -42 11111111111111111111111111010110 $ ./print_bits_of_int
Enter an int: 0 00000000000000000000000000000000 $ ./print_bits_of_int
Enter an int: 1 00000000000000000000000000000001 $ ./print_bits_of_int
Enter an int: -1 11111111111111111111111111111111 $ ./print_bits_of_int
Enter an int: 2147483647 01111111111111111111111111111111 $ ./print_bits_of_int
Enter an int: -2147483648 10000000000000000000000000000000 $ ```

#include <stdio.h>
#include <stdint.h>
#include "print_bits.h"

int main(void) {
    int a = 0;
    printf("Enter an int: ");
    scanf("%d", &a);

    // sizeof returns number of bytes, a byte has 8 bits
    int n_bits = 8 * sizeof a;

    print_bits(a, n_bits);
    printf("\n");

    return 0;
}

Download print_bits_of_int.c


print the twos-complement representation of 8 bit signed integers essentially all modern machines represent integers in
```
$ dcc 8_bit_twos_complement.c print_bits.c -o 8_bit_twos_complement
$ ./8_bit_twos_complement
-128 10000000
-127 10000001
-126 10000010
-125 10000011
-124 10000100
-123 10000101
-122 10000110
-121 10000111
-120 10001000
-119 10001001
-118 10001010
-117 10001011
-116 10001100
-115 10001101
-114 10001110
-113 10001111
-112 10010000
-111 10010001
-110 10010010
-109 10010011
-108 10010100
-107 10010101
-106 10010110
-105 10010111
-104 10011000
-103 10011001
-102 10011010
-101 10011011
-100 10011100
 -99 10011101
 -98 10011110
 -97 10011111
 -96 10100000
 -95 10100001
 -94 10100010
 -93 10100011
 -92 10100100
 -91 10100101
 -90 10100110
 -89 10100111
 -88 10101000
 -87 10101001
 -86 10101010
 -85 10101011
 -84 10101100
 -83 10101101
 -82 10101110
 -81 10101111
 -80 10110000
 -79 10110001
 -78 10110010
 -77 10110011
 -76 10110100
 -75 10110101
 -74 10110110
 -73 10110111
 -72 10111000
 -71 10111001
 -70 10111010
 -69 10111011
 -68 10111100
 -67 10111101
 -66 10111110
 -65 10111111
 -64 11000000
 -63 11000001
 -62 11000010
 -61 11000011
 -60 11000100
 -59 11000101
 -58 11000110
 -57 11000111
 -56 11001000
 -55 11001001
 -54 11001010
 -53 11001011
 -52 11001100
 -51 11001101
 -50 11001110
 -49 11001111
 -48 11010000
 -47 11010001
 -46 11010010
 -45 11010011
 -44 11010100
 -43 11010101
 -42 11010110
 -41 11010111
 -40 11011000
 -39 11011001
 -38 11011010
 -37 11011011
 -36 11011100
 -35 11011101
 -34 11011110
 -33 11011111
 -32 11100000
 -31 11100001
 -30 11100010
 -29 11100011
 -28 11100100
 -27 11100101
 -26 11100110
 -25 11100111
 -24 11101000
 -23 11101001
 -22 11101010
 -21 11101011
 -20 11101100
 -19 11101101
 -18 11101110
 -17 11101111
 -16 11110000
 -15 11110001
 -14 11110010
 -13 11110011
 -12 11110100
 -11 11110101
 -10 11110110
  -9 11110111
  -8 11111000
  -7 11111001
  -6 11111010
  -5 11111011
  -4 11111100
  -3 11111101
  -2 11111110
  -1 11111111
   0 00000000
   1 00000001
   2 00000010
   3 00000011
   4 00000100
   5 00000101
   6 00000110
   7 00000111
   8 00001000
   9 00001001
  10 00001010
  11 00001011
  12 00001100
  13 00001101
  14 00001110
  15 00001111
  16 00010000
  17 00010001
  18 00010010
  19 00010011
  20 00010100
  21 00010101
  22 00010110
  23 00010111
  24 00011000
  25 00011001
  26 00011010
  27 00011011
  28 00011100
  29 00011101
  30 00011110
  31 00011111
  32 00100000
  33 00100001
  34 00100010
  35 00100011
  36 00100100
  37 00100101
  38 00100110
  39 00100111
  40 00101000
  41 00101001
  42 00101010
  43 00101011
  44 00101100
  45 00101101
  46 00101110
  47 00101111
  48 00110000
  49 00110001
  50 00110010
  51 00110011
  52 00110100
  53 00110101
  54 00110110
  55 00110111
  56 00111000
  57 00111001
  58 00111010
  59 00111011
  60 00111100
  61 00111101
  62 00111110
  63 00111111
  64 01000000
  65 01000001
  66 01000010
  67 01000011
  68 01000100
  69 01000101
  70 01000110
  71 01000111
  72 01001000
  73 01001001
  74 01001010
  75 01001011
  76 01001100
  77 01001101
  78 01001110
  79 01001111
  80 01010000
  81 01010001
  82 01010010
  83 01010011
  84 01010100
  85 01010101
  86 01010110
  87 01010111
  88 01011000
  89 01011001
  90 01011010
  91 01011011
  92 01011100
  93 01011101
  94 01011110
  95 01011111
  96 01100000
  97 01100001
  98 01100010
  99 01100011
 100 01100100
 101 01100101
 102 01100110
 103 01100111
 104 01101000
 105 01101001
 106 01101010
 107 01101011
 108 01101100
 109 01101101
 110 01101110
 111 01101111
 112 01110000
 113 01110001
 114 01110010
 115 01110011
 116 01110100
 117 01110101
 118 01110110
 119 01110111
 120 01111000
 121 01111001
 122 01111010
 123 01111011
 124 01111100
 125 01111101
 126 01111110
 127 01111111
$
```

#include <stdio.h>
#include <stdint.h>
#include "print_bits.h"

int main(void) {

    for (int i = -128; i < 128; i++) {
        printf("%4d ", i);
        print_bits(i, 8);
        printf("\n");
    }

    return 0;
}

Download 8_bit_twos_complement.c

#include <stdio.h>
#include <stdint.h>

int main(void) {
    uint8_t b;
    uint32_t u;

    u = 0x03040506;
    // load first byte of u
    b = *(uint8_t *)&u;
    // prints 6 if little-endian
    // and 3 if big-endian
    printf("%d\n", b);
}

Download endian.c

main:
    lbu  $a0, u      # b = *(uint8_t *)&u;
    li   $v0, 1      # printf("%d", a0);
    syscall

    li   $a0, '\n'   # printf("%c", '\n');
    li   $v0, 11
    syscall

    li   $v0, 0      # return 0
    jr   $ra

    .data
u:
    .word 0x3040506  #u = 0x03040506;

Download endian.s

Bitwise Operations
two useful functions that we will use in a number of following programs
#include <stdio.h>
#include <stdint.h>

#include "print_bits.h"

// extract the nth bit from a value
int get_nth_bit(uint64_t value, int n) {
    // shift the bit right n bits
    // this leaves the n-th bit as the least significant bit
    uint64_t shifted_value = value >> n;

    // zero all bits except the the least significant bit
    int bit = shifted_value & 1;

    return bit;
}

// print the bottom how_many_bits bits of value
void print_bits(uint64_t value, int how_many_bits) {
    // print bits from most significant to least significant

    for (int i = how_many_bits - 1; i >= 0; i--) {
        int bit = get_nth_bit(value, i);
        printf("%d", bit);
    }
}

Download print_bits.c



Demonstrate that shifting the bits of a positive int left 1 position is equivalent to multiplying by 2
```
$ dcc shift_as_multiply.c print_bits.c -o shift_as_multiply
$ ./shift_as_multiply 4
2 to the power of 4 is 16

In binary it is: 00000000000000000000000000010000 $ ./shift_as_multiply 20 2 to the power of 20 is 1048576
In binary it is: 00000000000100000000000000000000 $ ./shift_as_multiply 31 2 to the power of 31 is 2147483648
In binary it is: 10000000000000000000000000000000 $ ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <exponent>\n", argv[0]);
        return 1;
    }

    int n = strtol(argv[1], NULL, 0);

    uint32_t power_of_two;

    int n_bits = 8 * sizeof power_of_two;

    if (n >= n_bits) {
        fprintf(stderr, "n is too large\n");
        return 1;
    }

    power_of_two = 1;
    power_of_two = power_of_two << n;

    printf("2 to the power of %d is %u\n", n, power_of_two);

    printf("In binary it is: ");
    print_bits(power_of_two, n_bits);
    printf("\n");

    return 0;
}

Download shift_as_multiply.c



Demonstrate use shift operators and subtraction to obtain a bit pattern of n 1s
```
$ dcc set_low_bits.c print_bits.c -o n_ones
$ ./set_low_bits 3

The bottom 3 bits of 7 are ones: 00000000000000000000000000000111 $ ./set_low_bits 19
The bottom 19 bits of 524287 are ones: 00000000000001111111111111111111 $ ./set_low_bits 29
The bottom 29 bits of 536870911 are ones: 00011111111111111111111111111111 ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "assert.h"

#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <exponent>\n", argv[0]);
        return 1;
    }

    int n = strtol(argv[1], NULL, 0);

    uint32_t mask;

    int n_bits = 8 * sizeof mask;

    assert(n >= 0 && n < n_bits);

    mask = 1;
    mask = mask << n;
    mask = mask - 1;

    printf("The bottom %d bits of %u are ones:\n", n, mask);
    print_bits(mask, n_bits);
    printf("\n");

    return 0;
}

Download set_low_bits.c



Demonstrate use shift operators and subtraction to obtain a bit pattern with a range of bits set.
```
$ dcc set_bit_range.c print_bits.c -o set_bit_range
$ ./set_bit_range 0 7

Bits 0 to 7 of 255 are ones: 00000000000000000000000011111111 $ ./set_bit_range 8 15
Bits 8 to 15 of 65280 are ones: 00000000000000001111111100000000 $ ./set_bit_range 8 23
Bits 8 to 23 of 16776960 are ones: 00000000111111111111111100000000 $ ./set_bit_range 1 30
Bits 1 to 30 of 2147483646 are ones: 01111111111111111111111111111110 ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <low-bit> <high-bit>\n", argv[0]);
        return 1;
    }

    int low_bit = strtol(argv[1], NULL, 0);
    int high_bit = strtol(argv[2], NULL, 0);

    uint32_t mask;

    int n_bits = 8 * sizeof mask;

    assert(low_bit >= 0);
    assert(high_bit >= low_bit);
    assert(high_bit < n_bits);

    int mask_size = high_bit - low_bit + 1;

    mask = 1;
    mask = mask << mask_size;
    mask = mask - 1;
    mask = mask << low_bit;

    printf("Bits %d to %d of %u are ones:\n", low_bit, high_bit, mask);
    print_bits(mask, n_bits);
    printf("\n");

    return 0;
}

Download set_bit_range.c



Demonstrate use shift operators and subtraction to extract a bit pattern with a range of bits set.
```
$ dcc extract_bit_range.c print_bits.c -o extract_bit_range
$ ./extract_bit_range 4 7 42

Value 42 in binary is: 00000000000000000000000000101010
Bits 4 to 7 of 42 are: 0010 $ ./extract_bit_range 10 20 123456789
Value 123456789 in binary is: 00000111010110111100110100010101
Bits 10 to 20 of 123456789 are: 11011110011 ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "Usage: %s <low-bit> <high-bit> <value>\n", argv[0]);
        return 1;
    }

    int low_bit = strtol(argv[1], NULL, 0);
    int high_bit = strtol(argv[2], NULL, 0);
    uint32_t value = strtol(argv[3], NULL, 0);

    uint32_t mask;

    int n_bits = 8 * sizeof mask;

    assert(low_bit >= 0);
    assert(high_bit >= low_bit);
    assert(high_bit < n_bits);

    int mask_size = high_bit - low_bit + 1;

    mask = 1;
    mask = mask << mask_size;
    mask = mask - 1;
    mask = mask << low_bit;

    // get a value with the bits outside the range low_bit..high_bit set to zero
    uint32_t extracted_bits = value & mask;

    // right shift the extracted_bits so low_bit becomes bit 0
    extracted_bits = extracted_bits >> low_bit;

    printf("Value %u in binary is:\n", value);
    print_bits(value, n_bits);
    printf("\n");

    printf("Bits %d to %d of %u are:\n", low_bit, high_bit, value);
    print_bits(extracted_bits, mask_size);
    printf("\n");

    return 0;
}

Download extract_bit_range.c



Examples of illegal bit-shift operations
#include <stdio.h>
#include <stdint.h>

int main(void) {
    // int16_t is a signed type (-32768..32767)
    // below operations are undefined for a signed type
    int16_t i;

    i = -1;
    i = i >> 1; // undefined -  shift of a negative value
    printf("%d\n", i);

    i = -1;
    i = i << 1; // undefined -  shift of a negative value
    printf("%d\n", i);

    i = 32767;
    i = i << 1; // undefined -  left shift produces a negative value

    uint64_t j;
    j = 1 << 33; // undefined - constant 1 is an int
    j = ((uint64_t)1) << 33; // ok

    return 0;
}

Download shift_bug.c

copy stdin to stdout xor'ing each byte with value given as argument
```
$ echo Hello Andrew|xor 42
bOFFE
kDNXO] $ echo Hello Andrew|xor 42|cat -A
bOFFE$
kDNXO] $
$  echo Hello |xor 42
bOFFE $ echo -n 'bOFFE '|xor 42

Hello $ echo Hello|xor 123|xor 123
Hello $ ```

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <xor-value>\n", argv[0]);
        return 1;
    }

    int xor_value = strtol(argv[1], NULL, 0);

    if (xor_value < 0 || xor_value > 255) {
        fprintf(stderr, "Usage: %s <xor-value>\n", argv[0]);
        return 1;
    }

    int c;
    while ((c = getchar()) != EOF) {
        //    exclusive-or
        //    ^  | 0  1
        //   ----|-----
        //    0  | 0  1
        //    1  | 1  0

        int xor_c = c ^ xor_value;

        putchar(xor_c);
    }

    return 0;
}

Download xor.c

Floating Point

Print size and min and max values of floating point types
```
$ ./floating_types
float        4 bytes  min=1.17549e-38   max=3.40282e+38
double       8 bytes  min=2.22507e-308  max=1.79769e+308
long double 16 bytes  min=3.3621e-4932  max=1.18973e+4932
```

#include <stdio.h>
#include <float.h>

int main(void) {

    float f;
    double d;
    long double l;
    printf("float       %2lu bytes  min=%-12g  max=%g\n", sizeof f, FLT_MIN, FLT_MAX);
    printf("double      %2lu bytes  min=%-12g  max=%g\n", sizeof d, DBL_MIN, DBL_MAX);
    printf("long double %2lu bytes  min=%-12Lg  max=%Lg\n", sizeof l, LDBL_MIN, LDBL_MAX);

    return 0;
}

Download floating_types.c


#include <stdio.h>
#include <math.h>

int main(void) {

    double x = 1.0/0.0;

    printf("%lf\n", x); //prints inf

    printf("%lf\n", -x); //prints -inf

    printf("%lf\n", x - 1); // prints inf

    printf("%lf\n", 2 * atan(x)); // prints 3.141593

    printf("%d\n", 42 < x); // prints 1 (true)

    printf("%d\n", x == INFINITY); // prints 1 (true)

    return 0;
}

Download infinity.c


#include <stdio.h>
#include <math.h>

int main(void) {

    double x = 0.0/0.0;

    printf("%lf\n", x); //prints nan

    printf("%lf\n", x - 1); // prints nan

    printf("%d\n", x == x); // prints 0 (false)

    printf("%d\n", isnan(x)); // prints 1 (true)

    return 0;
}

Download nan.c



The value 0.1 can not be precisely represented as a double
As a result b != 0
#include <stdio.h>

int main(void) {
    double a, b;

    a = 0.1;
    b = 1 - (a + a + a + a + a + a + a + a + a + a);

    if (b != 0) {  // better would be fabs(b) > 0.000001
        printf("1 != 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1\n");
    }

    printf("b = %g\n", b); // prints 1.11022e-16

    return 0;
}

Download double_imprecision.c



Demonstrate approximate representation of reals producing error. sometimes if we subtract or divide two approximations which are very close together we can can get a large relative error correct answer if x == 0.000000011 (1 - cos(x)) / (x * x) is very close to 0.5 code prints 0.917540 which is wrong by a factor of almost two
#include <stdio.h>
#include <math.h>

int main(void) {

    double x = 0.000000011;
    double y = (1 - cos(x)) / (x * x);

    // correct answer y = ~0.5
    // prints y = 0.917540
    printf("y = %lf\n", y);

    // division of similar approximate value
    // produces large error
    // sometimes called catastrophic cancellation
    printf("%g\n", 1 - cos(x)); // prints  1.11022e-16
    printf("%g\n", x * x); // prints 1.21e-16
    return 0;
}

Download double_catastrophe.c


- 9007199254740993 is $2^{53} + 1$ \
  it is smallest integer which can not be represented exactly as a double
- The closest double to 9007199254740993 is 9007199254740992.0
- aside: 9007199254740993 can not be represented by a int32_t \
  it can be represented by int64_t

#include <stdio.h>

int main(void) {


    // loop looks to print 10 numbers but actually never terminates
    double d = 9007199254740990;
    while (d < 9007199254741000) {
        printf("%lf\n", d); // always prints 9007199254740992.000000

        // 9007199254740993 can not be represented as a double
        // closest double is 9007199254740992.0
        // so 9007199254740992.0 + 1 = 9007199254740992.0
        d = d + 1;
    }

    return 0;
}

Download double_disaster.c

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/*```
$ dcc double_not_always.c -o double_not_always
$ ./double_not_always 42.3
d = 42.3
d == d is true
d == d + 1 is false
$  ./double_not_always 4200000000000000000
d = 4.2e+18
d == d is true
d == d + 1 is true
$ ./double_not_always NaN
d = nan
d == d is not true
d == d + 1 is false
````*/

int main(int argc, char *argv[]) {
    assert(argc == 2);

    double d = strtod(argv[1], NULL);

    printf("d = %g\n", d);

    if (d == d) {
        printf("d == d is true\n");
    } else {
        // will be executed if d is a NaN
        printf("d == d is not true\n");
    }

    if (d == d + 1) {
        // may be executed if d is large
        // because closest possible representation for d + 1
        // is also closest possible representation for d
        printf("d == d + 1 is true\n");
    } else {
        printf("d == d + 1 is false\n");
    }

    return 0;
}

Download double_not_always.c



Print the underlying representation of a float
The float can be supplied as a decimal or a bit-string
$ dcc explain_float_representation.c -o explain_float_representation
$ ./explain_float_representation

0.15625 is represented in IEEE-754 single-precision by these bits:
00111110001000000000000000000000
sign | exponent | fraction 0 | 01111100 | 01000000000000000000000
sign bit = 0 sign = +
raw exponent = 01111100 binary = 124 decimal actual exponent = 124 - exponent_bias = 124 - 127 = -3
number = +1.01000000000000000000000 binary * 2**-3 = 1.25 decimal * 2**-3 = 1.25 * 0.125 = 0.15625
$ ./explain_float_representation -0.125
-0.125 is represented as a float (IEEE-754 single-precision) by these bits:
10111110000000000000000000000000
sign | exponent | fraction 1 | 01111100 | 00000000000000000000000
sign bit = 1 sign = -
raw exponent = 01111100 binary = 124 decimal actual exponent = 124 - exponent_bias = 124 - 127 = -3
number = -1.00000000000000000000000 binary * 2**-3 = -1 decimal * 2**-3 = -1 * 0.125 = -0.125
$ ./explain_float_representation 150.75
150.75 is represented in IEEE-754 single-precision by these bits:
01000011000101101100000000000000
sign | exponent | fraction 0 | 10000110 | 00101101100000000000000
sign bit = 0 sign = +
raw exponent = 10000110 binary = 134 decimal actual exponent = 134 - exponent_bias = 134 - 127 = 7
number = +1.00101101100000000000000 binary * 2**7 = 1.17773 decimal * 2**7 = 1.17773 * 128 = 150.75
$ ./explain_float_representation -96.125
-96.125 is represented in IEEE-754 single-precision by these bits:
11000010110000000100000000000000
sign | exponent | fraction 1 | 10000101 | 10000000100000000000000
sign bit = 1 sign = -
raw exponent = 10000101 binary = 133 decimal actual exponent = 133 - exponent_bias = 133 - 127 = 6
number = -1.10000000100000000000000 binary * 2**6 = -1.50195 decimal * 2**6 = -1.50195 * 64 = -96.125
$ ./explain_float_representation inf
inf is represented in IEEE-754 single-precision by these bits:
01111111100000000000000000000000
sign | exponent | fraction 0 | 11111111 | 00000000000000000000000
sign bit = 0 sign = +
raw exponent = 11111111 binary = 255 decimal number = +inf
$ ./explain_float_representation 00111101110011001100110011001101 sign bit = 0 sign = +
raw exponent = 01111011 binary = 123 decimal actual exponent = 123 - exponent_bias = 123 - 127 = -4
number = +1.10011001100110011001101 binary * 2**-4 = 1.6 decimal * 2**-4 = 1.6 * 0.0625 = 0.1
$ ./explain_float_representation 01111111110000000000000000000000 sign bit = 0 sign = +
raw exponent = 11111111 binary = 255 decimal number = NaN $ ```

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <float.h>
#include <string.h>

void display_float(char *argument);
uint32_t get_float_bits(float f);
void print_float_bits(uint32_t bits);
void print_bit_range(uint32_t value, int high, int low);
void print_float_details(uint32_t bits);
uint32_t extract_bit_range(uint32_t value, int high, int low);
uint32_t convert_bitstring_to_uint32(char *bit_string);

int main(int argc, char *argv[]) {
    for (int arg = 1; arg < argc; arg++) {
        display_float(argv[arg]);
    }
    return 0;
}

// Define the constants used in representation of a float in IEEE 754 single-precision
// https://en.wikipedia.org/wiki/Single-precision_floating-point_format
// explains format

#define N_BITS             32
#define SIGN_BIT           31
#define EXPONENT_HIGH_BIT  30
#define EXPONENT_LOW_BIT   23
#define FRACTION_HIGH_BIT  22
#define FRACTION_LOW_BIT    0

#define EXPONENT_OFFSET   127
#define EXPONENT_INF_NAN  255

void display_float(char *argument) {
    uint32_t bits;

    // is this argument a bit string or a float?
    if (strlen(argument) > N_BITS - 4 && strspn(argument, "01") == N_BITS) {
        bits = convert_bitstring_to_uint32(argument);
    } else {
        float number = strtof(argument, NULL);
        bits = get_float_bits(number);
        printf("\n%s is represented as IEEE-754 single-precision by these bits:\n\n", argument);
        print_float_bits(bits);
    }

    print_float_details(bits);
}

void print_float_details(uint32_t bits) {
    uint32_t sign_bit = extract_bit_range(bits, SIGN_BIT, SIGN_BIT);
    uint32_t fraction_bits = extract_bit_range(bits, FRACTION_HIGH_BIT, FRACTION_LOW_BIT);
    uint32_t exponent_bits = extract_bit_range(bits, EXPONENT_HIGH_BIT, EXPONENT_LOW_BIT);

    int sign_char, sign_value;

    if (sign_bit == 1) {
        sign_char = '-';
        sign_value = -1;
    } else {
        sign_char = '+';
        sign_value = 1;
    }

    int exponent = exponent_bits - EXPONENT_OFFSET;

    printf("sign bit = %d\n", sign_bit);
    printf("sign = %c\n\n", sign_char);
    printf("raw exponent    = ");
    print_bit_range(bits, EXPONENT_HIGH_BIT, EXPONENT_LOW_BIT);
    printf(" binary\n");
    printf("                = %d decimal\n", exponent_bits);

    int implicit_bit = 1;

    // handle special cases of +infinity, -infinity
    // and Not a Number (NaN)
    if (exponent_bits == EXPONENT_INF_NAN) {
        if (fraction_bits == 0) {
            printf("number = %cinf\n\n", sign_char);
        } else {
            // https://en.wikipedia.org/wiki/NaN
            printf("number = NaN\n\n");
        }
        return;
    }

    if (exponent_bits == 0) {
        // if the exponent_bits are zero its a special case
        // called a denormal number
        // https://en.wikipedia.org/wiki/Denormal_number
        implicit_bit = 0;
        exponent++;
    }

    printf("actual exponent = %d - exponent_bias\n", exponent_bits);
    printf("                = %d - %d\n", exponent_bits, EXPONENT_OFFSET);
    printf("                = %d\n\n", exponent);

    printf("number = %c%d.", sign_char, implicit_bit);
    print_bit_range(bits, FRACTION_HIGH_BIT, FRACTION_LOW_BIT);
    printf(" binary * 2**%d\n", exponent);

    int fraction_size = FRACTION_HIGH_BIT - FRACTION_LOW_BIT + 1;
    double fraction_max = ((uint32_t)1) << fraction_size;
    double fraction = implicit_bit + fraction_bits / fraction_max;

    fraction *= sign_value;

    printf("       = %g decimal * 2**%d\n", fraction,  exponent);
    printf("       = %g * %g\n", fraction, exp2(exponent));
    printf("       = %g\n\n", fraction * exp2(exponent));
}

union overlay_float {
    float f;
    uint32_t u;
};

// return the raw bits of a float
uint32_t get_float_bits(float f) {
    union overlay_float overlay;
    overlay.f = f;
    return overlay.u;
}

// print out the bits of a float
void print_float_bits(uint32_t bits) {
    print_bit_range(bits, 8 * sizeof bits - 1, 0);
    printf("\n\n");
    printf("sign | exponent | fraction\n");
    printf("   ");
    print_bit_range(bits, SIGN_BIT, SIGN_BIT);
    printf(" | ");
    print_bit_range(bits, EXPONENT_HIGH_BIT, EXPONENT_LOW_BIT);
    printf(" | ");
    print_bit_range(bits, FRACTION_HIGH_BIT, FRACTION_LOW_BIT);
    printf("\n\n");
}

// print the binary representation of a value
void print_bit_range(uint32_t value, int high, int low) {
    for (int i = high; i >= low; i--) {
        int bit = extract_bit_range(value, i, i);
        printf("%d", bit);
    }
}

// extract a range of bits from a value
uint32_t extract_bit_range(uint32_t value, int high, int low) {
    uint32_t mask = (((uint32_t)1) << (high - low + 1)) - 1;
    return (value >> low) & mask;
}

// given a string of 1s and 0s return the correspong uint32_t
uint32_t convert_bitstring_to_uint32(char *bit_string) {
    uint32_t bits = 0;
    for (int i = 0; i < N_BITS && bit_string[i] != '\0'; i++) {
        int ascii_char = bit_string[N_BITS - 1 - i];
        uint32_t bit = ascii_char != '0';
        bits = bits | (bit << i);
    }
    return bits;
}

Download explain_float_representation.c

Files
hello world implemented with a direct syscall

This isn't portable or readable but shows us what system calls look like.
#include <unistd.h>

int main(void) {
    char bytes[13] = "Hello, Zac!\n";

    // argument 1 to syscall is the system call number, 1 is write
    // remaining arguments are specific to each system call

    // write system call takes 3 arguments:
    //   1) file descriptor, 1 == stdout
    //   2) memory address of first byte to write
    //   3) number of bytes to write

    syscall(1, 1, bytes, 12); // prints Hello, Zac! on stdout

    return 0;
}

Download hello_syscalls.c


#include <unistd.h>

#define O_RDONLY 00
#define O_WRONLY 01
#define O_CREAT  0100
#define O_TRUNC  01000

// cp <file1> <file2> with syscalls and no error handling
int main(int argc, char *argv[]) {

    // system call number 2 is open, takes 3 arguments:
    //   1) address of zero-terminated string containing file pathname
    //   2) bitmap indicating whether to write, read, ... file
    //      O_WRONLY | O_CREAT == 0x41 == write to file, creating if necessary
    //   3) permissions if file will be newly created
    //      0644 == readable to everyone, writeable by owner

    long read_file_descriptor  = syscall(2, argv[1], O_RDONLY,           0);
    long write_file_descriptor = syscall(2, argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);

    while (1) {

        // system call number 0 is read -  takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to put bytes read
        //   3) maximum number of bytes read
        // returns number of bytes actually read
        char bytes[4096];
        long bytes_read = syscall(0, read_file_descriptor, bytes, 4096);

        if (bytes_read <= 0) {
            break;
        }

        // system call number 1 is write - takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to take bytes from
        //   3) number of bytes to written
        // returns number of bytes actually written

        syscall(1, write_file_descriptor, bytes, bytes_read);
    }

    return 0;
}

Download cp_syscalls.c

hello world implemented with libc
#include <unistd.h>

int main(void) {
    char bytes[13] = "Hello, Zac!\n";

    // write takes 3 arguments:
    //   1) file descriptor, 1 == stdout
    //   2) memory address of first byte to write
    //   3) number of bytes to write

    write(1, bytes, 12); // prints Hello, Zac! on stdout

    return 0;
}

Download hello_libc.c

cp <file1> <file2> implemented with libc and *zero* error handling
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[]) {
    // copy bytes one at a time from  pathname passed as
    // command-line argument 1 to pathname given as argument 2
    int read_file_descriptor = open(argv[1], O_RDONLY);
    int write_file_descriptor = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    while (1) {
        char bytes[1];
        ssize_t bytes_read = read(read_file_descriptor, bytes, 1);
        if (bytes_read <= 0) {
            break;
        }
        write(write_file_descriptor, bytes, 1);
    }

    return 0;
}

Download cp_libc_one_byte.c

6 ways to print Hello, stdio!
#include <stdio.h>

int main(void) {
    char bytes[] = "Hello, stdio!\n"; // 15 bytes

    // write 14 bytes so we don't write (terminating) 0 byte
    for (int i = 0; i < (sizeof bytes) - 1; i++) {
        fputc(bytes[i], stdout);
    }

    // or as we know bytes is 0-terminated
    for (int i = 0; bytes[i] != '\0'; i++) {
        fputc(bytes[i], stdout);
    }

    // or if you prefer pointers
    for (char *p = &bytes[0]; *p != '\0'; p++) {
        fputc(*p, stdout);
    }

    // fputs relies on bytes being 0-terminated
    fputs(bytes, stdout);

    // write 14 1 byte items
    fwrite(bytes, 1, (sizeof bytes) - 1, stdout);

    // %s relies on bytes being 0-terminated
    fprintf(stdout, "%s", bytes);

    return 0;
}

Download hello_stdio.c

cp <file1> <file2> implemented with fgetc
#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source file> <destination file>\n", argv[0]);
        return 1;
    }

    FILE *input_stream = fopen(argv[1], "r");
    if (input_stream == NULL) {
        perror(argv[1]);  // prints why the open failed
        return 1;
    }

    FILE *output_stream = fopen(argv[2], "w");
    if (output_stream == NULL) {
        perror(argv[2]);
        return 1;
    }

    int c; // not char!
    while ((c = fgetc(input_stream)) != EOF) {
        fputc(c, output_stream);
    }


    fclose(input_stream);  // optional here as fclose occurs
    fclose(output_stream); // automatically on exit

    return 0;
}

Download cp_fgetc.c


#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// cp <file1> <file2> implemented with libc and no error handling
int main(int argc, char *argv[]) {

    // open takes 3 arguments:
    //   1) address of zero-terminated string containing pathname of file to open
    //   2) bitmap indicating whether to write, read, ... file
    //   3) permissions if file will be newly created
    //      0644 == readable to everyone, writeable by owner
    int read_file_descriptor = open(argv[1], O_RDONLY);
    int write_file_descriptor = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);

    while (1) {

        // read takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to put bytes read
        //   3) maximum number of bytes read
        // returns number of bytes actually read

        char bytes[4096];
        ssize_t bytes_read = read(read_file_descriptor, bytes, 4096);
        if (bytes_read <= 0) {
            break;
        }

        // write takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to take bytes from
        //   3) number of bytes to written
        // returns number of bytes actually written

        write(write_file_descriptor, bytes, bytes_read);
    }

    // good practice to close file descriptions as soon as finished using them
    // not necessary needed here as program about to exit
    close(read_file_descriptor);
    close(write_file_descriptor);

    return 0;
}

Download cp_libc.c