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 | Assignment 2 |
Exam Prep: | Practice exams | Virtual memory exercises |
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
- Weekly Test
- Tutorial
- Laboratory
- Weekly Test
- Monday Week 7 Lecture Topics
- Wednesday Week 7 Lecture Topics
- Tutorial
- Laboratory
- Weekly Test
- Monday Week 8 Lecture Topics
- Wednesday Week 8 Lecture Topics
- Tutorial
- Laboratory
- Weekly Test
- Monday Week 9 Lecture Topics
- Wedsday Week 9 Lecture Topics
- Tutorial
- Laboratory
- Weekly Test
- Wednesday Week 10 Lecture Topics
Course Content Topic-by-Topic
.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"
Perform some basic arithmetic.
#include <stdio.h>
int main(void) {
int x = 17;
int y = 25;
printf("%d\n", 2 * (x + y));
return 0;
}
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;
}
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;
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;
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;
}
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;
}
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 "
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;
}
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;
}
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;
}
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;
}
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;
- Hammond's slides (first lecture)
- Hammond's slides (second lecture)
- sample_data.s
- 2D array layout
struct student
layout
#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');
}
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;
#include <stdio.h>
int x, y, z;
int main(void) {
x = 17;
y = 25;
z = x + y;
printf("%d", z);
printf("\n");
return 0;
}
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
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
#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;
}
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}
Simple example of accessing an array element
#include <stdio.h>
int array[10];
int main(void) {
array[3] = 17;
}
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];
#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);
}
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
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
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
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;
}
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: "
#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));
}
$ 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));
}
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");
}
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"
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"
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"
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;
}
simple example of returning a value from a function
note storing of return address $ra
code for function main
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 #
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;
}
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 #
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);
}
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
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);
}
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
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
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;
}
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;
}
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;
}
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;
}
#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;
}
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);
}
}
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;
}
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;
}
#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);
}
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;
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);
}
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
- 18s2 COMP1521 Lecture Video: Data representation: floats, arrays, structs
- Wikpedia: floating point representation
- Floating point calculator
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;
}
#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;
}
#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;
}
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;
}
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;
}
- 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;
}
#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;
}
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;
}
hello world implemented with a direct syscall
This isn't portable or readable but shows us what system calls look like.
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;
}
#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;
}
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;
}
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;
}
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;
}
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;
}
#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;
}
call stat on each command line argument as simple example of its use
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
void stat_file(char *pathname);
int main(int argc, char *argv[]) {
for (int arg = 1; arg < argc; arg++) {
stat_file(argv[arg]);
}
return 0;
}
void stat_file(char *pathname) {
printf("stat(\"%s\", &s)\n", pathname);
struct stat s;
if (stat(pathname, &s) != 0) {
perror(pathname);
exit(1);
}
printf("ino = %10ld # Inode number\n", s.st_ino);
printf("mode = %10o # File mode \n", s.st_mode);
printf("nlink =%10ld # Link count \n", (long)s.st_nlink);
printf("uid = %10u # Owner uid\n", s.st_uid);
printf("gid = %10u # Group gid\n", s.st_gid);
printf("size = %10ld # File size (bytes)\n", (long)s.st_size);
printf("mtime =%10ld # Modification time (seconds since 1/1/70)\n",
(long)s.st_mtime);
}
$ dcc mkdir.c $ ./a.out new_dir $ ls -ld new_dir drwxr-xr-x 2 z5555555 z5555555 60 Oct 29 16:28 new_dir $
#include <stdio.h>
#include <sys/stat.h>
// create the directories specified as command-line arguments
int main(int argc, char *argv[]) {
for (int arg = 1; arg < argc; arg++) {
if (mkdir(argv[arg], 0755) != 0) {
perror(argv[arg]); // prints why the mkdir failed
return 1;
}
}
return 0;
}
$ dcc list_directory.c $ ./a.out . list_directory.c a.out . .. $
#include <stdio.h>
#include <dirent.h>
// list the contents of directories specified as command-line arguments
int main(int argc, char *argv[]) {
for (int arg = 1; arg < argc; arg++) {
DIR *dirp = opendir(argv[arg]);
if (dirp == NULL) {
perror(argv[arg]); // prints why the open failed
return 1;
}
struct dirent *de;
while ((de = readdir(dirp)) != NULL) {
printf("%ld %s\n", de->d_ino, de->d_name);
}
closedir(dirp);
}
return 0;
}
$ dcc chmod.c $ ls -l chmod.c -rw-r--r-- 1 z5555555 z5555555 746 Nov 4 08:20 chmod.c $ ./a.out 600 chmod.c $ ls -l chmod.c -rw------- 1 z5555555 z5555555 787 Nov 4 08:22 chmod.c $ ./a.out 755 chmod.c chmod.c 755 $ ls -l chmod.c -rwxr-xr-x 1 z5555555 z5555555 787 Nov 4 08:22 chmod.c $
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
// change permissions of the specified files
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <mode> <files>\n", argv[0]);
return 1;
}
char *end;
// first argument is mode in octal
mode_t mode = strtol(argv[1], &end, 8);
// check first argument was a valid octal number
if (argv[1][0] == '\0' || end[0] != '\0') {
fprintf(stderr, "%s: invalid mode: %s\n", argv[0], argv[1]);
return 1;
}
for (int arg = 2; arg < argc; arg++) {
if (chmod(argv[arg], mode) != 0) {
perror(argv[arg]); // prints why the chmod failed
return 1;
}
}
return 0;
}
$ dcc rm.c $ ./a.out rm.c $ ls -l rm.c ls: cannot access 'rm.c': No such file or directory $
#include <stdio.h>
#include <unistd.h>
// remove the specified files
int main(int argc, char *argv[]) {
for (int arg = 1; arg < argc; arg++) {
if (unlink(argv[arg]) != 0) {
perror(argv[arg]); // prints why the unlink failed
return 1;
}
}
return 0;
}
broken attempt to implement cd
chdir() affects only this process and any it runs
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc > 1 && chdir(argv[1]) != 0) {
perror("chdir");
return 1;
}
return 0;
}
getcwd and chdir example
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
int main(void) {
// use repeated chdir("..") to climb to root of the file system
char pathname[PATH_MAX];
while (1) {
if (getcwd(pathname, sizeof pathname) == NULL) {
perror("getcwd");
return 1;
}
printf("getcwd() returned %s\n", pathname);
if (strcmp(pathname, "/") == 0) {
return 0;
}
if (chdir("..") != 0) {
perror("chdir");
return 1;
}
}
return 0;
}
$ dcc rename.c $ ./a.out rename.c renamed.c $ ls -l renamed.c renamed.c $
#include <stdio.h>
// rename the specified file
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <old-filename> <new-filename>\n",
argv[0]);
return 1;
}
char *old_filename = argv[1];
char *new_filename = argv[2];
if (rename(old_filename, new_filename) != 0) {
fprintf(stderr, "%s rename %s %s:", argv[0], old_filename,
new_filename);
perror("");
return 1;
}
return 0;
}
silly program which creates a 1000-deep directory hierarchy
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>
int main(void) {
for (int i = 0; i < 1000;i++) {
char dirname[256];
snprintf(dirname, sizeof dirname, "d%d", i);
if (mkdir(dirname, 0755) != 0) {
perror(dirname);
return 1;
}
if (chdir(dirname) != 0) {
perror(dirname);
return 1;
}
char pathname[1000000];
if (getcwd(pathname, sizeof pathname) == NULL) {
perror("getcwd");
return 1;
}
printf("\nCurrent directory now: %s\n", pathname);
}
return 0;
}
silly program which create a 1000 links to file
in effect there are 1001 names for the file
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
int main(int argc, char *argv[]) {
char pathname[256] = "hello.txt";
// create a target file
FILE *f1;
if ((f1 = fopen(pathname, "w")) == NULL) {
perror(pathname);
return 1;
}
fprintf(f1, "Hello Andrew!\n");
fclose(f1);
for (int i = 0; i < 1000; i++) {
printf("Verifying '%s' contains: ", pathname);
FILE *f2;
if ((f2 = fopen(pathname, "r")) == NULL) {
perror(pathname);
return 1;
}
int c;
while ((c = fgetc(f2)) != EOF) {
fputc(c, stdout);
}
fclose(f2);
char new_pathname[256];
snprintf(new_pathname, sizeof new_pathname,
"hello_%d.txt", i);
printf("Creating a link %s -> %s\n",
new_pathname, pathname);
if (link(pathname, new_pathname) != 0) {
perror(pathname);
return 1;
}
}
return 0;
}
#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
/**
* @brief Convert a string to uppercase
*
* Subtract 32 from each lowercase letter to convert it to uppercase.
*
* @param s - string to convert
* @return char* - pointer to the converted string (same as input pointer)
*/
char *to_upper_subtraction(char *s) {
for (int i = 0; s[i]; i++) {
if (s[i] >= 'a' && s[i] <= 'z') {
s[i] -= 32;
}
}
return s;
}
/**
* @brief Convert a string to uppercase
*
* Bitwise AND with 0xDF to convert lowercase letters to uppercase.
*
* @param s - string to convert
* @return char* - pointer to the converted string (same as input pointer)
*/
char *to_upper_bitwise(char *s) {
for (int i = 0; s[i]; i++) {
if (s[i] >= 'a' && s[i] <= 'z') {
s[i] &= ~0x20;
}
}
return s;
}
/**
* @brief Compare two strings, ignoring case
*
* This is already implemented in the standard library as strcasecmp().
*
* @param s1 - first string
* @param s2 - second string
* @return true - if the strings are equal, ignoring case
* @return false - if the strings are not equal
*/
bool case_insensitive_compare_bitwise(char *s1, char *s2) {
for (int i = 0; s1[i] && s2[i]; i++) {
if (isalpha(s1[i]) && isalpha(s2[i])) {
// Alphabetical character
// Compare ignoring case
// Convert both characters to uppercase lowercase
// by inserting a 1 in the 6th bit then comparing
if ((s1[i] | 0x20) != (s2[i] | 0x20)) {
return false;
}
} else {
// Non-Alphabetical character
// Normal comparison
if (s1[i] != s2[i]) {
return false;
}
}
}
return true;
}
int main(void) {
char s1[] = "Hello, World!";
char s2[] = "Hello, World!";
assert(0 == strcmp("HELLO, WORLD!", to_upper_subtraction(s1)));
assert(0 == strcmp("HELLO, WORLD!", to_upper_bitwise(s2)));
char s3[] = "HeLLo, WOrLD!";
char s4[] = "hEllo, WORld!";
assert(case_insensitive_compare_bitwise(s3, s4));
}
#include <stdio.h>
#include <string.h>
#define cmp(s1, s2) strcmp(s1, s2) ? "Not Equal" : "Equal"
int main(void) {
char *string1 = "Hello World"; // normal ASCII
char *string2 = "Hellо Wоrld"; // These are not latin o's (cyrillic)
char *string3 = "Hellⲟ Wօrld"; // These are also not latin o's (coptic and armenian)
char *string4 = "Ⓗⓔⓛⓛⓞ Ⓦⓞⓡⓛⓓ"; // letters in circles
char *string5 = "Hëllo World"; // e with a diaeresis (one character)
char *string6 = "Hëllo World"; // latin small letter e followed by a combining diaeresis (two characters)
// The command `unicode` can be used to see the unicode code points of a string
// eg `unicode -sm0 "Hello World" --brief` will display:
/*
H U+0048 LATIN CAPITAL LETTER H
e U+0065 LATIN SMALL LETTER E
l U+006C LATIN SMALL LETTER L
l U+006C LATIN SMALL LETTER L
ⲟ U+2C9F COPTIC SMALL LETTER O
U+0020 SPACE
W U+0057 LATIN CAPITAL LETTER W
օ U+0585 ARMENIAN SMALL LETTER OH
r U+0072 LATIN SMALL LETTER R
l U+006C LATIN SMALL LETTER L
d U+0064 LATIN SMALL LETTER D
*/
// Even though the strings look the same, they are not the same
// all of the strings contain different unicode characters
// so comparing them with strcmp will return false
printf("string1 == string2: %s\n", cmp(string1, string2));
printf("string1 == string3: %s\n", cmp(string1, string3));
printf("string1 == string4: %s\n", cmp(string1, string4));
printf("string1 == string5: %s\n", cmp(string1, string5));
printf("string1 == string6: %s\n", cmp(string1, string6));
printf("string2 == string3: %s\n", cmp(string2, string3));
printf("string2 == string4: %s\n", cmp(string2, string4));
printf("string2 == string5: %s\n", cmp(string2, string5));
printf("string2 == string6: %s\n", cmp(string2, string6));
printf("string3 == string4: %s\n", cmp(string3, string4));
printf("string3 == string5: %s\n", cmp(string3, string5));
printf("string3 == string6: %s\n", cmp(string3, string6));
printf("string4 == string5: %s\n", cmp(string4, string5));
printf("string4 == string6: %s\n", cmp(string4, string6));
printf("string5 == string6: %s\n", cmp(string5, string6));
char _; scanf("%c", &_);
// the strlen function does not count the number of characters in a string
// it counts the number of bytes in a string
// for ASCII strings, the number of bytes is the same as the number of characters
// but for UTF-8 strings, the number of bytes is not the same as the number of characters
printf("string1: %lu\n", strlen(string1));
printf("string2: %lu\n", strlen(string2));
printf("string3: %lu\n", strlen(string3));
printf("string4: %lu\n", strlen(string4));
printf("string5: %lu\n", strlen(string5));
printf("string6: %lu\n", strlen(string6));
}
Python has a built-in module for dealing with Unicode strings
Updated regularly to match the latest Unicode standard
import unicodedata
string1 = "Hello World" # normal ASCII
string2 = "Hellо Wоrld" # These are not latin o's (cyrillic)
string3 = "Hellⲟ Wօrld" # These are also not latin o's (coptic and armenian)
string4 = "Ⓗⓔⓛⓛⓞ Ⓦⓞⓡⓛⓓ" # letters in circles
string5 = "Hëllo World" # e with a diaeresis (one character)
string6 = "Hëllo World" # latin small letter e followed by a combining diaeresis (two characters)
def tryEqualities(s1, s2):
return (
s1 == s2,
# normalization rules are used to compare UNICODE characters that are semantically equivalent even if they are not identical
# NFC is Canonical Composition
# NFKC is Compatibility Composition
# NFD is Canonical Decomposition
# NFKD is Compatibility Decomposition
# Compatibility is a less strict equality than Canonical
# Composition means that eg "letter e followed by a combining diaeresis" is converted to "e with a diaeresis"
# Decomposition means that eg "e with a diaeresis" is converted to "letter e followed by a combining diaeresis"
unicodedata.normalize('NFC', s1) == unicodedata.normalize('NFC', s2),
unicodedata.normalize('NFKC', s1) == unicodedata.normalize('NFKC', s2),
unicodedata.normalize('NFD', s1) == unicodedata.normalize('NFD', s2),
unicodedata.normalize('NFKD', s1) == unicodedata.normalize('NFKD', s2),
)
print("string1 == string2:", tryEqualities(string1, string2))
print("string1 == string3:", tryEqualities(string1, string3))
print("string1 == string4:", tryEqualities(string1, string4))
print("string1 == string5:", tryEqualities(string1, string5))
print("string1 == string6:", tryEqualities(string1, string6))
print("string2 == string3:", tryEqualities(string2, string3))
print("string2 == string4:", tryEqualities(string2, string4))
print("string2 == string5:", tryEqualities(string2, string5))
print("string2 == string6:", tryEqualities(string2, string6))
print("string3 == string4:", tryEqualities(string3, string4))
print("string3 == string5:", tryEqualities(string3, string5))
print("string3 == string6:", tryEqualities(string3, string6))
print("string4 == string5:", tryEqualities(string4, string5))
print("string4 == string6:", tryEqualities(string4, string6))
print("string5 == string6:", tryEqualities(string5, string6))
input()
# len() returns the number of characters in a string
# and is unicode-aware so will correctly count the number of characters in a unicode string
# not jsut the number of bytes (like in C)
# encode('utf-8') can be used to get the raw bytes of a string
# which can be used to get the number of bytes in a string
print(len(string1), len(string1.encode('utf-8')))
print(len(string2), len(string2.encode('utf-8')))
print(len(string3), len(string3.encode('utf-8')))
print(len(string4), len(string4.encode('utf-8')))
print(len(string5), len(string5.encode('utf-8')))
print(len(string6), len(string6.encode('utf-8')))
#include <stdio.h>
int main(void) {
printf("The unicode code point U+1F600 encodes in UTF-8\n");
printf("as 4 bytes: 0xF0 0x9F 0x98 0x80\n");
printf("We can output the 4 bytes like this: \xF0\x9F\x98\x80 (UTF-8)\n");
printf("Or like this: ");
putchar(0xF0);
putchar(0x9F);
putchar(0x98);
putchar(0x80);
putchar('\n');
printf("Or like this: \U0001F600 (UTF-32)\n");
// UNICODE code point less than 0x10000 (ie the BMP) can be encoded with
// \uXXXX (lowercase u) with only 4 hex digits
// \U must always be followed by 8 hex digits
}
#include <stdio.h>
#include <string.h>
#include <assert.h>
unsigned long utf8_strlen(char *string) {
unsigned long num_code_points = 0;
for (char *code_point = string; *code_point;) {
if ((*code_point & 0xF8) == 0xF0) {
// 4-byte head byte
code_point += 4;
} else if ((*code_point & 0xF0) == 0xE0) {
// 3-byte head byte
code_point += 3;
} else if ((*code_point & 0xE0) == 0xC0) {
// 2-byte head byte
code_point += 2;
} else if ((*code_point & 0xC0) == 0x80) {
// INVALID STRING
// tail byte - should not be here
// as we should be moving from head byte to head byte
fprintf(stderr, "Invalid UTF-8 string: \"%s\"\n", string);
fprintf(stderr, "Found a tail byte when head byte was expected\n");
assert(0);
} else if ((*code_point & 0x80) == 0x00) {
// ASCII
code_point += 1;
} else {
// INVALID STRING
// this is not a valid UTF-8 byte
fprintf(stderr, "Invalid UTF-8 string: \"%s\"\n", string);
fprintf(stderr, "Head byte indicates invalid length\n");
assert(0);
}
num_code_points++;
}
return num_code_points;
}
int main(void) {
char *string1 = "Hello World";
char *string2 = "Hellо Wоrld";
char *string3 = "Hellⲟ W𐓪rld";
char *string4 = "Ⓗⓔⓛⓛⓞ Ⓦⓞⓡⓛⓓ";
char *string5 = "Hëllo World";
char *string6 = "Hëllo World";
printf("\"%s\": strlen=%lu, utf8_strlen=%lu\n", string1, strlen(string1), utf8_strlen(string1));
printf("\"%s\": strlen=%lu, utf8_strlen=%lu\n", string2, strlen(string2), utf8_strlen(string2));
printf("\"%s\": strlen=%lu, utf8_strlen=%lu\n", string3, strlen(string3), utf8_strlen(string3));
printf("\"%s\": strlen=%lu, utf8_strlen=%lu\n", string4, strlen(string4), utf8_strlen(string4));
printf("\"%s\": strlen=%lu, utf8_strlen=%lu\n", string5, strlen(string5), utf8_strlen(string5));
printf("\"%s\": strlen=%lu, utf8_strlen=%lu\n", string6, strlen(string6), utf8_strlen(string6));
}
#include <stdio.h>
#include <stdint.h>
void print_utf8_encoding(uint32_t code_point) {
uint8_t encoding[5] = {0};
if (code_point < 0x80) {
encoding[0] = code_point;
} else if (code_point < 0x800) {
encoding[0] = 0xC0 | (code_point >> 6);
encoding[1] = 0x80 | (code_point & 0x3f);
} else if (code_point < 0x10000) {
encoding[0] = 0xE0 | (code_point >> 12);
encoding[1] = 0x80 | ((code_point >> 6) & 0x3f);
encoding[2] = 0x80 | (code_point & 0x3f);
} else if (code_point < 0x200000) {
encoding[0] = 0xF0 | (code_point >> 18);
encoding[1] = 0x80 | ((code_point >> 12) & 0x3f);
encoding[2] = 0x80 | ((code_point >> 6) & 0x3f);
encoding[3] = 0x80 | (code_point & 0x3f);
}
printf("UNICODE codepoint: U+%04x, UTF-32: 0x%08x, UTF-8: ", code_point, code_point);
for (uint8_t *s = encoding; *s != 0; s++) {
printf("0x%02x ", *s);
}
printf(" %s\n", encoding);
}
int main(void) {
print_utf8_encoding(0x0042);
print_utf8_encoding(0x00A2);
print_utf8_encoding(0x10be);
print_utf8_encoding(0x1F600);
}
$ dcc exec.c $ a.out good-bye cruel world $
#include <stdio.h>
#include <unistd.h>
// simple example of program replacing itself with exec
int main(void) {
char *echo_argv[] = {"/bin/echo","good-bye","cruel","world",NULL};
execv("/bin/echo", echo_argv);
// if we get here there has been an error
perror("execv");
return 1;
}
$ dcc fork.c $ a.out
I am the parent because fork() returned 2884551.
I am the child because fork() returned 0. $
#include <stdio.h>
#include <unistd.h>
int main(void) {
// fork creates 2 identical copies of program
// only return value is different
pid_t pid = fork();
if (pid == -1) {
perror("fork"); // print why the fork failed
} else if (pid == 0) {
printf("I am the child because fork() returned %d.\n", pid);
} else {
printf("I am the parent because fork() returned %d.\n", pid);
}
return 0;
}
simple example of classic fork/exec
run date --utc to print current UTC
use posix_spawn instead
use posix_spawn instead
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
int main(void) {
pid_t pid = fork();
if (pid == -1) {
perror("fork"); // print why fork failed
} else if (pid == 0) { // child
char *date_argv[] = {"/bin/date", "--utc", NULL};
execv("/bin/date", date_argv);
perror("execvpe"); // print why exec failed
} else { // parent
int exit_status;
if (waitpid(pid, &exit_status, 0) == -1) {
perror("waitpid");
exit(1);
}
printf("/bin/date exit status was %d\n", exit_status);
}
return 0;
}
simple example of system
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// system passes string to a shell for evaluation
// brittle and highly vulnerable to security exploits
// system is suitable for quick debugging and throw-away programs only
// run date --utc to print current UTC
int exit_status = system("/bin/date --utc");
printf("/bin/date exit status was %d\n", exit_status);
return 0;
}
$ dcc spawn.c $ a.out
Tue 3 Nov 23:51:27 UTC 2022 /bin/date exit status was 0
simple example of posix_spawn run date --utc to print current UTC
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>
int main(void) {
pid_t pid;
extern char **environ;
char *date_argv[] = {"/bin/date", "--utc", NULL};
// spawn "/bin/date" as a separate process
int ret = posix_spawn(&pid, "/bin/date", NULL, NULL, date_argv, environ);
if (ret != 0) {
errno = ret; //posix_spawn returns error code, does not set errno
perror("spawn");
exit(1);
}
// wait for spawned processes to finish
int exit_status;
if (waitpid(pid, &exit_status, 0) == -1) {
perror("waitpid");
exit(1);
}
printf("/bin/date exit status was %d\n", exit_status);
return 0;
}
spawn ls -ld adding as argument the arguments we have been given
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>
int main(int argc, char *argv[]) {
char *ls_argv[argc + 2];
ls_argv[0] = "/bin/ls";
ls_argv[1] = "-ld";
for (int i = 1; i <= argc; i++) {
ls_argv[i + 1] = argv[i];
}
pid_t pid;
extern char **environ;
int ret = posix_spawn(&pid, "/bin/ls", NULL, NULL, ls_argv, environ);
if (ret != 0) {
errno = ret;
perror("spawn");
return 1;
}
int exit_status;
if (waitpid(pid, &exit_status, 0) == -1) {
perror("waitpid");
exit(1);
}
// exit with whatever status ls exited with
return exit_status;
}
spawn ls -ld adding as argument the arguments we have been given
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *ls = "/bin/ls -ld";
int command_length = strlen(ls);
for (int i = 1; i < argc; i++) {
command_length += strlen(argv[i]) + 1;
}
// create command as string
char command[command_length + 1];
strcpy(command, ls);
for (int i = 1; i <= argc; i++) {
strcat(command, " ");
strcat(command, argv[i]);
}
int exit_status = system(command);
return exit_status;
}
print all environment variables
#include <stdio.h>
int main(void) {
// print all environment variables
extern char **environ;
for (int i = 0; environ[i] != NULL; i++) {
printf("%s\n", environ[i]);
}
}
simple example of using environment variableto change program behaviour
run date -to print time
Perth time printed, due to TZ environment variable
Perth time printed, due to TZ environment variable
#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>
int main(void) {
pid_t pid;
char *date_argv[] = { "/bin/date", NULL };
char *date_environment[] = { "TZ=Australia/Perth", NULL };
// print time in Perth
int ret = posix_spawn(&pid, "/bin/date", NULL, NULL, date_argv,
date_environment);
if (ret != 0) {
errno = ret;
perror("spawn");
return 1;
}
int exit_status;
if (waitpid(pid, &exit_status, 0) == -1) {
perror("waitpid");
return 1;
}
printf("/bin/date exit status was %d\n", exit_status);
return 0;
}
$ dcc get_status.c -o get_status $ STATUS=ok ./get_status
Environment variable 'STATUS' has value 'ok' $
#include <stdio.h>
#include <stdlib.h>
// simple example of accessing an environment variable
int main(void) {
// print value of environment variable STATUS
char *value = getenv("STATUS");
printf("Environment variable 'STATUS' has value '%s'\n", value);
return 0;
}
$ dcc set_status.c -o set_status $ dcc get_status.c -o get_status $ ./set_status
Environment variable 'STATUS' has value 'great' $
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>
// simple example of setting an environment variable
int main(void) {
// set environment variable STATUS
setenv("STATUS", "great", 1);
char *getenv_argv[] = {"./get_status", NULL};
pid_t pid;
extern char **environ;
int ret = posix_spawn(&pid, "./get_status", NULL, NULL,
getenv_argv, environ);
if (ret != 0) {
errno = ret;
perror("spawn");
return 1;
}
int exit_status;
if (waitpid(pid, &exit_status, 0) == -1) {
perror("waitpid");
exit(1);
}
// exit with whatever status s exited with
return exit_status;
}
simple example of use to popen to capture output from a process
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// popen passes string to a shell for evaluation
// brittle and highly-vulnerable to security exploits
// popen is suitable for quick debugging and throw-away programs only
FILE *p = popen("/bin/date --utc", "r");
if (p == NULL) {
perror("");
return 1;
}
char line[256];
if (fgets(line, sizeof line, p) == NULL) {
fprintf(stderr, "no output from date\n");
return 1;
}
printf("output captured from /bin/date was: '%s'\n", line);
pclose(p); // returns command exit status
return 0;
}
simple example of use to popen to capture output
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// popen passes command to a shell for evaluation
// brittle and highly-vulnerable to security exploits
// popen is suitable for quick debugging and throw-away programs only
//
// tr a-z A-Z - passes stdin to stdout converting lower case to upper case
FILE *p = popen("tr a-z A-Z", "w");
if (p == NULL) {
perror("");
return 1;
}
fprintf(p, "plz date me - I know every SPIM system call\n");
pclose(p); // returns command exit status
return 0;
}
simple example using a pipe with posix_spawn
to capture output from spawned process
#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
int main(void) {
// create a pipe
int pipe_file_descriptors[2];
if (pipe(pipe_file_descriptors) == -1) {
perror("pipe");
return 1;
}
// create a list of file actions to be carried out on spawned process
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0) {
perror("posix_spawn_file_actions_init");
return 1;
}
// tell spawned process to close unused read end of pipe
// without this - spawned process would not receive EOF
// when read end of the pipe is closed below,
if (posix_spawn_file_actions_addclose(&actions, pipe_file_descriptors[0]) != 0) {
perror("posix_spawn_file_actions_init");
return 1;
}
// tell spawned process to replace file descriptor 1 (stdout)
// with write end of the pipe
if (posix_spawn_file_actions_adddup2(&actions, pipe_file_descriptors[1], 1) != 0) {
perror("posix_spawn_file_actions_adddup2");
return 1;
}
pid_t pid;
extern char **environ;
char *date_argv[] = {"/bin/date", "--utc", NULL};
if (posix_spawn(&pid, "/bin/date", &actions, NULL, date_argv, environ) != 0) {
perror("spawn");
return 1;
}
// close unused write end of pipe
// in some case processes will deadlock without this
// not in this case, but still good practice
close(pipe_file_descriptors[1]);
// create a stdio stream from read end of pipe
FILE *f = fdopen(pipe_file_descriptors[0], "r");
if (f == NULL) {
perror("fdopen");
return 1;
}
// read a line from read-end of pipe
char line[256];
if (fgets(line, sizeof line, f) == NULL) {
fprintf(stderr, "no output from date\n");
return 1;
}
printf("output captured from /bin/date was: '%s'\n", line);
// close read-end of the pipe
// spawned process will now receive EOF if attempts to read input
fclose(f);
int exit_status;
if (waitpid(pid, &exit_status, 0) == -1) {
perror("waitpid");
return 1;
}
printf("/bin/date exit status was %d\n", exit_status);
// free the list of file actions
posix_spawn_file_actions_destroy(&actions);
return 0;
}
simple example of using a pipe to with posix_spawn
to sending input to spawned process
#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
int main(void) {
// create a pipe
int pipe_file_descriptors[2];
if (pipe(pipe_file_descriptors) == -1) {
perror("pipe");
return 1;
}
// create a list of file actions to be carried out on spawned process
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0) {
perror("posix_spawn_file_actions_init");
return 1;
}
// tell spawned process to close unused write end of pipe
// without this - spawned process will not receive EOF
// when write end of the pipe is closed below,
// because spawned process also has the write-end open
// deadlock will result
if (posix_spawn_file_actions_addclose(&actions, pipe_file_descriptors[1]) != 0) {
perror("posix_spawn_file_actions_init");
return 1;
}
// tell spawned process to replace file descriptor 0 (stdin)
// with read end of the pipe
if (posix_spawn_file_actions_adddup2(&actions, pipe_file_descriptors[0], 0) != 0) {
perror("posix_spawn_file_actions_adddup2");
return 1;
}
// create a process running /usr/bin/sort
// sort reads lines from stdin and prints them in sorted order
char *sort_argv[] = {"sort", NULL};
pid_t pid;
extern char **environ;
if (posix_spawn(&pid, "/usr/bin/sort", &actions, NULL, sort_argv, environ) != 0) {
perror("spawn");
return 1;
}
// close unused read end of pipe
close(pipe_file_descriptors[0]);
// create a stdio stream from write-end of pipe
FILE *f = fdopen(pipe_file_descriptors[1], "w");
if (f == NULL) {
perror("fdopen");
return 1;
}
// send some input to the /usr/bin/sort process
//sort with will print the lines to stdout in sorted order
fprintf(f, "sort\nwords\nplease\nthese\n");
// close write-end of the pipe
// without this sort will hang waiting for more input
fclose(f);
int exit_status;
if (waitpid(pid, &exit_status, 0) == -1) {
perror("waitpid");
return 1;
}
printf("/usr/bin/sort exit status was %d\n", exit_status);
// free the list of file actions
posix_spawn_file_actions_destroy(&actions);
return 0;
}
A simple example which launches two threads of execution.
$ gcc -pthread two_threads.c -o two_threads $ ./two_threads | more Hello this is thread #1 i=0 Hello this is thread #1 i=1 Hello this is thread #1 i=2 Hello this is thread #1 i=3 Hello this is thread #1 i=4 Hello this is thread #2 i=0 Hello this is thread #2 i=1 ...
#include <pthread.h>
#include <stdio.h>
// This function is called to start thread execution.
// It can be given any pointer as an argument.
void *run_thread(void *argument) {
int *p = argument;
for (int i = 0; i < 10; i++) {
printf("Hello this is thread #%d: i=%d\n", *p, i);
}
// A thread finishes when either the thread's start function
// returns, or the thread calls `pthread_exit(3)'.
// A thread can return a pointer of any type --- that pointer
// can be fetched via `pthread_join(3)'
return NULL;
}
int main(void) {
// Create two threads running the same task, but different inputs.
pthread_t thread_id1;
int thread_number1 = 1;
pthread_create(&thread_id1, NULL, run_thread, &thread_number1);
pthread_t thread_id2;
int thread_number2 = 2;
pthread_create(&thread_id2, NULL, run_thread, &thread_number2);
// Wait for the 2 threads to finish.
pthread_join(thread_id1, NULL);
pthread_join(thread_id2, NULL);
return 0;
}
Simple example of running an arbitrary number of threads.
For example::
$ gcc -pthread n_threads.c -o n_threads $ ./n_threads 10 Hello this is thread 0: i=0 Hello this is thread 0: i=1 Hello this is thread 0: i=2 Hello this is thread 0: i=3 Hello this is thread 0: i=4 Hello this is thread 0: i=5 Hello this is thread 0: i=6 Hello this is thread 0: i=7 ...
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void *run_thread(void *argument) {
int *p = argument;
for (int i = 0; i < 42; i++) {
printf("Hello this is thread %d: i=%d\n", *p, i);
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <n-threads>\n", argv[0]);
return 1;
}
int n_threads = strtol(argv[1], NULL, 0);
assert(0 < n_threads && n_threads < 100);
pthread_t thread_id[n_threads];
int argument[n_threads];
for (int i = 0; i < n_threads; i++) {
argument[i] = i;
pthread_create(&thread_id[i], NULL, run_thread, &argument[i]);
}
// Wait for the threads to finish
for (int i = 0; i < n_threads; i++) {
pthread_join(thread_id[i], NULL);
}
return 0;
}
Simple example of dividing a task between `n' threads.
Compile like:
$ gcc -O3 -pthread thread_sum.c -o thread_sum
One thread takes 10 seconds:
$ time ./thread_sum 1 10000000000 Creating 1 threads to sum the first 10000000000 integers Each thread will sum 10000000000 integers Thread summing integers 0 to 10000000000 finished sum is 49999999990067863552
Combined sum of integers 0 to 10000000000 is 49999999990067863552
real 0m11.924s user 0m11.919s sys 0m0.004s $
Four threads runs 4x as fast on a machine with 4 cores:
$ time ./thread_sum 4 10000000000 Creating 4 threads to sum the first 10000000000 integers Each thread will sum 2500000000 integers Thread summing integers 2500000000 to 5000000000 finished sum is 9374999997502005248 Thread summing integers 7500000000 to 10000000000 finished sum is 21874999997502087168 Thread summing integers 5000000000 to 7500000000 finished sum is 15624999997500696576 Thread summing integers 0 to 2500000000 finished sum is 3124999997567081472
Combined sum of integers 0 to 10000000000 is 49999999990071869440
real 0m3.154s user 0m12.563s sys 0m0.004s $
Note the result is inexact, because we use values can't be exactly represented as double and exact value printed depends on how many threads we use - because we break up the computation differently depending on the number of threads.
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
struct job {
long start, finish;
double sum;
};
void *run_thread(void *argument) {
struct job *j = argument;
long start = j->start;
long finish = j->finish;
double sum = 0;
for (long i = start; i < finish; i++) {
sum += i;
}
j->sum = sum;
printf("Thread summing integers %10lu to %11lu finished sum is %20.0f\n",
start, finish, sum);
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <n-threads> <n-integers-to-sum>\n", argv[0]);
return 1;
}
int n_threads = strtol(argv[1], NULL, 0);
assert(0 < n_threads && n_threads < 1000);
long integers_to_sum = strtol(argv[2], NULL, 0);
assert(0 < integers_to_sum);
long integers_per_thread = (integers_to_sum - 1) / n_threads + 1;
printf("Creating %d threads to sum the first %lu integers\n"
"Each thread will sum %lu integers\n",
n_threads, integers_to_sum, integers_per_thread);
pthread_t thread_id[n_threads];
struct job jobs[n_threads];
for (int i = 0; i < n_threads; i++) {
jobs[i].start = i * integers_per_thread;
jobs[i].finish = jobs[i].start + integers_per_thread;
if (jobs[i].finish > integers_to_sum) {
jobs[i].finish = integers_to_sum;
}
// create a thread which will sum integers_per_thread integers
pthread_create(&thread_id[i], NULL, run_thread, &jobs[i]);
}
// Wait for threads to finish, then add results for an overall sum.
double overall_sum = 0;
for (int i = 0; i < n_threads; i++) {
pthread_join(thread_id[i], NULL);
overall_sum += jobs[i].sum;
}
printf("\nCombined sum of integers 0 to %lu is %.0f\n", integers_to_sum,
overall_sum);
return 0;
}
Simple example which launches two threads of execution, but which demonstrates the perils of accessing non-local variables from a thread.
$ gcc -pthread two_threads_broken.c -o two_threads_broken $ ./two_threads_broken|more Hello this is thread 2: i=0 Hello this is thread 2: i=1 Hello this is thread 2: i=2 Hello this is thread 2: i=3 Hello this is thread 2: i=4 Hello this is thread 2: i=5 Hello this is thread 2: i=6 Hello this is thread 2: i=7 Hello this is thread 2: i=8 Hello this is thread 2: i=9 Hello this is thread 2: i=0 Hello this is thread 2: i=1 Hello this is thread 2: i=2 Hello this is thread 2: i=3 Hello this is thread 2: i=4 Hello this is thread 2: i=5 Hello this is thread 2: i=6 Hello this is thread 2: i=7 Hello this is thread 2: i=8 Hello this is thread 2: i=9 $...
#include <pthread.h>
#include <stdio.h>
void *run_thread(void *argument) {
int *p = argument;
for (int i = 0; i < 10; i++) {
// variable thread_number will probably have changed in main
// before execution reaches here
printf("Hello this is thread %d: i=%d\n", *p, i);
}
return NULL;
}
int main(void) {
pthread_t thread_id1;
int thread_number = 1;
pthread_create(&thread_id1, NULL, run_thread, &thread_number);
thread_number = 2;
pthread_t thread_id2;
pthread_create(&thread_id2, NULL, run_thread, &thread_number);
pthread_join(thread_id1, NULL);
pthread_join(thread_id2, NULL);
return 0;
}
Simple example demonstrating unsafe access to a global variable from threads.
$ gcc -O3 -pthread bank_account_broken.c -o bank_account_broken $ ./bank_account_broken Andrew's bank account has $108829 $
#define _POSIX_C_SOURCE 199309L
#include <pthread.h>
#include <stdio.h>
#include <time.h>
int bank_account = 0;
// add $1 to Andrew's bank account 100,000 times
void *add_100000(void *argument) {
for (int i = 0; i < 100000; i++) {
// execution may switch threads in middle of assignment
// between load of variable value
// and store of new variable value
// changes other thread makes to variable will be lost
nanosleep(&(struct timespec){ .tv_nsec = 1 }, NULL);
// RECALL: shorthand for `bank_account = bank_account + 1`
bank_account++;
}
return NULL;
}
int main(void) {
// create two threads performing the same task
pthread_t thread_id1;
pthread_create(&thread_id1, NULL, add_100000, NULL);
pthread_t thread_id2;
pthread_create(&thread_id2, NULL, add_100000, NULL);
// wait for the 2 threads to finish
pthread_join(thread_id1, NULL);
pthread_join(thread_id2, NULL);
// will probably be much less than $200000
printf("Andrew's bank account has $%d\n", bank_account);
return 0;
}
Simple example demonstrating safe access to a global variable from threads, using a mutex (mutual exclusion) lock
$ gcc -O3 -pthread bank_account_mutex.c -o bank_account_mutex $ ./bank_account_mutex Andrew's bank account has $200000 $
#include <pthread.h>
#include <stdio.h>
int bank_account = 0;
pthread_mutex_t bank_account_lock = PTHREAD_MUTEX_INITIALIZER;
// add $1 to Andrew's bank account 100,000 times
void *add_100000(void *argument) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&bank_account_lock);
// only one thread can execute this section of code at any time
bank_account = bank_account + 1;
pthread_mutex_unlock(&bank_account_lock);
}
return NULL;
}
int main(void) {
// create two threads performing the same task
pthread_t thread_id1;
pthread_create(&thread_id1, NULL, add_100000, NULL);
pthread_t thread_id2;
pthread_create(&thread_id2, NULL, add_100000, NULL);
// wait for the 2 threads to finish
pthread_join(thread_id1, NULL);
pthread_join(thread_id2, NULL);
// will always be $200000
printf("Andrew's bank account has $%d\n", bank_account);
return 0;
}
! simple example which launches two threads of execution
! which increment a global variable
#include <pthread.h>
#include <stdio.h>
int andrews_bank_account = 200;
pthread_mutex_t andrews_bank_account_lock = PTHREAD_MUTEX_INITIALIZER;
int xaviers_bank_account = 100;
pthread_mutex_t xaviers_bank_account_lock = PTHREAD_MUTEX_INITIALIZER;
// Andrew sends Xavier all his money dollar by dollar
void *andrew_send_xavier_money(void *argument) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&andrews_bank_account_lock);
pthread_mutex_lock(&xaviers_bank_account_lock);
if (andrews_bank_account > 0) {
andrews_bank_account--;
xaviers_bank_account++;
}
pthread_mutex_unlock(&xaviers_bank_account_lock);
pthread_mutex_unlock(&andrews_bank_account_lock);
}
return NULL;
}
// Xavier sends Andrew all his money dollar by dollar
void *xavier_send_andrew_money(void *argument) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&xaviers_bank_account_lock);
pthread_mutex_lock(&andrews_bank_account_lock);
if (xaviers_bank_account > 0) {
xaviers_bank_account--;
andrews_bank_account++;
}
pthread_mutex_unlock(&andrews_bank_account_lock);
pthread_mutex_unlock(&xaviers_bank_account_lock);
}
return NULL;
}
int main(void) {
// create two threads sending each other money
pthread_t thread_id1;
pthread_create(&thread_id1, NULL, andrew_send_xavier_money, NULL);
pthread_t thread_id2;
pthread_create(&thread_id2, NULL, xavier_send_andrew_money, NULL);
// threads will probably never finish
// deadlock will likely likely occur
// with one thread holding andrews_bank_account_lock
// and waiting for xaviers_bank_account_lock
// and the other thread holding xaviers_bank_account_lock
// and waiting for andrews_bank_account_lock
pthread_join(thread_id1, NULL);
pthread_join(thread_id2, NULL);
return 0;
}
Simple example demonstrating safe access to a global variable from threads, using atomics
$ gcc -O3 -pthread bank_account_atomic.c -o bank_account_atomic $ ./bank_account_atomic Andrew's bank account has $200000 $
#include <pthread.h>
#include <stdio.h>
#include <stdatomic.h>
atomic_int bank_account = 0;
// add $1 to Andrew's bank account 100,000 times
void *add_100000(void *argument) {
for (int i = 0; i < 100000; i++) {
// NOTE: This *cannot* be `bank_account = bank_account + 1`,
// as that will not be atomic!
// However, `bank_account++` would be okay
// and, `atomic_fetch_add(&bank_account, 1)` would also be okay
bank_account += 1;
}
return NULL;
}
int main(void) {
// create two threads performing the same task
pthread_t thread_id1;
pthread_create(&thread_id1, NULL, add_100000, NULL);
pthread_t thread_id2;
pthread_create(&thread_id2, NULL, add_100000, NULL);
// wait for the 2 threads to finish
pthread_join(thread_id1, NULL);
pthread_join(thread_id2, NULL);
// will always be $200000
printf("Andrew's bank account has $%d\n", bank_account);
return 0;
}
! A small program that demonstrates lifetime issues
! with respect to differing threads
!
! When compiled with `dcc`:
! $ dcc -pthread thread_data_broken.c -o thread_data_broken
! $ ./thread_data_broken
! Hello there! Please patiently wait for your number!
! The number is 0x6c6c6548!
!
! Note that the 0x6c6c6548 value can easily change between
! compilers, platforms, or even individual executions.
! In this case, 0x6c6c6548 is the four ASCII bytes:
! 'l', 'l', 'e', 'H' -- the first four letters of "Hello there..."
! in little-endian byte ordering
!
! Curiously, the correct answer will occasionally appear:
! $ ./thread_data_broken
! Hello there! Please patiently wait for your number!
! The number is 0x42!
!
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *my_thread(void *data) {
int number = *(int *)data;
sleep(1);
// should print 0x42, probably won't
printf("The number is 0x%x!\n", number);
return NULL;
}
pthread_t create_thread(void) {
int super_special_number = 0x42;
pthread_t thread_handle;
pthread_create(&thread_handle, NULL, my_thread, &super_special_number);
// super_special_number is destroyed when create_thread returns
// but the thread just created may still be running and access it
return thread_handle;
}
/// This function is entirely innocent but calling it below
/// (probably) makes the bug in create_thread obvious by changing stack memory
void say_hello(void) {
char greeting[] = "Hello there! Please patiently wait for your number!\n";
printf("%s", greeting);
}
int main(void) {
pthread_t thread_handle = create_thread();
say_hello();
pthread_join(thread_handle, NULL);
}
! A potential solution to the issue in thread_data_broken.c
!
! This program uses a heap allocation to make sure that
! the memory will not be deallocated before the thread
! has a chance to read it.
!
! This in turn means that the thread is responsible for freeing
! the passed-in data.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *my_thread(void *data) {
int number = *(int *)data;
sleep(1);
printf("The number is 0x%x!\n", number);
free(data);
return NULL;
}
pthread_t function_creates_thread(void) {
int *super_special_number = malloc(sizeof(int));
*super_special_number = 0x42;
pthread_t thread_handle;
pthread_create(&thread_handle, NULL, my_thread, super_special_number);
return thread_handle;
}
void function_says_hello(void) {
char greeting[] = "Hello there! Please patiently wait for your number!\n";
printf("%s", greeting);
}
int main(void) {
pthread_t thread_handle = function_creates_thread();
function_says_hello();
pthread_join(thread_handle, NULL);
}
All Links
- All Tutorial Questions
- All Tutorial Answers
- All Laboratory Exercises
- All Laboratory Sample Solutions
- All Weekly Test Questions
- All Weekly Test Sample Answers
- Course Intro
- Mips Basics
- Mips Control
- Mips Data
- Mips Functions
- Integers
- Bitwise Operations
- Floating Point
- Files
- Unicode
- Processes
- Virtual Memory
- Threads
- Exam
Print hello world in MIPS.