Computer Systems Fundamentals
Course Resources
- Administrivia
- Resources
- Resources
- MIPS Resources
- Lab/Test/Assignment
- Other
Week-by-Week
- Tutorial
- Laboratory
- Wednesday 18 September Lecture Topics
- Thursday 19 September Lecture Topics
- Tutorial
- Laboratory
- Wednesday 25 September Lecture Topics
- Thursday 26 September Lecture Topics
- Tutorial
- Laboratory
- Weekly Test
- Wednesday 02 October Lecture Topics
- Thursday 03 October Lecture Topics
- Tutorial
- Laboratory
- Weekly Test
- Wednesday 09 October Lecture Topics
- Thursday 10 October Lecture Topics
- Tutorial
- Laboratory
- Weekly Test
- Lecture Topics
-
- Lecture Topics
Topic-by-Topic
-
18s2 COMP1521 Lecture Video: Data representation: integer, pointer, float
-
18s2 COMP1521 Lecture Video: Data representation: integer, pointer, float
- (Short and sweet, with lots of well-explained examples)
- (longer but informative)
#include <stdio.h>
#include <limits.h>
int main(void) {
char c;
printf("char %lu bytes min=%20d, max=%20d\n", sizeof c, CHAR_MIN, CHAR_MAX);
signed char sc;
printf("signed char %lu bytes min=%20d, max=%20d\n", sizeof sc, SCHAR_MIN, SCHAR_MAX);
unsigned char uc;
printf("unsigned char %lu bytes min=%20d, max=%20d\n", sizeof uc, 0, UCHAR_MAX);
short s;
printf("short %lu bytes min=%20d, max=%20d\n", sizeof s, SHRT_MIN, SHRT_MAX);
unsigned short us;
printf("unsigned short %lu bytes min=%20d, max=%20d\n", sizeof us, 0, USHRT_MAX);
int i;
printf("int %lu bytes min=%20d, max=%20d\n", sizeof i, INT_MIN, INT_MAX);
unsigned int ui;
printf("unsigned int %lu bytes min=%20d, max=%20d\n", sizeof ui, 0, UINT_MAX);
long l;
printf("long %lu bytes min=%20ld, max=%20ld\n", sizeof l, LONG_MIN, LONG_MAX);
unsigned long ul;
printf("unsigned long %lu bytes min=%20d, max=%20lu\n", sizeof ul, 0, ULONG_MAX);
long long ll;
printf("long long %lu bytes min=%20lld, max=%20lld\n", sizeof ll, LLONG_MIN, LLONG_MAX);
unsigned long long ull;
printf("unsigned long long %lu bytes min=%20d, max=%20llu\n", sizeof ull, 0, ULLONG_MAX);
return 0;
}
#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 may be signed (e.g. x86) or unsigned (powerpc)
//
// if char is signed (-128..127)
// loop will incorrect exit for a byte containing 0xFF
//
// if char is unsigned (0..255)
// loop will never exit
//
// fix bug by making c int
//
char c;
while ((c = getchar()) != EOF) {
putchar(c);
}
return 0;
}
Print binary representation of ints
#include <stdio.h>
void print_bits(int value);
int get_nth_bit(int value, int n);
int main(void) {
int a = 0;
printf("Enter an int: ");
scanf("%d", &a);
print_bits(a);
printf("\n");
return 0;
}
// print the binary representation of a value
void print_bits(int value) {
// sizeof returns size in bytes and 1 byte == 8 bits
int how_many_bits = 8 * (sizeof value);
for (int i = how_many_bits - 1; i >= 0; i--) {
int bit = get_nth_bit(value, i);
printf("%d", bit);
}
}
// extract the nth bit from a value
int get_nth_bit(int value, int n) {
return (value >> n) & 1;
}
Demonstrate C bitwise operations
#include <stdio.h>
void print_bits_hex(char *description, short n);
void print_bits(short value);
int get_nth_bit(short value, int n);
int main(void) {
short a = 0;
printf("Enter a: ");
scanf("%hd", &a);
short b = 0;
printf("Enter b: ");
scanf("%hd", &b);
printf("Enter c: ");
int c = 0;
scanf("%d", &c);
print_bits_hex(" a = ", a);
print_bits_hex(" b = ", b);
print_bits_hex(" ~a = ", ~a);
print_bits_hex(" a & b = ", a & b);
print_bits_hex(" a | b = ", a | b);
print_bits_hex(" a ^ b = ", a ^ b);
print_bits_hex("a >> c = ", a >> c);
print_bits_hex("a << c = ", a << c);
return 0;
}
// print description then binary, hex and decimal representation of value
void print_bits_hex(char *description, short value) {
printf("%s", description);
print_bits(value);
printf(" = 0x%04x = %d\n", value & 0xFFFF, value);
}
// print the binary representation of a value
void print_bits(short value) {
// sizeof returns size in bytes and 1 byte == 8 bits
int how_many_bits = 8 * (sizeof value);
for (int i = how_many_bits - 1; i >= 0; i--) {
int bit = get_nth_bit(value, i);
printf("%d", bit);
}
}
// extract the nth bit from a value
int get_nth_bit(short value, int n) {
return (value >> n) & 1;
}
Print hexadecimal directly (without using printf) usign bitwise opeators to extract digits
#include <stdio.h>
#include <stdio.h>
void print_hex(int n);
int main(void) {
int a = 0;
printf("Enter an int: ");
scanf("%d", &a);
printf("%d = 0x", a);
print_hex(a);
printf("\n");
return 0;
}
void print_hex(int n) {
int which_digit = 2 * (sizeof n);
while (which_digit > 0) {
which_digit--;
int digit = (n >> (4 * which_digit)) & 0xF;
int ascii = "0123456789ABCDEF"[digit];
putchar(ascii);
}
}
#include <stdio.h>
// Andrew Taylor - andrewt@unsw.edu.au
// 16/9/2019
// Represent a small set of possible values using bits
#define FIRE_TYPE 0x0001
#define FIGHTING_TYPE 0x0002
#define WATER_TYPE 0x0004
#define FLYING_TYPE 0x0008
#define POISON_TYPE 0x0010
#define ELECTRIC_TYPE 0x0020
#define GROUND_TYPE 0x0040
#define PSYCHIC_TYPE 0x0080
#define ROCK_TYPE 0x0100
#define ICE_TYPE 0x0200
#define BUG_TYPE 0x0400
#define DRAGON_TYPE 0x0800
#define GHOST_TYPE 0x1000
#define DARK_TYPE 0x2000
#define STEEL_TYPE 0x4000
#define FAIRY_TYPE 0x8000
int main(void) {
// give our pokemon 3 types
int pokemon_type = BUG_TYPE | POISON_TYPE | FAIRY_TYPE;
printf("0x%04xd\n", pokemon_type);
if (pokemon_type & POISON_TYPE) {
printf("Danger poisonous\n"); // prints
}
if (pokemon_type & GHOST_TYPE) {
printf("Scary\n"); // does not print
}
}
Respresent set of small non-negative integers using bit-operations
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
typedef uint64_t set;
#define MAX_SET_MEMBER ((int)(8 * sizeof(set) - 1))
#define EMPTY_SET 0
set set_add(int x, set a);
set set_union(set a, set b);
set set_intersection(set a, set b);
set set_member(int x, set a);
int set_cardinality(set a);
set set_read(char *prompt);
void set_print(char *description, set a);
void print_bits_hex(char *description, set n);
void print_bits(set value);
int get_nth_bit(set value, int n);
int main(void) {
printf("Set members can be 0-%d, negative number to finish\n", MAX_SET_MEMBER);
set a = set_read("Enter set a: ");
set b = set_read("Enter set b: ");
print_bits_hex("a = ", a);
print_bits_hex("b = ", b);
set_print("a = ", a);
set_print("b = ", b);
set_print("a union b = ", set_union(a, b));
set_print("a intersection b = ", set_intersection(a, b));
printf("cardinality(a) = %d\n", set_cardinality(a));
printf("is_member(42, a) = %d\n", (int)set_member(42, a));
return 0;
}
set set_add(int x, set a) {
return a | ((set)1 << x);
}
set set_union(set a, set b) {
return a | b;
}
set set_intersection(set a, set b) {
return a & b;
}
// return a non-zero value iff x is a member of a
set set_member(int x, set a) {
assert(x >= 0 && x < MAX_SET_MEMBER);
return a & ((set)1 << x);
}
// return size of set
int set_cardinality(set a) {
int n_members = 0;
while (a != 0) {
n_members += a & 1;
a >>= 1;
}
return n_members;
}
set set_read(char *prompt) {
printf("%s", prompt);
set a = EMPTY_SET;
int x;
while (scanf("%d", &x) == 1 && x >= 0) {
a = set_add(x, a);
}
return a;
}
// print out member of the set in increasing order
// for example {5,11,56}
void set_print(char *description, set a) {
printf("%s", description);
printf("{");
int n_printed = 0;
for (int i = 0; i < MAX_SET_MEMBER; i++) {
if (set_member(i, a)) {
if (n_printed > 0) {
printf(",");
}
printf("%d", i);
n_printed++;
}
}
printf("}\n");
}
// print description then binary, hex and decimal representation of value
void print_bits_hex(char *description, set value) {
printf("%s", description);
print_bits(value);
printf(" = 0x%08lx = %ld\n", value, value);
}
// print the binary representation of a value
void print_bits(set value) {
// sizeof returns size in bytes and 1 byte == 8 bits
int how_many_bits = 8 * (sizeof value);
for (int i = how_many_bits - 1; i >= 0; i--) {
int bit = get_nth_bit(value, i);
printf("%d", bit);
}
}
// extract the nth bit from a value
int get_nth_bit(set value, int n) {
return (value >> n) & 1;
}
#include <stdio.h>
#include <stdint.h>
int main(void) {
// int16_t is a signed type (-32768..32767)
// all operations below are defined 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 - 1 type is int
j = ((uint64_t)1) << 33; // ok
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//
// copy stdin to stdout xor each byte with value supplied on STDIN
//
//
// Try
// $ dcc xor.c -o xor
// $ <xor.c xor 42
// $ <xor.c xor 42|xor 42
//
int main(int argc, char *argv[]) {
assert(argc == 2);
char key = strtol(argv[1], NULL, 0);
int c;
while ((c = getchar()) != EOF) {
int xor_c = c ^ key;
putchar(xor_c);
}
return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
//
// use a union to alias uint16_t, uint32_t,uint64_t types
// to an array of bytes so we can see order (endian-ness)
// bytes of integer type stored in memory
//
// what this program prints vary between platforms:
// https://en.wikipedia.org/wiki/Endianness
// CSE machines are little-endian
//
union overlay {
uint16_t s;
uint32_t i;
uint64_t l;
uint8_t bytes[8];
};
void print_bytes(char *message, void *v, int n);
int main(void) {
union overlay u = {.l = 0};
u.s = 0x1234;
print_bytes("uint16_t s = 0x1234 ", u.bytes, 2); // @cse prints 34 12
u.i = 0x12345678;
print_bytes("uint32_t i = 0x12345678 ", u.bytes, 4); // @cse prints 78 56 34 12
u.l = 0x123456789abcdef0;
print_bytes("uint64_t l = 0x123456789abcdef0", u.bytes, 8); // @cse prints f0 de bc 9a 78 56 34 12
if (u.bytes[0] == 0xf0) {
printf("little-endian machine\n");
} else if (u.bytes[0] == 0x01) {
printf("big-endian machine\n");
} else {
printf("unusual machine\n");
}
}
void print_bytes(char *message, void *v, int n) {
uint8_t *p = v;
printf("%s, bytes[0..%d] = ", message, n - 1);
for (int i = 0; i < n; i++) {
printf("%02x ", p[i]);
}
printf("\n");
}
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main(void) {
double array[10];
for (int i = 0; i < 10; i++) {
printf("&array[%d]=%p\n", i, &array[i]);
}
printf("\nexample computation for address of array element \\n\n");
uint64_t a = (uint64_t)&array[0];
printf("&array[0] + 7 * sizeof (double) = 0x%lx\n", a + 7 * sizeof (double));
printf("&array[0] + 7 * %lx = 0x%lx\n", sizeof (double), a + 7 * sizeof (double));
printf("0x%lx + 7 * %lx = 0x%lx\n", a, sizeof (double), a + 7 * sizeof (double));
printf("&array[7] = %p\n", &array[7]);
}
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
void print_bytes(void *v, int n);
struct s1 {
uint8_t c1;
uint64_t l1;
uint8_t c2;
uint64_t l2;
uint8_t c3;
uint64_t l3;
uint8_t c4;
uint64_t l4;
};
struct s2 {
uint64_t l1;
uint64_t l2;
uint64_t l3;
uint64_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("&(v2.c1) = %p\n", &(v2.l1));
printf("&(v2.l1) = %p\n", &(v2.l2));
}
#include <stdio.h>
int main(void) {
printf("I love MIPS\n");
return 0;
}
main:
la $a0, string # get addr of string
li $v0, 4 # 4 is print string syscall
syscall
jr $ra
.data
string:
.asciiz "I love MIPS\n"
add 17 and 25 and print result
#include <stdio.h>
int main(void) {
int x = 17;
int y = 25;
printf("%d\n", x + y);
return 0;
}
#include <stdio.h>
int main(void) {
int x, y, z;
x = 17;
y = 25;
z = x + y;
printf("%d", z);
printf("\n");
return 0;
}
add 17 and 25 and print result
main: # x, y, z in $t0, $t1, $t2,
li $t0, 17 # x = 17;
li $t1, 25 # y = 25;
add $t2, $t1, $t0 # z = x + y
move $a0, $t2 # printf("%d", a0);
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
li $v0, 0 # return 0
jr $ra
read a number and print whther its odd or even
#include <stdio.h>
int main(void) {
int x;
printf("Enter a number: ");
scanf("%d", &x);
if ((x & 1) == 0) {
printf("Even\n");
} else {
printf("Odd\n");
}
return 0;
}
#include <stdio.h>
int main(void) {
int x, v0;
printf("Enter a number: ");
scanf("%d", &x);
v0 = x & 1;
if (v0 == 1) goto odd;
printf("Even\n");
goto end;
odd:
printf("Odd\n");
end:
return 0;
}
read a number and print whther its odd or even
main:
la $a0, string0 # printf("Enter a number: ");
li $v0, 4
syscall
li $v0, 5 # scanf("%d", x);
syscall
and $t0, $v0, 1 # if (x & 1 == 0) {
beq $t0, 1, odd
la $a0, string1 # printf("Even\n");
li $v0, 4
syscall
b end
odd: # else
la $a0, string2 # printf("Odd\n");
li $v0, 4
syscall
end:
li $v0, 0 # return 0
jr $ra
.data
string0:
.asciiz "Enter a number: "
string1:
.asciiz "Even\n"
string2:
.asciiz "Odd\n"
print integers 1..10 one per line
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 10; i++) {
printf("%d\n", i);
}
return 0;
}
#include <stdio.h>
int main(void) {
int i;
i = 1;
loop:
if (i > 10) goto end;
i++;
printf("%d", i);
printf("\n");
goto loop;
end:
return 0;
}
print integers 1..10 one per line
main: # int main(void) {
# int i; // in register $t0
li $t0, 1 # i = 1;
loop: # loop:
bgt $t0, 10 end # if (i > 10) goto end;
move $a0, $t0 # printf("%d" i);
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
add $t0, $t0 1 # i++;
b loop # goto loop;
end:
li $v0, 0 # return 0
jr $ra
calculate 1*1 + 2*2 + ... + 99 * 99 + 100 * 100
#include <stdio.h>
int main(void) {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i * i;
}
printf("%d\n", sum);
return 0;
}
#include <stdio.h>
// sum of first 100 squares.
int main(void) {
int i, sum, t3;
sum = 0;
i = 0;
loop:
if (i > 100) goto end;
t3 = i * i;
sum = sum + t3;
i = i + 1;
goto loop;
end:
printf("%d", sum);
printf("\n");
return 0;
}
calculate 1*1 + 2*2 + ... + 99 * 99 + 100 * 100
sum in $t0, i in $t1
sum in $t0, i in $t1
main:
li $t0, 0 # sum = 0;
li $t1, 0 # i = 0
loop:
bgt $t1, 100 end # if (i > 100) goto end;
mul $t3, $t1, $t1 # t3 = i * i;
add $t0, $t0, $t3 # sum = sum + t3;
add $t1, $t1, 1 # i = i + 1;
b loop
end:
move $a0, $t0 # printf("%d", sum);
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
li $v0, 0 # return 0
jr $ra
#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 use variables stored in memory and print
result
main: # x, y, z in $t0, $t1, $t2,
li $t0, 17 # x = 17;
sw $t0, x
li $t0, 25 # y = 25;
sw $t0, y
lw $t0, x
lw $t1, y
add $t2, $t1, $t0 # z = x + y
sw $t2, z
lw $a0, z # printf("%d", a0);
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
li $v0, 0 # return 0
jr $ra
.data
x: .word 0
y: .word 0
z: .word 0
read 10 numbers into an array then print the 10 numbers
#include <stdio.h>
int numbers[10] = { 0 };
int main(void) {
int i;
i = 0;
while (i < 10) {
printf("Enter a number: ");
scanf("%d", &numbers[i]);
i++;
}
i = 0;
while (i < 10) {
printf("%d\n", numbers[i]);
i++;
}
return 0;
}
read 10 numbers into an array then print the 10 numbers
i in register $t0 registers $t1, $t2 & $t3 used to hold temporary results
i in register $t0 registers $t1, $t2 & $t3 used to hold temporary results
main:
li $t0, 0 # i = 0
loop0:
bge $t0, 10, end0 # while (i < 10) {
la $a0, string0 # printf("Enter a number: ");
li $v0, 4
syscall
li $v0, 5 # scanf("%d", &numbers[i]);
syscall #
mul $t1, $t0, 4 # calculate &numbers[i]
la $t2, numbers #
add $t3, $t1, $t2 #
sw $v0, ($t3) # store entered number in array
add $t0, $t0, 1 # i++;
b loop0 # }
end0:
li $t0, 0 # i = 0
loop1:
bge $t0, 10, end1 # while (i < 10) {
mul $t1, $t0, 4 # calculate &numbers[i]
la $t2, numbers #
add $t3, $t1, $t2 #
lw $a0, ($t3) # load numbers[i] into $a0
li $v0, 1 # printf("%d", numbers[i])
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
add $t0, $t0, 1 # i++
b loop1 # }
end1:
li $v0, 0 # return 0
jr $ra
.data
numbers: # int numbers[10];
.word 0 0 0 0 0 0 0 0 0 0
string0:
.asciiz "Enter a number: "
read 10 integers then print them in reverse order
#include <stdio.h>
int numbers[10];
int main() {
int count;
count = 0;
while (count < 10) {
printf("Enter a number: ");
scanf("%d", &numbers[count]);
count++;
}
printf("Reverse order:\n");
count = 9;
while (count >= 0) {
printf("%d\n", numbers[count]);
count--;
}
return 0;
}
read 10 integers then print them in reverse order
count in register $t0 registers $t1 and $t2 used to hold temporary results
count in register $t0 registers $t1 and $t2 used to hold temporary results
main:
li $t0, 0 # count = 0
read:
bge $t0, 10, print # while (count < 10) {
la $a0, string0 # printf("Enter a number: ");
li $v0, 4
syscall
li $v0, 5 # scanf("%d", &numbers[count]);
syscall #
mul $t1, $t0, 4 # calculate &numbers[count]
la $t2, numbers #
add $t1, $t1, $t2 #
sw $v0, ($t1) # store entered number in array
add $t0, $t0, 1 # count++;
b read # }
print:
la $a0, string1 # printf("Reverse order:\n");
li $v0, 4
syscall
li $t0, 9 # count = 9;
next:
blt $t0, 0, end1 # while (count >= 0) {
mul $t1, $t0, 4 # printf("%d", numbers[count])
la $t2, numbers # calculate &numbers[count]
add $t1, $t1, $t2 #
lw $a0, ($t1) # load numbers[count] into $a0
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
sub $t0, $t0,1 # count--;
b next # }
end1:
li $v0, 0 # return 0
jr $ra
.data
numbers: # int numbers[10];
.word 0 0 0 0 0 0 0 0 0 0
string0:
.asciiz "Enter a number: "
string1:
.asciiz "Reverse order:\n"
#include <stdio.h>
#include <stdint.h>
int main(void) {
uint8_t b;
uint32_t u;
u = 0x03040506;
b = *(uint8_t *)&u;
printf("%d\n", b); // prints 6 on a little-endian machine
}
main:
li $t0, 0x03040506
sw $t0, u
lb $a0, 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 0
#include <stdio.h>
#include <stdint.h>
int main(void) {
uint8_t bytes[32];
uint32_t *i = (int *)bytes[1];
*i = 0x03040506; // store will not be aligned on a 4-byte boundary
printf("%d\n", bytes[1]);
}
main:
li $t0, 1
sb $t0, a # will succeed because no alignment needed
sh $t0, a # will fail a because is not aligned on 2-byte boundary
sw $t0, a # will fail a because is not aligned on 3-byte boundary
sh $t0, b # will succeeed because b is aligned on 2-byte boundary
sw $t0, b # will fail b because is not aligned on a 4-byte boundary
sh $t0, c # will succeeed because c is aligned on 2-byte boundary
sw $t0, c # will fail d because is not aligned on a 4-byte boundary
sh $t0, d # will succeeed because d is aligned on 2-byte boundary
sw $t0, d # will succeeed because d is aligned on a 4-byte boundary
sw $t0, e # will succeeed because e is aligned on a 4-byte boundary
sw $t0, f # will succeeed because f is aligned on a 4-byte boundary
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
a:
.space 1
b:
.space 4
c:
.space 2
d:
.space 4
.space 1
.align 2 # ensure e is on a 4 (2**2) byte boundary
e:
.space 4
.space 1
f:
.word 0 # word directive automaticatically aligns on 4 byte boundary
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main(void) {
double array[10];
for (int i = 0; i < 10; i++) {
printf("&array[%d]=%p\n", i, &array[i]);
}
printf("\nexample computation for address of array element \\n\n");
uint64_t a = (uint64_t)&array[0];
printf("&array[0] + 7 * sizeof (double) = 0x%lx\n", a + 7 * sizeof (double));
printf("&array[0] + 7 * %lx = 0x%lx\n", sizeof (double), a + 7 * sizeof (double));
printf("0x%lx + 7 * %lx = 0x%lx\n", a, sizeof (double), a + 7 * sizeof (double));
printf("&array[7] = %p\n", &array[7]);
}
non-portable code illustrating array indexing this relies
on pointers being implemented by memory addresses which
most compiled C implementations do
#include <stdio.h>
#include <stdint.h>
uint32_t array[10] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
int main(void) {
// use a typecast to assign array address to integer variable i
uint64_t i = (uint64_t)&array;
i += 7 * sizeof array[0]; // add 28 to i
// use a typecast to assign i to a pointer vaiable
uint32_t *y = (uint32_t *)i;
printf("*y = %d\n", *y); // prints 17
// compare to pointer arithmetic where adding 1
// moves to the next array element
uint32_t *z = array;
z += 7;
printf("*z = %d\n", *z); // prints 17
}
simple example of accessing an array element
#include <stdio.h>
int x[10];
int main(void) {
x[3] = 17;
}
main:
li $t0, 3
mul $t1, $t0, 4
la $t1, x
add $t2, $t1, $t0
li $t3, 17
sw $t3, ($t2)
# ...
.data
x: .space 40
#include <stdio.h>
#define X 3
#define Y 4
int main(void) {
int array[X][Y];
for (int x = 0; x < X; x++) {
for (int y = 0; y < Y; y++) {
array[x][y] = x + y;
}
}
for (int x = 0; x < X; x++) {
for (int y = 0; y < Y; y++) {
printf("%d ", array[x][y]);
}
printf("\n");
}
printf("sizeof array[2][3] = %lu\n", sizeof array[2][3]);
printf("sizeof array[1] = %lu\n", sizeof array[1]);
printf("sizeof array = %lu\n", sizeof array);
printf("&array=%p\n", &array);
for (int x = 0; x < X; x++) {
printf("&array[%d]=%p\n", x, &array[x]);
for (int y = 0; y < Y; y++) {
printf("&array[%d][%d]=%p\n", x, y, &array[x][y]);
}
}
}
non-portable code illustrating 2d-array indexing this
relies on pointers being implemented by memory addresses
which most compiled C implementations do
#include <stdio.h>
#include <stdint.h>
uint32_t array[3][4] = {{10, 11, 12, 13}, {14, 15, 16, 17}, {18, 19, 20, 21}};
int main(void) {
// use a typecast to assign array address to integer variable i
uint64_t i = (uint64_t)&array;
// i += (2 * 16) + 2 * 4
i += (2 * sizeof array[0]) + 2 * sizeof array[0][0];
// use a typecast to assign i to a pointer vaiable
uint32_t *y = (uint32_t *)i;
printf("*y = %d\n", *y); // prints 20
}
#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));
}
non-portable code illustrating access to a struct field
this relies on pointers being implemented by memory
addresses which most compiled C implementations do
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
struct simple {
char c;
uint32_t i;
double d;
};
struct simple s = { 'Z', 42, 3.14159 };
int main(void) {
// use a typecast to assign struct address to integer variable i
uint64_t i = (uint64_t)&s;
// 3 bytes of padding - likely but not guaranteed
i += (sizeof s.c) + 3;
// use a typecast to assign i to a pointer vaiable
uint32_t *y = (uint32_t *)i;
printf("*y = %d\n", *y); // prints 42
}
simple example of returning from a function
#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:
sub $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
add $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 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 and $a0 on stack for
simplicity we are not using a frame pointer
main:
sub $sp, $sp, 4 # move stack pointer down to make room
sw $ra, 0($sp) # save $ra on $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
lw $ra, 0($sp) # recover $ra from $stack
add $sp, $sp, 4 # move stack pointer back up to what it was when main called
li $v0, 0 # return 0 from function main
jr $ra #
answer:
li $v0, 42 #
jr $ra # return from answer
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 for simplicity we are not using
a frame pointer
main:
sub $sp, $sp, 4 # move stack pointer down to make room
sw $ra, 0($sp) # save $ra on $stack
li $a0, 6 # sum_product(10, 12);
li $a1, 7
jal product
move $a0, $v0 # printf("%d", z);
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
lw $ra, 0($sp) # recover $ra from $stack
add $sp, $sp, 4 # move stack pointer back up to what it was when main called
li $v0, 0 # return 0 from function main
jr $ra # return from function main
sum_product:
sub $sp, $sp, 12 # move stack pointer down to make room
sw $ra, 8($sp) # save $ra on $stack
sw $a1, 4($sp) # save $a1 on $stack
sw $a0, 0($sp) # save $a0 on $stack
li $a0, 6 # product(6, 7);
li $a1, 7
jal product
lw $a1, 4($sp) # restore $a1 from $stack
lw $a0, 0($sp) # restore $a0 from $stack
add $v0, $v0, $a0 # add a and b to value returned in $v0
add $v0, $v0, $a1 # and put result in $v0 to be returned
lw $ra, 8($sp) # restore $ra from $stack
add $sp, $sp, 12 # move stack pointer back up to what it was when main called
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
stack for simplicity we are not using a frame pointer
main:
sub $sp, $sp, 4 # move stack pointer down to make room
sw $ra, 0($sp) # save $ra on $stack
li $a0, 1 # two(1);
jal two
lw $ra, 0($sp) # recover $ra from $stack
add $sp, $sp, 4 # move stack pointer back up to what it was when main called
jr $ra # return from function main
two:
sub $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
bge $a0, 1000000, print
mul $a0, $a0, 2 # restore $a0 from $stack
jal two
print:
lw $a0, 0($sp) # restore $a0 from $stack
li $v0, 1 # printf("%d");
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
lw $ra, 4($sp) # restore $ra from $stack
add $sp, $sp, 8 # move stack pointer back up to what it was when main called
jr $ra # return from two
calculate the length of a string using a strlen like
function
#include <stdio.h>
int my_strlen(char *s);
int main(void) {
int i = my_strlen("Hello Andrew");
printf("%d\n", i);
return 0;
}
int my_strlen(char *s) {
int length = 0;
while (s[length] != 0) {
length++;
}
return length;
}
calculate the length of a string using a strlen like
function
#include <stdio.h>
int my_strlen(char *s);
int main(void) {
int i = my_strlen("Hello Andrew");
printf("%d\n", i);
return 0;
}
int my_strlen(char *s) {
int length = 0;
loop:
if (s[length] == 0) goto end;
length++;
goto loop;
end:
return length;
}
calculate the length of a string using a strlen like
function
main:
sub $sp, $sp, 4 # move stack pointer down to make room
sw $ra, 0($sp) # save $ra on $stack
la $a0, string # my_strlen("Hello Andrew");
jal my_strlen
move $a0, $v0 # printf("%d", i);
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
lw $ra, 0($sp) # recover $ra from $stack
add $sp, $sp, 4 # move stack pointer back up to what it was when main called
li $v0, 0 # return 0 from function main
jr $ra #
my_strlen: # length in t0, s in $a0
li $t0, 0
loop: # while (s[length] != 0) {
add $t1, $a0, $t0 # calculate &s[length]
lb $t2, 0($t1) # load s[length] into $t2
beq $t2, 0, end #
add $t0, $t0, 1 # length++;
b loop # }
end:
move $v0, $t0 # return length
jr $ra
.data
string:
.asciiz "Hello Andrew"
#include <stdio.h>
int my_strlen(char *s);
int main(void) {
int i = my_strlen("Hello Andrew");
printf("%d\n", i);
return 0;
}
int my_strlen(char *s) {
int length = 0;
while (*s != 0) {
length++;
s++;
}
return length;
}
simple example of placing return address $ra and $a0 on
stack for simplicity we are not using a frame pointer
main:
sub $sp, $sp, 4 # move stack pointer down to make room
sw $ra, 0($sp) # save $ra on $stack
la $a0, string # my_strlen("Hello Andrew");
jal my_strlen
move $a0, $v0 # printf("%d", i);
li $v0, 1
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
lw $ra, 0($sp) # recover $ra from $stack
add $sp, $sp, 4 # move stack pointer back up to what it was when main called
jr $ra # return from function main
my_strlen: # length in t0, s in $a0
li $t0, 0
loop: #
lb $t1, 0($a0) # load *s into $t1
beq $t1, 0, end #
add $t0, $t0, 1 # length++
add $a0, $a0, 1 # s++
b loop #
end:
move $v0, $t0 # return length
jr $ra
.data
string:
.asciiz "Hello Andrew"
i in register $t0 registers $t1 and $t2 used to hold
temporary results
main:
sub $tp, $tp, 40 # move stack pointer down to make room
# to store array numbers on stack
li $t0, 0 # i = 0
loop0:
bge $t0, 10, end0 # while (i < 10) {
la $a0, string0 # printf("Enter a number: ");
li $v0, 4
syscall
li $v0, 5 # scanf("%d", &numbers[i]);
syscall #
mul $t1, $t0, 4 # calculate &numbers[i]
add $t1, $t1, $tp #
sw $v0, ($t1) # store entered number in array
add $t0, $t0, 1 # i++;
b loop0 # }
end0:
li $t0, 0 # i = 0
loop1:
bge $t0, 10, end1 # while (i < 10) {
mul $t1, $t0, 4
add $t1, $t1, $t0 # calculate &numbers[i]
lw $a0, ($t1) # load numbers[i] into $a0
li $v0, 1 # printf("%d", numbers[i])
syscall
li $a0, '\n' # printf("%c", '\n');
li $v0, 11
syscall
add $t0, $t0, 1 # i++
b loop1 # }
end1:
add $tp, $tp, 40 # move stack pointer back up to what it was when main called
li $v0, 0 # return 0 from function main
jr $ra #
.data
string0:
.asciiz "Enter a number: "
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:
sub $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
add $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:
sub $sp, $sp, 12 # move stack pointer down to make room
sw $fp, 8($sp) # save $fp on $stack
sw $ra, 4($sp) # save $ra on $stack
sw $a0, 0($sp) # save $a0 on $stack
add $fp, $sp, 12 # have frame pointer at start of stack frame
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 ...
lw $ra, -8($fp) # restore $ra from stack
move $sp, $fp # move stack pointer backup to what it was when main called
la $fp, -4($fp) # restore $fp from $stack
jr $ra # return
#include <stdio.h>
#include <stdint.h>
/*
$ clang stack_inspect.c
$ a.out
0: Address 0x7ffe1766c304 contains 3 <- a[0]
1: Address 0x7ffe1766c308 contains 5 <- x
2: Address 0x7ffe1766c30c contains 2a <- b
3: Address 0x7ffe1766c310 contains 1766c330 <- f frame pointer (64 bit)
4: Address 0x7ffe1766c314 contains 7ffe
5: Address 0x7ffe1766c318 contains 40120c <- f return address
6: Address 0x7ffe1766c31c contains 0
7: Address 0x7ffe1766c320 contains 22
8: Address 0x7ffe1766c324 contains 25
9: Address 0x7ffe1766c328 contains 9 <- a
10: Address 0x7ffe1766c32c contains 0
11: Address 0x7ffe1766c330 contains 401220 <- main return address
12: Address 0x7ffe1766c334 contains 0
13: Address 0x7ffe1766c338 contains c7aca09b <- main frame pointer (64 bit)
14: Address 0x7ffe1766c33c contains 7ff3
15: Address 0x7ffe1766c340 contains 0
*/
void f(int b) {
int x = 5;
uint32_t a[1] = { 3 };
for (int i = 0; i < 16; i++)
printf("%2d: Address %p contains %x\n", i, &a[i], a[0 + i]);
}
int main(void) {
int a = 9;
printf("function main is at address %p\n", &main);
printf("function f is at address %p\n", &f);
f(42);
return 0;
}
Run at CSE like this
$ gcc-7 invalid0.c -o invalid0 $ ./invalid0 42 42 42 77 77 77 77 77 77 77
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int a[10];
int b[10];
printf("a[0] is at address %p\n", &a[0]);
printf("a[9] is at address %p\n", &a[9]);
printf("b[0] is at address %p\n", &b[0]);
printf("b[9] is at address %p\n", &b[9]);
for (int i = 0; i < 10; i++) {
a[i] = 77;
}
// loop writes to b[10] .. b[12] which don't exist -
// with gcc 7.3 on x86_64/Linux
// b[12] is stored where a[0] is stored
// with gcc 7 on CSE lab machines
// b[10] is stored where a[0] is stored
for (int i = 0; i <= 12; i++) {
b[i] = 42;
}
// prints 42 77 77 77 77 77 77 77 77 77 on x86_64/Linux
// prints 42 42 42 77 77 77 77 77 77 77 at CSE
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
Run at CSE like this
$ gcc-7 invalid1.c -o invalid1 $ ./invalid1 42 42 42 77 77 77 77 77 77 77
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
int a[10];
printf("i is at address %p\n", &i);
printf("a[0] is at address %p\n", &a[0]);
printf("a[9] is at address %p\n", &a[9]);
printf("a[10] would be stored at address %p\n", &a[10]);
// loop writes to a[10] .. a[11] which don't exist -
// but with gcc 7 on x86_64/Linux
// i would be stored where a[11] is stored
for (i = 0; i <= 11; i++) {
a[i] = 0;
}
return 0;
}
Run at CSE like this
$ gcc-7 invalid2.c -o invalid2 $ ./invalid2 answer=42
#include <stdio.h>
void f(int x);
int main(void) {
int answer = 36;
printf("answer is stored at address %p\n", &answer);
f(5);
printf("answer=%d\n", answer); // prints 42 not 36
return 0;
}
void f(int x) {
int a[10];
// a[19] doesn't exist
// with gcc-7 at CSE variable answer in main
// happens to be where a[19] would be
printf("a[19] would be stored at address %p\n", &a[19]);
a[19] = 42;
}
Run at CSE like this
$ gcc-7 invalid3.c -o invalid3 $ ./invalid3
I will never be printed. argc was 1 $
#include <stdio.h>
#include <stdlib.h>
void f(void);
void f(void);
int main(int argc, char *argv[]) {
f();
if (argc > 0) {
printf("I will always be printed.\n");
}
if (argc <= 0) {
printf("I will never be printed.\n");
}
printf("argc was %d\n", argc);
return 0;
}
void f() {
int a[10];
// function f has it return address on the stack
// the call of function f from main should return to
// the next statement which is: if (argc > 0)
//
// with gcc7 at CSE f's return address is stored where a[14] would be
//
// so changing a[14] changes where the function returns
//
// adding 24 to a[11] happens to cause it to return several statements later
// at the printf("I will never be printed.\n");
a[14] += 24;
}
Run at CSE like this
$ gcc-7 invalid4.c -o invalid4 $ ./invalid4 authenticated is at address 0xff94bf44 password is at address 0xff94bf3c
Enter your password: 123456789
Welcome. You are authorized. $
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int authenticated = 0;
char password[8];
printf("authenticated is at address %p\n", &authenticated);
printf("password[8] would be at address %p\n", &password[8]);
printf("Enter your password: ");
int i = 0;
int ch = getchar();
while (ch != '\n' && ch != EOF) {
password[i] = ch;
ch = getchar();
i = i + 1;
}
password[i] = '\0';
if (strcmp(password, "secret") == 0) {
authenticated = 1;
}
// a password longer than 8 characters will overflow the array password
// the variable authenticated is at the address where
// where password[8] would be and gets overwritten
//
// This allows access without knowing the correct password
if (authenticated) {
printf("Welcome. You are authorized.\n");
} else {
printf("Welcome. You are unauthorized. Your death will now be implemented.\n");
printf("Welcome. You will experience a tingling sensation and then death. \n");
printf("Remain calm while your life is extracted.\n");
}
return 0;
}
hello world implemented with direct syscall
#include <unistd.h>
int main(void) {
char bytes[16] = "Hello, Andrew!\n";
// argument 1 to syscall is system call number, 1 == 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, 15); // prints Hello, Andrew! on stdout
return 0;
}
copy stdin to stdout implemented with system calls
#include <unistd.h>
int main(void) {
while (1) {
char bytes[4096];
// system call number 0 == read
// read system call takes 3 arguments:
// 1) file descriptor, 1 == stdin
// 2) memory address to put bytes read
// 3) maximum number of bytes read
// returns number of bytes actually read
long bytes_read = syscall(0, 0, bytes, 4096);
if (bytes_read <= 0) {
break;
}
syscall(1, 1, bytes, bytes_read); // prints bytes to stdout
}
return 0;
}
cp <file1> <file2> implemented with syscalls
and *zero* error handling
#include <unistd.h>
int main(int argc, char *argv[]) {
// system call number 2 == open
// open system call takes 3 arguments:
// 1) address of zero-terminated string containing pathname of file to open
// 2) bitmap indicating whether to write, read, ... file
// 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], 0, 0);
long write_file_descriptor = syscall(2, argv[2], 0x41, 0644);
while (1) {
char bytes[4096];
long bytes_read = syscall(0, read_file_descriptor, bytes, 4096);
if (bytes_read <= 0) {
break;
}
syscall(1, write_file_descriptor, bytes, bytes_read);
}
return 0;
}
hello world implemented with libc
#include <unistd.h>
int main(void) {
char bytes[16] = "Hello, Andrew!\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, 15); // prints Hello, Andrew! on stdout
return 0;
}
copy stdin to stdout implemented with libc
#include <unistd.h>
int main(void) {
while (1) {
char bytes[4096];
// system call number 0 == read
// read system call takes 3 arguments:
// 1) file descriptor, 1 == stdin
// 2) memory address to put bytes read
// 3) maximum number of bytes read
// returns number of bytes actually read
ssize_t bytes_read = read(0, bytes, 4096);
if (bytes_read <= 0) {
break;
}
write(1, bytes, bytes_read); // prints bytes to 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[]) {
// 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, 0644);
while (1) {
char bytes[4096];
ssize_t bytes_read = read(read_file_descriptor, bytes, 4096);
if (bytes_read <= 0) {
break;
}
write(write_file_descriptor, bytes, bytes_read);
}
return 0;
}
hello world implemented with fputc
#include <stdio.h>
int main(void) {
char bytes[16] = "Hello, Andrew!\n";
for (int i = 0; i < 15; i++) {
fputc(bytes[i], stdout);
}
// or as we know bytes is null-terminated: bytes[15] == '\0'
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);
}
return 0;
}
hello world implemented with fputs
#include <stdio.h>
int main(void) {
char bytes[] = "Hello, Andrew!\n";
fputs(bytes, stdout); // relies on bytes being nul-terminated
return 0;
}
hello world implemented with fwrite
#include <stdio.h>
int main(void) {
char bytes[] = "Hello, Andrew!\n";
fwrite(bytes, 1, 15, stdout); // prints Hello, Andrew! on stdout
return 0;
}
copy stdin to stdout implemented with fgetc
#include <stdio.h>
int main(void) {
// c can not be char (common bug)
// fgetc returns 0..255 and EOF (usually -1)
int c;
// return bytes from the stream (stdin) one at a time
while ((c = fgetc(stdin)) != EOF) {
fputc(c, stdout); // write the byte to standard output
}
return 0;
}
copy stdin to stdout implemented with fgets
#include <stdio.h>
int main(void) {
// return bytes from the stream (stdin) line at a time
// BUFSIZ is defined in stdio.h - its an efficient value to use
// but any value would work
// NOTE: fgets returns a null-terminated string
// in other words a 0 byte marks the end of the bytes read
// so fgets can not be used to read data containing bytes which are 0
// also fputs takes a null-terminated string so it can not be used to write bytes which are 0
// in other word you can't use fget/fputs for binary data e.g. jpgs
char line[BUFSIZ];
while (fgets(line, BUFSIZ, stdin) != NULL) {
fputs(line, stdout);
}
return 0;
}
copy stdin to stdout implemented with fwrite
#include <stdio.h>
int main(void) {
while (1) {
char bytes[4096];
// system call number 0 == read
// read system call takes 3 arguments:
// 1) file descriptor, 1 == stdin
// 2) memory address to put bytes read
// 3) maximum number of bytes read
// returns number of bytes actually read
ssize_t bytes_read = fread(bytes, 1, 4096, stdin);
if (bytes_read <= 0) {
break;
}
fwrite(bytes, 1, bytes_read, stdout); // prints bytes to stdout
}
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], "rb");
if (input_stream == NULL) {
perror(argv[1]); // prints why the open failed
return 1;
}
FILE *output_stream = fopen(argv[2], "wb");
if (output_stream == NULL) {
perror(argv[2]);
return 1;
}
int c; // not char!
while ((c = fgetc(input_stream)) != EOF) {
fputc(c, output_stream);
}
// close occurs automatically on exit
// so these lines not nee
fclose(input_stream);
fclose(output_stream);
return 0;
}
cp <file1> <file2> implemented with libc and
*zero* error handling
#include <stdio.h>
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
// b = binary mode - not needed on Linux, OSX (POSIX) systems
// - needed on Windows
FILE *read_stream = fopen(argv[1], "rb");
FILE *write_stream = fopen(argv[2], "wb");
// this will be slightly faster than an a fgetc/fputc loop
while (1) {
char bytes[BUFSIZ];
size_t bytes_read = fread(bytes, 1, 4096, read_stream);
if (bytes_read <= 0) {
break;
}
fwrite(bytes, 1, bytes_read, write_stream);
}
return 0;
}
Simple example of file creation creates file "hello.txt" containing 1 line ("Hello, Andrew!\n")
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
FILE *output_stream = fopen("hello.txt", "w");
if (output_stream == NULL) {
perror("hello.txt");
return 1;
}
fprintf(output_stream, "Hello, Andrew!\n");
fclose(output_stream);
return 0;
}
$ dcc create_append_truncate_fopen.c $ ./a.out open("hello.txt", "w") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:11 hello.txt fputs("Hello, Andrew!\n") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:11 hello.txt fclose -> -rw-r--r-- 1 andrewt andrewt 15 Oct 22 19:11 hello.txt fopen("hello.txt", "a") -> -rw-r--r-- 1 andrewt andrewt 15 Oct 22 19:11 hello.txt fputs("Hello again, Andrew!\n") -> -rw-r--r-- 1 andrewt andrewt 15 Oct 22 19:11 hello.txt fflush -> -rw-r--r-- 1 andrewt andrewt 36 Oct 22 19:11 hello.txt open("hello.txt", "w") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:11 hello.txt fputs("Good Bye Andrew!\n") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:11 hello.txt assa:files% ./a.out open("hello.txt", "w") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:12 hello.txt fputs("Hello, Andrew!\n") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:12 hello.txt fclose -> -rw-r--r-- 1 andrewt andrewt 15 Oct 22 19:12 hello.txt fopen("hello.txt", "a") -> -rw-r--r-- 1 andrewt andrewt 15 Oct 22 19:12 hello.txt fputs("Hello again, Andrew!\n") -> -rw-r--r-- 1 andrewt andrewt 15 Oct 22 19:12 hello.txt fflush -> -rw-r--r-- 1 andrewt andrewt 36 Oct 22 19:12 hello.txt open("hello.txt", "w") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:12 hello.txt fputs("Good Bye Andrew!\n") -> -rw-r--r-- 1 andrewt andrewt 0 Oct 22 19:12 hello.txt $ ls -l hello.txt -rw-r--r-- 1 andrewt andrewt 17 Oct 22 19:12 hello.txt $ cat hello.txt
Good Bye Andrew! $
#include <stdio.h>
#include <stdlib.h>
void show_file_state(char *message);
int main(int argc, char *argv[]) {
FILE *output_stream1 = fopen("hello.txt", "w"); // no error checking
// hello.txt will be created if it doesn't exist already
// if hello.txt previous existed it will now contain 0 bytes
show_file_state("open(\"hello.txt\", \"w\")");
fputs("Hello, Andrew!\n", output_stream1);
// the 15 bytes in "Hello, Andrew!\n" are buffered by the stdio library
// they haven't been written to hello.txt
// so it will still contain 0 bytes
show_file_state("fputs(\"Hello, Andrew!\\n\")");
fclose(output_stream1);
// The fclose will flush the buffered bytes to hello.txt
// hello.txt will now contain 15 bytes
show_file_state("fclose()");
FILE *output_stream2 = fopen("hello.txt", "a"); // no error checking
// because "a" was specified hello.txt will not be changed
// it will still contain 15 bytes
show_file_state("fopen(\"hello.txt\", \"a\")");
fputs("Hello again, Andrew!\n", output_stream2);
// the 21 bytes in "Hello again, Andrew!\n" are buffered by the stdio library
// they haven't been written to hello.txt
// so it will still contain 15 bytes
show_file_state("fputs(\"Hello again, Andrew!\\n\")");
fflush(output_stream2);
// The fflush will flush ahe buffered bytes to hello.txt
// hello.txt will now contain 36 bytes
show_file_state("fflush()");
FILE *output_stream3 = fopen("hello.txt", "w"); // no error checking
// because "w" was specified hello.txt will be truncated to zero length
// hello.txt will now contain 0 bytes
show_file_state("open(\"hello.txt\", \"w\")");
fputs("Good Bye Andrew!\n", output_stream3);
// the 17 bytes in "Good Bye Andrew!\" are buffered by the stdio library
// they haven't been written to hello.txt
// so it will still contain 0 bytes
show_file_state("fputs(\"Good Bye Andrew!\\n\")");
// if exit is called or main returns stdio flushes all stream
// this will leave hello.txt with 17 bytes
// but if a program terminates abnormally this doesn't happen
return 0;
}
void show_file_state(char *message) {
printf("%-32s -> ", message);
fflush(stdout);
system("ls -l hello.txt");
}
simple re-implementation of stdio functions fopen, fgetc,
fputc, fclose no buffering *zero* error handling for
clarity
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#define MY_EOF -1
// struct to hold data for a stream
typedef struct my_file {
int fd;
} my_file_t;
my_file_t *my_fopen(char *file, char *mode) {
int fd = -1;
if (mode[0] == 'r') {
fd = open(file, O_RDONLY);
} else if (mode[0] == 'w') {
fd = open(file, O_WRONLY | O_CREAT, 0666);
} else if (mode[0] == 'a') {
fd = open(file, O_WRONLY | O_APPEND);
}
if (fd == -1) {
return NULL;
}
my_file_t *f = malloc(sizeof *f);
f->fd = fd;
return f;
}
int my_fgetc(my_file_t *f) {
uint8_t byte;
int bytes_read = read(f->fd, &byte, 1);
if (bytes_read == 1) {
return byte;
} else {
return MY_EOF;
}
}
int my_fputc(int c, my_file_t *f) {
uint8_t byte = c;
if (write(f->fd, &byte, 1) == 1) {
return byte;
} else {
return MY_EOF;
}
}
int my_fclose(my_file_t *f) {
int result = close(f->fd);
free(f);
return result;
}
int main(int argc, char *argv[]) {
my_file_t *input_stream = my_fopen(argv[1], "r");
if (input_stream == NULL) {
perror(argv[1]);
return 1;
}
my_file_t *output_stream = my_fopen(argv[2], "w");
if (output_stream == NULL) {
perror(argv[2]);
return 1;
}
int c;
while ((c = my_fgetc(input_stream)) != MY_EOF) {
my_fputc(c, output_stream);
}
return 0;
}
simple re-implementation of stdio functions fopen, fgetc,
fputc, fclose input buffering *zero* error handling for
clarity
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
// how equivalents for EOF & BUFSIZ from stdio.h
#define MY_EOF -1
#define MY_BUFSIZ 512
// struct to hold data for a stream
typedef struct my_file {
int fd;
int n_buffered_bytes;
int next_byte;
uint8_t buffer[MY_BUFSIZ];
} my_file_t;
my_file_t *my_fopen(char *file, char *mode) {
int fd = -1;
if (mode[0] == 'r') {
fd = open(file, O_RDONLY);
} else if (mode[0] == 'w') {
fd = open(file, O_WRONLY | O_CREAT, 0666);
} else if (mode[0] == 'a') {
fd = open(file, O_WRONLY | O_APPEND);
}
if (fd == -1) {
return NULL;
}
my_file_t *f = malloc(sizeof *f);
f->fd = fd;
f->next_byte = 0;
f->n_buffered_bytes = 0;
return f;
}
int my_fgetc(my_file_t *f) {
if (f->next_byte == f->n_buffered_bytes) {
// buffer is empty so fill it with a read
int bytes_read = read(f->fd, f->buffer, sizeof f->buffer);
if (bytes_read <= 0) {
return MY_EOF;
}
f->n_buffered_bytes = bytes_read;
f->next_byte = 0;
}
// return 1 byte from the buffer
int byte = f->buffer[f->next_byte];
f->next_byte++;
return byte;
}
int my_fputc(int c, my_file_t *f) {
uint8_t byte = c;
if (write(f->fd, &byte, 1) == 1) {
return byte;
} else {
return MY_EOF;
}
}
int my_fclose(my_file_t *f) {
int result = close(f->fd);
free(f);
return result;
}
int main(int argc, char *argv[]) {
my_file_t *input_stream = my_fopen(argv[1], "r");
if (input_stream == NULL) {
perror(argv[1]);
return 1;
}
my_file_t *output_stream = my_fopen(argv[2], "w");
if (output_stream == NULL) {
perror(argv[2]);
return 1;
}
int c;
while ((c = my_fgetc(input_stream)) != MY_EOF) {
my_fputc(c, output_stream);
}
my_fclose(input_stream);
my_fclose(output_stream);
return 0;
}
simple re-implementation of stdio functions fopen, fgetc,
fputc, fclose *zero* error handling for clarity
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
// how equivalents for EOF & BUFSIZ from stdio.h
#define MY_EOF -1
#define MY_BUFSIZ 512
// struct to hold data for a stream
typedef struct my_file {
int fd;
int is_output_stream;
int n_buffered_bytes;
int next_byte;
uint8_t buffer[MY_BUFSIZ];
} my_file_t;
my_file_t *my_fopen(char *file, char *mode) {
int fd = -1;
if (mode[0] == 'r') {
fd = open(file, O_RDONLY);
} else if (mode[0] == 'w') {
fd = open(file, O_WRONLY | O_CREAT, 0666);
} else if (mode[0] == 'a') {
fd = open(file, O_WRONLY | O_APPEND);
}
if (fd == -1) {
return NULL;
}
my_file_t *f = malloc(sizeof *f);
f->fd = fd;
f->is_output_stream = mode[0] != 'r';
f->next_byte = 0;
f->n_buffered_bytes = 0;
return f;
}
int my_fgetc(my_file_t *f) {
if (f->next_byte == f->n_buffered_bytes) {
// buffer is empty so fill it with a read
int bytes_read = read(f->fd, f->buffer, sizeof f->buffer);
if (bytes_read <= 0) {
return MY_EOF;
}
f->n_buffered_bytes = bytes_read;
f->next_byte = 0;
}
// return 1 byte from the buffer
int byte = f->buffer[f->next_byte];
f->next_byte++;
return byte;
}
int my_fputc(int c, my_file_t *f) {
if (f->n_buffered_bytes == sizeof f->buffer) {
// buffer is full so empty it with a write
write(f->fd, f->buffer, sizeof f->buffer); // no error checking
f->n_buffered_bytes = 0;
}
// add byte byte to buffer to be written later
f->buffer[f->n_buffered_bytes] = c;
f->n_buffered_bytes++;
return 1;
}
int my_fclose(my_file_t *f) {
// don't keave unwritten bytes
if (f->is_output_stream && f->n_buffered_bytes > 0) {
write(f->fd, f->buffer, f->n_buffered_bytes); // no error checking
}
int result = close(f->fd);
free(f);
return result;
}
int main(int argc, char *argv[]) {
my_file_t *input_stream = my_fopen(argv[1], "r");
if (input_stream == NULL) {
perror(argv[1]);
return 1;
}
my_file_t *output_stream = my_fopen(argv[2], "w");
if (output_stream == NULL) {
perror(argv[2]);
return 1;
}
int c;
while ((c = my_fgetc(input_stream)) != MY_EOF) {
my_fputc(c, output_stream);
}
my_fclose(input_stream);
my_fclose(output_stream);
return 0;
}
use lseek to access diferent bytes of a file with no error
checking
the return value of thecalls to open, lseek and read should be checked to see if they worked!
the return value of thecalls to open, lseek and read should be checked to see if they worked!
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <source file>\n", argv[0]);
return 1;
}
int read_file_descriptor = open(argv[1], O_RDONLY);
char bytes[1];
// move to a position 1 byte from end of file
// then read 1 byte
lseek(read_file_descriptor, -1, SEEK_END);
read(read_file_descriptor, bytes, 1);
printf("The last byte of the file is 0x%02x\n", bytes[0]);
// move to a position 0 bytes from start of file
// then read 1 byte
lseek(read_file_descriptor, 0, SEEK_SET);
read(read_file_descriptor, bytes, 1);
printf("The first byte of the file is 0x%02x\n", bytes[0]);
// move to a position 41 bytes from start of file
// then read 1 byte
lseek(read_file_descriptor, 41, SEEK_SET);
read(read_file_descriptor, bytes, 1);
printf("The 42nd byte of the file is 0x%02x\n", bytes[0]);
// move to a position 58 bytes from current position
// then read 1 byte
lseek(read_file_descriptor, 58, SEEK_CUR);
read(read_file_descriptor, bytes, 1);
printf("The 100th byte of the file is 0x%02x\n", bytes[0]);
return 0;
}
INTERNAL ERROR MISSING FILE: "code/files/fseek.cs"
INTERNAL ERROR MISSING FILE: "code/files/fseek.cs"
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) {
struct stat s;
printf("stat(\"%s\", &s)\n", pathname);
if (stat(pathname, &s) != 0) {
perror(pathname);
exit(1);
}
printf(" s.st_ino = %10ld # Inode number\n", s.st_ino);
printf(" s.st_mode = %10o # File mode \n", s.st_mode);
printf(" s.st_nlink = %10ld # Link count \n", (long)s.st_nlink);
printf(" s.st_uid = %10u # Owner uid\n", s.st_uid);
printf(" s.st_gid = %10u # Group gid\n", s.st_gid);
printf(" s.st_size = %10ld # File size (bytes)\n", (long)s.st_size);
printf(" s.st_mtime = %10ld # Modification time (seconds since 01/01/70)\n", (long)s.st_mtime);
}
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
int main(void) {
int fd = open("sparse_file.txt", O_WRONLY | O_CREAT, 0644);
write(fd, "Hello, Andrew!\n", 15);
lseek(fd, 16L * 1000 * 1000 * 1000 * 1000, SEEK_CUR);
write(fd, "Good Bye Andrew!\n", 17);
close(fd);
return 0;
}
use fseek to change arandom bit in a file
the return value of the calls to fopen, fseek and fgetc should be checked to see if they worked!
the return value of the calls to fopen, fseek and fgetc should be checked to see if they worked!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <source file>\n", argv[0]);
return 1;
}
// open file for reading and writing
FILE *f = fopen(argv[1], "r+");
// move to end of file
fseek(f, 0, SEEK_END);
long n_bytes_in_file = ftell(f);
// seed random number generator with current time
srandom(time(NULL));
// pick a random byte
long target_byte = random() % n_bytes_in_file;
// move to byte
fseek(f, target_byte, SEEK_SET);
// read byte
int byte = fgetc(f);
// pick a random bit
int bit = random() % 7;
// flip the bit
int new_byte = byte ^ (1 << bit);
// move back to write byte to same position
fseek(f, -1, SEEK_CUR);
// write the byte
fputc(new_byte, f);
fclose(f);
printf("Changed byte %ld of %s from %02x to %02x\n",target_byte, argv[1], byte, new_byte);
return 0;
}
useles suse of chdir() because it only affects 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;
}
use repeated chdir("..") to climb to the root of
the file system as a silly example of getcwd and chdir
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
int main(void) {
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;
}
list the contenst of directories specified as command-line
arguments
#include <stdio.h>
#include <dirent.h>
/*
$ dcc read_directory.c
$ ./a.out .
read_directory.c
a.out
.
..
$
*/
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;
}
create the directories specified as command-line arguments
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
/*
$ dcc create_directory.c
$ ./a.out new_dir
$ ls -ld new_dir
drwxr-xr-x 2 z5555555 z5555555 60 Oct 29 16:28 new_dir
$
*/
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;
}
rename the specified file
#include <stdio.h>
/*
$ dcc rename.c
$ ./a.out rename.c renamed.c
$ ls -l renamed.c
renamed.c
$
*/
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <old-filename> <new-filename>\n", argv[0]);
return 1;
}
if (rename(argv[1], argv[2]) != 0) {
fprintf(stderr, "%s rename <old-filename> <new-filename> failed:", argv[0]);
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(int argc, char *argv[]) {
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;
}
silly program which attempts to creates a long chain of
symbolic links
#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 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 symbolic link %s -> %s\n", new_pathname, pathname);
if (symlink(pathname, new_pathname) != 0) {
perror(pathname);
return 1;
}
strcpy(pathname, new_pathname);
}
return 0;
}
write bytes of array to file array.save
$ dcc write_array.c -o write_array $ dcc read_array.c -o read_array $ ./write_array $ ls -l array.save -rw-r--r-- 1 z5555555 z5555555 40 Oct 30 21:46 array.save $ ./read_array 10 11 12 13 14 15 16 17 18 19 $
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
int main(int argc, char *argv[]) {
int fd = open("array.save", O_WRONLY|O_CREAT, 0644);
if (fd < 0) {
perror("array.save");
return 1;
}
if (write(fd, array, sizeof array) != sizeof array) {
perror("array.save");
return 1;
}
close(fd);
return 0;
}
read bytes of array + pointer to file array_pointer.save
non-portable between platforms breaks if sizeof int
changes or endian-ness changes
Handling this safely is called serialization: https://en.wikipedia.org/wiki/Serialization
Handling this safely is called serialization: https://en.wikipedia.org/wiki/Serialization
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int array[10];
int main(int argc, char *argv[]) {
int fd = open("array.save", O_RDONLY, 0644);
if (fd < 0) {
perror("array.save");
return 1;
}
if (read(fd, array, sizeof array) != sizeof array) {
perror("array.save");
return 1;
}
close(fd);
// print array
for (int i = 0; i < 10; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
write bytes of array + pointer to file array_pointer.save
$ dcc write_pointer.c -o write_pointer $ dcc read_pointer.c -o read_pointer $ ./write_pointer p = 0x410234 &array[5] = 0x410234 array[5] = 15 *p = 15 $ ls -l array_pointer.save -rw-r--r-- 1 z5555555 z5555555 48 Oct 30 21:46 array.save $ ./read_pointer 10 11 12 13 14 15 16 17 18 19 p = 0x410234 &array[5] = 0x4163f4 array[5] = 15 *p = -1203175425 $
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
int *p = &array[5];
int main(int argc, char *argv[]) {
int fd = open(".save", O_WRONLY|O_CREAT, 0644);
if (fd < 0) {
perror("array_pointer.save");
return 1;
}
if (write(fd, array, sizeof array) != sizeof array) {
perror("array_pointer.save");
return 1;
}
if (write(fd, &p, sizeof p) != sizeof p) {
perror("array_pointer.save");
return 1;
}
close(fd);
printf("p = %p\n", p);
printf("&array[5] = %p\n", &array[5]);
printf("array[5] = %d\n", array[5]);
printf("*p = %d\n", *p);
return 0;
}
read bytes of array + pointer to file array_pointer.save
breaks even on same machine because address of array
different for every execution see
https://en.wikipedia.org/wiki/Address_space_layout_randomization
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int array[10];
int *p;
int main(int argc, char *argv[]) {
int fd = open("array_pointer.save", O_RDONLY, 0644);
if (fd < 0) {
perror("array_pointer.save");
return 1;
}
if (read(fd, array, sizeof array) != sizeof array) {
perror("array_pointer.save");
return 1;
}
if (read(fd, &p, sizeof p) != sizeof p) {
perror("array_pointer.save");
return 1;
}
close(fd);
// print array
for (int i = 0; i < 10; i++) {
printf("%d ", array[i]);
}
printf("\n");
printf("p = %p\n", p);
printf("&array[5] = %p\n", &array[5]);
printf("array[5] = %d\n", array[5]);
printf("*p = %d\n", *p);
return 0;
}
#include <stdio.h>
int main(void) {
printf("The unicode code point U+1F600 encodes in UTF-8 as 4 bytes: 0xF0 0x9F 0x98 0x80\n");
printf("We can output them like this: \xF0\x9F\x98\x80\n");
}
hello.c
index.txt
utf8_encode.c
#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("U+%x UTF-8: ", code_point);
for (uint8_t *s = encoding; *s != 0; s++) {
printf("0x%02x ", *s);
}
printf(" %s\n", encoding);
}
int main(void) {
print_utf8_encoding(0x42);
print_utf8_encoding(0x239);
print_utf8_encoding(0x10be);
print_utf8_encoding(0x1F600);
}
simple example of posix_spawn run date --utc to print
current UTC
#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
int main(void) {
pid_t pid;
extern char **environ;
char *date_argv[] = {"/bin/date", "--utc", NULL};
if (posix_spawn(&pid, "/bin/date", NULL, NULL, date_argv, environ) != 0) {
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;
}
simple example of program replacing itself with exec
#include <stdio.h>
#include <unistd.h>
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("");
return 1;
}
#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) {
// the fork failed, perror will print why
perror("fork");
} else if (pid == 0) {
printf("I know I am the child because fork() returned %d.\n", pid);
} else {
printf("I know 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 <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
int main(void) {
pid_t pid = fork();
if (pid == -1) {
// the fork failed, perror will print why
perror("fork");
} else if (pid == 0) {
// child
char *date_argv[] = {"/bin/date", "--utc", NULL};
execv("/bin/date", date_argv);
// execution will not reach here if exec is successful
perror("execvpe");
return 1;
} else {
// parent
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;
}
simple example of system run date --utc to print current
UTC
#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
int exit_status = system("/bin/date --utc");
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>
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;
if (posix_spawn(&pid, "/bin/ls", NULL, NULL, ls_argv, environ) != 0) {
perror("spawn");
exit(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;
}
simple example of catching a signal don't compile
with dcc
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void signal_handler(int signum) {
printf("signal number %d received\n", signum);
}
int main(void) {
struct sigaction action = {.sa_handler = signal_handler};
sigaction(SIGUSR1, &action, NULL);
printf("I am process %d waiting for signal %d\n", getpid(), SIGUSR1);
// loop waiting for signal
// bad consumes CPU/electricity/battery
// sleep would be better
while (1) {
}
}
simple example of catching a signal don't compile
with dcc
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void signal_handler(int signum) {
printf("signal number %d received\n", signum);
}
int main(void) {
struct sigaction action = {.sa_handler = signal_handler};
sigaction(SIGUSR1, &action, NULL);
printf("I am process %d waiting for signal %d\n", getpid(), SIGUSR1);
// suspend execution for 1 hour
sleep(3600);
}
simple example of sending a signal
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <signal> <pid>\n", argv[0]);
return 1;
}
int signal = atoi(argv[1]);
int pid = atoi(argv[2]);
kill(pid, signal);
}
simple example of catching a signal don't compile
with dcc
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(void) {
// catch SIGINT which is sent if user types cntrl-d
struct sigaction action = {.sa_handler = SIG_IGN};
sigaction(SIGINT, &action, NULL);
while (1) {
printf("Can't interrupt me, I'm ignoring ctrl-C\n");
sleep(1);
}
}
simple example of catching a signal don't compile
with dcc
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void ha_ha(int signum) {
printf("Ha Ha!\n");
}
int main(void) {
// catch SIGINT which is sent if user types cntrl-d
struct sigaction action = {.sa_handler = ha_ha};
sigaction(SIGINT, &action, NULL);
while (1) {
printf("Can't interrupt me, I'm ignoring ctrl-C\n");
sleep(1);
}
}
simple example of catching a signal don't compile
with dcc
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int signal_received = 0;
void stop(int signum) {
signal_received = 1;
}
int main(void) {
// catch SIGINT which is sent if user types cntrl-C
struct sigaction action = {.sa_handler = stop};
sigaction(SIGUSR1, &action, NULL);
while (!signal_received) {
printf("Type ctrl-c to stop me\n");
sleep(1);
}
printf("Good bye\n");
}
simple example of catching a signal don't compile
with dcc
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void report_signal(int signum) {
printf("Signal %d received\n", signum);
printf("Please send help\n");
exit(0);
}
int main(int argc, char *argv[]) {
struct sigaction action = {.sa_handler = report_signal};
sigaction(SIGFPE, &action, NULL);
// this will produce a divide by zero
// if there are no command-line arguments
// which will cause program to receive SIGFPE
printf("%d\n", 42/(argc - 1));
printf("Good bye\n");
}
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]);
// creae 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);
return 0;
}
simple example of use to popen to capture output
don't compile with dcc - it currently has a bug with
popen
#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);
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);
return 0;
}
simple example of use to popen to capture output
don't compile with dcc - it currently has a bug with
popen
#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
//
// 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\n");
pclose(p);
return 0;
}
print allenvirnoment variables
#include <stdio.h>
int main(void) {
extern char **environ;
for (int i = 0; environ[i] != NULL; i++) {
printf("%s\n", environ[i]);
}
}
simple example of accessing an environment variable
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *value = getenv("STATUS");
printf("Environment variable 'STATUS' has value '%s'\n", value);
return 0;
}
simple example of setting an environment variable
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/wait.h>
int main(void) {
setenv("STATUS", "great", 1);
char *getenv_argv[] = {"./getenv", NULL};
pid_t pid;
extern char **environ;
if (posix_spawn(&pid, "./getenv", NULL, NULL, getenv_argv, environ) != 0) {
perror("spawn");
exit(1);
}
return 0;
}
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>
int main(void) {
pid_t pid;
char *date_argv[] = {"/bin/date", NULL};
char *date_environment[] = {"TZ=Australia/Perth", NULL};
if (posix_spawn(&pid, "/bin/date", NULL, NULL, date_argv, date_environment) != 0) {
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;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glob.h>
int main(void) {
printf("Pattern? ");
char line[256];
if (fgets(line, sizeof line, stdin) == NULL) {
return 0;
}
char *newline = strrchr(line, '\n');
if (newline != NULL) {
*newline = '\0';
}
glob_t matches; // holds pattern expansion
int result = glob(line, GLOB_NOCHECK|GLOB_TILDE, NULL, &matches);
if (result != 0) {
printf("glob returns %d\n", result);
} else {
printf("%d matches\n", (int)matches.gl_pathc);
for (int i = 0; i < matches.gl_pathc; i++) {
printf("\t%s\n", matches.gl_pathv[i]);
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void test0(int x, int y, int a[x][y]) {
fprintf(stderr, "writing to array i-j order\n");
for (int i = 0; i < x; i++)
for (int j = 0; j < y; j++)
a[i][j] = i+j;
}
void test1(int x, int y, int a[x][y]) {
fprintf(stderr, "writing to array j-i order\n");
for (int j = 0; j < y; j++)
for (int i = 0; i < x; i++)
a[i][j] = i+j;
}
int main(int argc, char*argv[]) {
int x = atoi(argv[2]);
int y = atoi(argv[3]);
fprintf(stderr, "allocating a %dx%d array = %lld bytes\n", x, y, ((long long)x)*y*sizeof (int));
void *m = malloc(x*y*sizeof (int));
assert(m);
if (atoi(argv[1])) {
test1(x, y, m);
} else {
test0(x, y, m);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
int main(void) {
// create a IPv4 TCP/IP socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo hints = {
.ai_family = AF_INET, /* IPv4 only */
.ai_socktype = SOCK_STREAM, /* TCP */
};
// we wish to connect to port 1521 on our local machine
// first convert it to a struct addrinfo
struct addrinfo *a;
int s = getaddrinfo("localhost", "1521", &hints, &a);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
return 1;
}
// then try to establish a connection
if (connect(sock_fd, a->ai_addr, a->ai_addrlen) == -1) {
perror("connect");
exit(2);
}
char *message = "My name is Ingo Montoya";
printf("SENDING: %s\n", message);
write(sock_fd, message, strlen(message));
char response[1000];
int n_bytes = read(sock_fd, response, (sizeof response) - 1);
response[n_bytes] = '\0';
printf("RESPONSE WAS: %s\n", response);
close(sock_fd);
return 0;
}
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
int main(int argc, char **argv) {
// create a IPv4 TCP/IP socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
.ai_flags = AI_PASSIVE,
};
// we wish to wait for connections on port 1521 on our local machine
// first convert it to a struct addrinfo
struct addrinfo *a;
int s = getaddrinfo(NULL, "1521", &hints, &a);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(1);
}
// attach the address to the socket
if (bind(sock_fd, a->ai_addr, a->ai_addrlen) != 0) {
perror("bind()");
return 1;
}
// specify the maximum number of connections that can be queued for the socket
if (listen(sock_fd, 16) != 0) {
perror("listen()");
return 1;
}
printf("Waiting for connection\n");
int client_fd;
while ((client_fd = accept(sock_fd, NULL, NULL)) >= 0) {
// a real server might spawn a client process here to handle the connection
// so it can accept another connection immediately
printf("Connection made: client_fd=%d\n", client_fd);
char message[1024];
int n_bytes = read(client_fd, message, (sizeof message) - 1);
message[n_bytes] = '\0';
printf("READ %d BYTES: %s\n", n_bytes, message);
char response[1024];
snprintf(response, sizeof response, "%d bytes received \n", n_bytes);
printf("SENDING: %s\n", response);
write(client_fd, response, strlen(response));
close(client_fd);
}
close(sock_fd);
return 0;
}
A simple Web server
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
int main(int argc, char **argv) {
// create a IPv4 TCP/IP socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
.ai_flags = AI_PASSIVE,
};
// we wish to wait for connections on port 1521 on our local machine
// first convert it to a struct addrinfo
struct addrinfo *a;
int s = getaddrinfo(NULL, "1080", &hints, &a);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(1);
}
// attach the address to the socket
if (bind(sock_fd, a->ai_addr, a->ai_addrlen) != 0) {
perror("bind()");
return 1;
}
// specify the maximum number of connections that can be queued for the socket
if (listen(sock_fd, 16) != 0) {
perror("listen()");
return 1;
}
printf("Connect to me at http://localhost:1080/\n");
int client_fd;
while ((client_fd = accept(sock_fd, NULL, NULL)) >= 0) {
// a real server might spawn a client process here to handle the connection
// so it can accept another connection immediately
printf("Connection made: client_fd=%d\n", client_fd);
char message[1024];
int n_bytes = read(client_fd, message, (sizeof message) - 1);
message[n_bytes] = '\0';
printf("READ %d BYTES: %s\n", n_bytes, message);
printf("SENDING BACK ASCII ART\n");
char *header = "HTTP/1.1 200 OK\r\nContent-type: text/plain\r\n\r\n";
write(client_fd, header, strlen(header));
char *body = "\n |\\/\\/\\/| \n | | \n | | \n | (o)(o) \n C _) \n | ,___| \n | / \n /____\\ \n/ \\\n";
write(client_fd, body, strlen(body));
close(client_fd);
}
close(sock_fd);
return 0;
}
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 <stdio.h>
#include <pthread.h>
// this function is called to start thread execution
// it can be given any pointer as argument (int *) in this example
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 the function returns or thread_exit is called
// a pointer of any type can be returned
// this can be obtained via thread_join's 2nd argument
return NULL;
}
int main(void) {
//create two threads performing almost the same task
pthread_t thread_id1;
int thread_number1 = 1;
pthread_create(&thread_id1, NULL, run_thread, &thread_number1);
int thread_number2 = 2;
pthread_t thread_id2;
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 which launches two threads of execution but 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 <stdio.h>
#include <pthread.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 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 <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <assert.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(n_threads > 0 && 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 this:
$ 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 result is inexact because we use values can't be exactly represented as double and exact value print depends on how many threads we use - becayse we break up the computation differently depdning on number of threads
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <assert.h>
struct job {
long start;
long 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(n_threads > 0 && n_threads < 100);
long integers_to_sum = strtol(argv[2], NULL, 0);
assert(integers_to_sum > 0);
long integers_per_thread = (integers_to_sum - 1)/n_threads + 1;
printf("Creating %d threads to sum the first %lu integers\n", n_threads, integers_to_sum);
printf("Each thread will sum %lu integers\n", 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 each threads to finish
// then add its individual sum to the 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 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 <stdio.h>
#include <pthread.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);
bank_account = 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 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 <stdio.h>
#include <pthread.h>
int andrews_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
andrews_bank_account = andrews_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", andrews_bank_account);
return 0;
}
simple example demonstrating ensuring safe access to a global variable from threads using a mutex (mutual exclusion) lock
$ gcc -O3 -pthread bank_account_semphore.c -o bank_account_semphore $ ./bank_account_semphore
Andrew's bank account has $200000 $
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int andrews_bank_account = 0;
sem_t bank_account_semaphore;
// add $1 to Andrew's bank account 100,000 times
void *add_100000(void *argument) {
for (int i = 0; i < 100000; i++) {
// decrement bank_account_semaphore if > 0
// otherwise wait until > 0
sem_wait(&bank_account_semaphore);
// only one thread can execute this section of code at any time
// because bank_account_semaphore was initialized to 1
andrews_bank_account = andrews_bank_account + 1;
// increment bank_account_semaphore
sem_post(&bank_account_semaphore);
}
return NULL;
}
int main(void) {
// initialize bank_account_semaphore to 1
sem_init(&bank_account_semaphore, 0, 1);
//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", andrews_bank_account);
sem_destroy(&bank_account_semaphore);
return 0;
}
simple example which launches two threads of execution
which increment a global variable
#include <stdio.h>
#include <pthread.h>
int andrews_bank_account1 = 100;
pthread_mutex_t bank_account1_lock = PTHREAD_MUTEX_INITIALIZER;
int andrews_bank_account2 = 200;
pthread_mutex_t bank_account2_lock = PTHREAD_MUTEX_INITIALIZER;
// swap values between Andrew's two bank account 100,000 times
void *swap1(void *argument) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&bank_account1_lock);
pthread_mutex_lock(&bank_account2_lock);
int tmp = andrews_bank_account1;
andrews_bank_account1 = andrews_bank_account2;
andrews_bank_account2 = tmp;
pthread_mutex_unlock(&bank_account2_lock);
pthread_mutex_unlock(&bank_account1_lock);
}
return NULL;
}
// swap values between Andrew's two bank account 100,000 times
void *swap2(void *argument) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&bank_account2_lock);
pthread_mutex_lock(&bank_account1_lock);
int tmp = andrews_bank_account1;
andrews_bank_account1 = andrews_bank_account2;
andrews_bank_account2 = tmp;
pthread_mutex_unlock(&bank_account1_lock);
pthread_mutex_unlock(&bank_account2_lock);
}
return NULL;
}
int main(void) {
//create two threads performing almost the same task
pthread_t thread_id1;
pthread_create(&thread_id1, NULL, swap1, NULL);
pthread_t thread_id2;
pthread_create(&thread_id2, NULL, swap2, NULL);
// threads will probably never finish
// deadlock will likely likely core
// with one thread holding bank_account1_lock
// and waiting for bank_account2_lock
// and the other thread holding bank_account2_lock
// and waiting for bank_account1_lock
pthread_join(thread_id1, NULL);
pthread_join(thread_id2, NULL);
return 0;
}
All Links
- All Tutorial Questions
- All Tutorial Solutions
-
- All Laboratory Exercises
- All Laboratory Sample Solutions
-
- All Weekly Test Questions
- All Weekly Test Sample Answers
-
- Course Intro
- Integers
- Bitwise Operations
- Floating Point
- C Memory Model
- Mips Basics
- Mips Control
- Mips Data
- Mips Functions
- Files
- Unicode
- Processes
- Virtual Memory
- Networking
- Threads
- Exam
Print size and min and max values of integer types