Computer Systems Fundamentals

Course Resources

Administrivia
Resources
Platforms
MIPS Resources
SPIM Resources
Lab/Test/Assignment
Other

Week-by-Week

Topic-by-Topic

Course Intro
Integers


Print size and min and max values of integer types
#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;
}

Download integer_types.c


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

int main(void) {

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

    return 0;
}

Download stdint.c


#include <stdio.h>

int main(void) {
    // common C bug
    //
    // char 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;
}

Download char_bug.c


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

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

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

int main(void) {

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

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

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

    return 0;
}

Download print_bits_of_int.c

Bitwise Operations


Demonstrate C bitwise operations of 16 bit values

$ dcc bitwise.c print_bits.c -o bitwise $ ./bitwise
Enter a: 23032
Enter b: 12345
Enter c: 3 a = 0101100111111000 = 0x59f8 = 23032 b = 0011000000111001 = 0x3039 = 12345 ~a = 1010011000000111 = 0xa607 = 42503 a & b = 0001000000111000 = 0x1038 = 4152 a | b = 0111100111111001 = 0x79f9 = 31225 a ^ b = 0110100111000001 = 0x69c1 = 27073 a >> c = 0000101100111111 = 0x0b3f = 2879 a << c = 1100111111000000 = 0xcfc0 = 53184

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

void print_bits_hex(char *description, uint16_t n);

int main(void) {

    uint16_t a = 0;
    printf("Enter a: ");
    scanf("%hd", &a);

    uint16_t 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 print
// binary, hexadecimal and decimal representation of a value
void print_bits_hex(char *description, uint16_t value) {
    printf("%s", description);
    print_bits(value, 8 * sizeof value);
    printf(" = 0x%04x = %d\n", value & 0xFFFF, value);
}

Download bitwise.c



Represent a small set of possible values using bits
#include <stdio.h>

#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
    }

    return 0;
}

Download pokemon.c



Examples of illegal bit-shift operations
#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 - constant 1 is an int
    j = ((uint64_t)1) << 33; // ok

    return 0;
}

Download shift_bug.c



copy stdin to stdout xor'ing each byte with value given as argument



$ echo Hello Andrew|xor 42 bOFFE kDNXO] $ echo Hello Andrew|xor 42|cat -A bOFFE$ kDNXO] $ $ echo Hello |xor 42 bOFFE $ echo -n 'bOFFE '|xor 42
Hello $ echo Hello|xor 123|xor 123
Hello $

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

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

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

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

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

        int xor_c = c ^ xor_value;

        putchar(xor_c);
    }

    return 0;
}

Download xor.c


Represent set of small non-negative integers using bit-operations
$ dcc bitset.c print_bits.c -o bitset
$ ./bitset

Set members can be 0-63, negative number to finish
Enter set a: 1 2 4 8 16 32 -1
Enter set b: 5 4 3 33 -1 a = 0000000000000000000000000000000100000000000000010000000100010110 = 0x100010116 = 4295033110 b = 0000000000000000000000000000001000000000000000000000000000111000 = 0x200000038 = 8589934648 a = {1,2,4,8,16,32} b = {3,4,5,33} a union b = {1,2,3,4,5,8,16,32,33} a intersection b = {4} cardinality(a) = 6 is_member(42, a) = 0

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include "print_bits.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);

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, 8 * sizeof value);
    printf(" = 0x%08lx = %ld\n", value, value);
}

Download bitset.c

Floating Point


Print size and min and max values of floating point types
#include <stdio.h>
#include <float.h>

int main(void) {

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

    return 0;
}

Download floating_types.c



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

int main(void) {
    double a, b;

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

    if (b != 0) {
        printf("1 != 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1\n");
    }

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

    return 0;
}

Download double_imprecision.c



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

int main(void) {

    double x = 0.000000011;

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

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

    return 0;
}

Download double_catastrophe.c


9007199254740993 is smallest integer which can not be represented exactly as a double

The closest double to 9007199254740993 is 9007199254740992.0
As a result loop never terminates
9007199254740992 is 2 to the power of 53
It can not be represented by a int32_t,
It can be represented by int64_t
#include <stdio.h>

int main(void) {

    double d = 9007199254740992;

    while (d < 9007199254740999) {
        printf("%lf\n", d); // prints 9007199254740992.000000
        d = d + 1;
    }

    return 0;
}

Download double_disaster.c

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

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

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

    if (d == d) {
        printf("This should be always executed\n");
    } else {
        // will be executed if d is a NaN
        printf("This should never executed\n");
    }

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

    return 0;
}

Download double_not_always.c



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

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

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

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

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

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

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

#define EXPONENT_OFFSET   127
#define EXPONENT_INF_NAN  255

void display_float(char *argument) {
    uint32_t bits;

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

    print_float_details(bits);
}

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

    int sign_char, sign_value;

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

    int exponent = exponent_bits - EXPONENT_OFFSET;

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

    int implicit_bit = 1;

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

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

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

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

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

    fraction *= sign_value;

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

union overlay_float {
    float f;
    uint32_t u;
};

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

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

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

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

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

Download explain_floating_point_representation.c

Mips Basics
#include <stdio.h>

int main(void) {
    printf("I love MIPS\n");
    return 0;
}

Download i_love_mips.c

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"

Download i_love_mips.s

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

Download add.c

#include <stdio.h>

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

Download add.simple.c

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

Download add.s

Mips Control
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;
}

Download odd_even.c

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

Download odd_even.simple.c

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"

Download odd_even.s

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

Download print10.c

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

Download print10.simple.c

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

Download print10.s

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

Download sum_100_squares.c

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

Download sum_100_squares.simple.c

calculate 1*1 + 2*2 + ... + 99 * 99 + 100 * 100
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

Download sum_100_squares.s

Mips Data
#include <stdio.h>

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

Download add_memory.c

add 17 and 25 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

Download add_memory.s

#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]);
}

Download array_element_address.c

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
}

Download emulating_array_indexing.c

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

int x[10];

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

Download store_array_element.c

main:
    li   $t0, 3
    mul  $t0, $t0, 4
    la   $t1, x
    add  $t2, $t1, $t0
    li   $t3, 17
    sw   $t3, ($t2)
    # ...
.data
x:  .space 40

Download store_array_element.s

print 5 numbers
#include <stdio.h>

int numbers[5] = { 3, 9, 27, 81, 243};

int main(void) {
    int i = 0;
    while (i < 5) {
        printf("%d\n", numbers[i]);
        i++;
    }
    return 0;
}

Download print5.c

print 5 numbers i in $s0 j in $s1
main:
    li   $s0, 0          # int i = 0;
loop:
    bge  $s0, 5, end     # if (i >= 5) goto end;
    la   $t0, numbers    #    int j = numbers[i];
    mul  $t1, $s0, 4
    add  $t2, $t1, $t0
    lw   $s1, ($t2)
    move $a0, $s1        # printf("%d", j);
    li   $v0, 1
    syscall
    li   $a0, '\n'       #   printf("%c", '\n');
    li   $v0, 11
    syscall

    add  $s0, $s0, 1     #   i++
    b loop               # goto loop
end:

    li   $v0, 0          # return 0
    jr   $ra

.data

numbers:                 # int numbers[10] = { 3, 9, 27, 81, 243};
     .word 3, 9, 27, 81, 243

Download print5.s

print 5 numbers
#include <stdio.h>

int numbers[5] = { 3, 9, 27, 81, 243};

int main(void) {
    int *p = &numbers[0];
    int *q = &numbers[4];
    while (p <= q) {
        printf("%d\n", *p);
        p++;
    }
    return 0;
}

Download pointer5.c

print 5 numbers
#include <stdio.h>

int numbers[5] = { 3, 9, 27, 81, 243};

int main(void) {
    int *p = &numbers[0];
    int *q = &numbers[4];
loop:
    if (p > q) goto end;
        int j = *p;
        printf("%d", j);
        printf("%c", '\n');
        p++;
    goto loop;
end:
    return 0;
}

Download pointer5.simple.c

print 5 numbers p in $s0 q in $s1 j in $s2
main:
    la   $s0, numbers    # int *p = &numbers[0];
    la   $t0, numbers    # int *q = &numbers[4];
    add  $s1, $t0, 16    #
loop:
    bgt  $s0, $s1, end   # if (p > q) goto end;
    lw   $s2, ($s0)      # int j = *p;
    move $a0, $s2        # printf("%d", j);
    li   $v0, 1
    syscall
    li   $a0, '\n'       #   printf("%c", '\n');
    li   $v0, 11
    syscall

    add  $s0, $s0, 4     #   p++
    b loop               # goto loop
end:

    li   $v0, 0          # return 0
    jr   $ra

.data

numbers:                 # int numbers[10] = { 3, 9, 27, 81, 243};
     .word 3, 9, 27, 81, 243

Download pointer5.s

print 5 numbers - this is closer to the code a compiler might produce p in $s0 q in $s1
main:
    la   $s0, numbers    # int *p = &numbers[0];
    add  $s1, $s0, 16    # int *q = &numbers[4];
loop:
    lw   $a0, ($s0)      # printf("%d", *p);
    li   $v0, 1
    syscall
    li   $a0, '\n'       #   printf("%c", '\n');
    li   $v0, 11
    syscall
    add  $s0, $s0, 4     #   p++
    ble  $s0, $s1, loop  # if (p <= q) goto loop;

    li   $v0, 0          # return 0
    jr   $ra

.data

numbers:                 # int numbers[10] = { 3, 9, 27, 81, 243};
     .word 3, 9, 27, 81, 243

Download pointer5.faster.s

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

Download read10.c

read 10 numbers into an array then print the 10 numbers
i in register $s0 registers $t1, $t2 & $t3 used to hold temporary results
main:

    li $s0, 0           # i = 0
loop0:
    bge $s0, 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, $s0, 4     #   calculate &numbers[i]
    la $t2, numbers     #
    add $t3, $t1, $t2   #
    sw $v0, ($t3)       #   store entered number in array

    add $s0, $s0, 1     #   i++;
    b loop0             # }
end0:

    li   $s0, 0          # i = 0
loop1:
    bge  $s0, 10, end1   # while (i < 10) {

    mul  $t1, $s0, 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  $s0, $s0, 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: "

Download read10.s

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

Download reverse10.c

read 10 integers then print them in reverse order
count in register $s0 registers $t1 and $t2 used to hold temporary results
main:
    li   $s0, 0           # count = 0

read:
    bge  $s0, 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, $s0, 4      #   calculate &numbers[count]
    la   $t2, numbers     #
    add  $t1, $t1, $t2    #
    sw   $v0, ($t1)       #   store entered number in array

    add  $s0, $s0, 1      #   count++;
    b read                # }

print:
    la   $a0, string1     # printf("Reverse order:\n");
    li   $v0, 4
    syscall
    li   $s0, 9           # count = 9;
next:
    blt  $s0, 0, end1     # while (count >= 0) {

    mul  $t1, $s0, 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  $s0, $s0,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"

Download reverse10.s

#include <stdio.h>

int
main() {
    int i;
    int numbers[10];

    i = 0;
    while (i < 10) {
        printf("Enter a number: ");
        scanf("%d", &numbers[i]);
        i++;
    }
    i = 0;
    while (i < 10) {
        numbers[i] *= 42;
        i++;
    }
    i = 0;
    while (i < 10) {
        printf("%d\n", numbers[i]);
        i++;
    }
    return 0;
}

Download scale10.c

i in register $s0 registers $s1 and $s2 used to hold temporary results
main:
    li $s0, 0           # i = 0

loop0:
    bge $s0, 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 $s1, $s0, 4     # calculate &numbers[i]
    la $s2, numbers     #
    add $s1, $s1, $s2   #
    sw $v0, ($s1)       # store entered number in array

    add $s0, $s0, 1     # i++;
    b loop0
end0:
    li $s0, 0           # i = 0

loop1:
    bge $s0, 10, done   # while (i < 10) {

    mul $s1, $s0, 4     # printf("%d", numbers[i])
    la $s2, numbers     # calculate &numbers[i]
    add $s1, $s1, $s2   #
    lw $a0, ($s1)       # load numbers[i] into $a0
    li $v0, 1
    syscall

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

    add $s0, $s0, 1     # i++
    b loop1

done:
    jr $31

.data

numbers:
    .space 40           # int numbers[10];

string0:
    .asciiz "Enter a number: "
string1:
    .asciiz "Reverse order:\n"

Download scale10.s

#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
}

Download endian.c

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

Download endian.s

#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]);
}

Download unalign.c

main:
    li $t0, 1

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

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

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

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

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

    sw $t0, v6            # will succeeed because v6 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 4
    .space 1
v1:
    .space 1
v2:
    .space 4
v3:
    .space 2
v4:
    .space 4
    .space 1
    .align 2 # ensure e is on a 4 (2**2) byte boundary
v5:
    .space 4
    .space 1
v6:
    .word 0  # word directive automaticatically aligns on 4 byte boundary

Download unalign.s

#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]);
        }
    }
}

Download 2d_array_element_address.c

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
}

Download emulating_2d_array_indexing.c

print a 2d array
#include <stdio.h>

int numbers[3][5] = {{3,9,27,81,243},{4,16,64,256,1024},{5,25,125,625,3125}};

int main(void) {
    int i = 0;
    while (i < 3) {
        int j = 0;
        while (j < 5) {
            printf("%d", numbers[i][j]);
            printf("%c", ' ');
            j++;
        }
        printf("%c", '\n');
        i++;
    }
    return 0;
}

Download print2d.c

print a 2d array
#include <stdio.h>

int numbers[3][5] = {{3,9,27,81,243},{4,16,64,256,1024},{5,25,125,625,3125}};

int main(void) {
    int i = 0;
loop1:
    if (i >= 3) goto end1;
        int j = 0;
    loop2:
        if (j >= 5) goto end2;
            printf("%d", numbers[i][j]);
            printf("%c", ' ');
            j++;
        goto loop2;
    end2:
        printf("%c", '\n');
        i++;
    goto loop1;
end1:
    return 0;
}

Download print2d.simple.c

print a 2d array i in $s0 j in $s1
main:
    li   $s0, 0          # int i = 0;
loop1:
    bge  $s0, 3, end1    # if (i >= 3) goto end1;
    li   $s1, 0          #    int j = 0;
loop2:
    bge  $s1, 5, end2    #    if (j >= 5) goto end2;
    la   $t0, numbers    #        printf("%d", numbers[i][j]);
    mul  $t1, $s0, 20
    add  $t2, $t1, $t0
    mul  $t3, $s1, 4
    add  $t4, $t3, $t2
    lw   $a0, ($t4)
    li   $v0, 1
    syscall
    li   $a0, ' '       #       printf("%c", ' ');
    li   $v0, 11
    syscall
    add  $s1, $s1, 1     #       j++;
    b loop2              #    goto loop2;
end2:
    li   $a0, '\n'       #    printf("%c", '\n');
    li   $v0, 11
    syscall

    add  $s0, $s0, 1      #   i++
    b loop1               # goto loop1
end1:

    li   $v0, 0          # return 0
    jr   $ra

.data
# int numbers[3][5] = {{3,9,27,81,243},{4,16,64,256,1024},{5,25,125,625,3125}};
numbers:
     .word  3, 9, 27, 81, 243, 4, 16, 64, 256, 1024, 5, 25, 125, 625, 3125

Download print2d.s

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

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

struct s2 {
    uint8_t    b;
    uint64_t   l;
};

int main(void) {
    struct s1 v1;

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

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

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

Download struct_address.c

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


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

void print_bytes(void *v, int n);

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

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

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

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

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

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

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

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

Download struct_packing.c

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
}

Download emulating_struct_addressing.c

Mips Functions

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

void f(void);

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

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

Download call_return.c

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

    jal f               # set $ra to following address

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

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


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


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

Download call_return.broken.s

simple example of placing return address on stack note stack grows down
main:
    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"

Download call_return.s

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

int answer(void);

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

int answer(void) {
    return 42;
}

Download return_answer.c

simple example of returning a value from a function note storing of return address $ra 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

Download return_answer.s

example of function calls
#include <stdio.h>

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

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

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

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

Download more_calls.c

example of function calls note storing of return address $a0, $a1 and $ra on stack 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, 10         # sum_product(10, 12);
    li   $a1, 12
    jal  sum_product

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

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

    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            #

Download more_calls.s

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

void two(int i);

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

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

Download two_powerful.c

simple example of placing return address $ra and $a0 on 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

Download two_powerful.s

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

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

Download frame_pointer.c

example stack growing during function execution breaking the function return
f:
    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

Download frame_pointer.broken.s

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
    lw   $fp, -4($fp)   # restore $fp from $stack
    jr   $ra            # return

Download frame_pointer.s

demonstrate implementaion of pointers by an address
#include <stdio.h>

int answer = 42;

int main(void) {
    int i;
    int *p;

    p = &answer;
    i = *p;
    printf("%d\n", i); // prints 42
    *p = 27;
    printf("%d\n", answer); // prints 27

    return 0;
}

Download pointer.c

demonstrate implementation of pointers by an address p in register $t0 i in register $t1 $t2 used for temporary value
main:
    la   $t0, answer    # p = &answer;

    lw   $t1, ($t0)     # i = *p;

    move $a0, $t1       # printf("%d\n", i);
    li   $v0, 1
    syscall

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

    li   $t2, 27        # *p = 27;
    sw   $t2, ($t0)     #

    lw   $a0, answer    # printf("%d\n", answer);
    li   $v0, 1
    syscall

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

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

    .data
answer:
    .word 42            # int answer = 42;

Download pointer.s

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

Download strlen_array.c

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

Download strlen_array.goto.c

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"

Download strlen_array.s

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

Download strlen_pointer.c

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"

Download strlen_pointer.s

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

Download stack_inspect.c



Run at CSE like this
$ clang invalid0.c -o invalid0 $ ./invalid0 42 77 77 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;
}

Download invalid0.c



Run at CSE like this
$ clang invalid1.c -o invalid1 $ ./invalid1 i is at address 0x7ffe2c01cd58 a[0] is at address 0x7ffe2c01cd30 a[9] is at address 0x7ffe2c01cd54 a[10] would be stored at address 0x7ffe2c01cd58
doesn't terminate

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

Download invalid1.c



Run at CSE like this
$ clang -Wno-everything 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[18] doesn't exist
    // with clang at CSE variable answer in main
    // happens to be where a[19] would be

    printf("a[18] would be stored at address %p\n", &a[18]);

    a[18] = 42;
}

Download invalid2.c



Run at CSE like this
$ clang invalid3.c -o invalid3 $ ./invalid3
I will never be printed. argc was 1 $
#include <stdio.h>
#include <stdlib.h>

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 clang at CSE  f's return address is stored where a[12] would be
    //
    // so changing a[12] changes where the function returns
    //
    // adding 12 to a[12] happens to cause it to return several statements later
    // at the printf("I will never be printed.\n");

    a[12] += 12;
}

Download invalid3.c



Run at CSE like this
$ clang 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;
}

Download invalid4.c

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

Download hello_syscalls.c

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

Download cat_syscalls.c

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

Download cp_syscalls.c

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

Download hello_libc.c

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

Download cat_libc.c

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

int main(int argc, char *argv[]) {
    // 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;
}

Download cp_libc.c

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

Download hello_fputc.c

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

Download hello_fputs.c

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

Download hello_fwrite.c

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

Download cat_fgetc.c

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

Download cat_fgets.c

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

Download cat_fwrite.c

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

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

    FILE *input_stream = fopen(argv[1], "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;
}

Download cp_fgetc.c

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

Download cp_fwrite.c


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

Download create_file_fopen.c

$ 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");
}

Download create_append_truncate_fopen.c

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

Download myio_unbuffered.c

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

Download myio_input_buffered.c

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

Download myio_output_buffered.c

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!
#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;
}

Download lseek.c

use fseek to access diferent bytes of a file with no error checking
the return value of the calls to fopen, fseek and fgetc should be checked to see if they worked!
#include <stdio.h>

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

    FILE *input_stream = fopen(argv[1], "rb");

    // move to a position 1 byte from end of file
    // then read 1 byte
    fseek(input_stream, -1, SEEK_END);
    printf("The last byte of the file is 0x%02x\n", fgetc(input_stream));

    // move to a position 0 bytes from start of file
    // then read 1 byte
    fseek(input_stream, 0, SEEK_SET);
    printf("The first byte of the file is 0x%02x\n", fgetc(input_stream));

    // move to a position 41 bytes from start of file
    // then read 1 byte
    fseek(input_stream, 41, SEEK_SET);
    printf("The 42nd byte of the file is 0x%02x\n", fgetc(input_stream));

    // move to a position 58 bytes from current position
    // then read 1 byte
    fseek(input_stream, 58, SEEK_CUR);
    printf("The 100th byte of the file is 0x%02x\n", fgetc(input_stream));

    return 0;
}

Download fseek.c

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

Download stat.c

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

Download create_gigantic_file.c

use fseek to change a random bit in a file 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;
}

Download fuzz.c

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

Download my_cd.c

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

Download getcwd.c

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

Download read_directory.c

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

Download create_directory.c

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

Download rename.c

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

Download nest_directories.c

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

Download write_array.c

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

Download read_array.c

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

Download write_pointer.c

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

Download read_pointer.c

Virtual Memory
Unicode
#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 the 4 bytes like this: \xF0\x9F\x98\x80\n");
    printf("Or like this: ");
    putchar(0xF0);
    putchar(0x9F);
    putchar(0x98);
    putchar(0x80);
    putchar('\n');
}

Download hello.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(0x00A2);
    print_utf8_encoding(0x10be);
    print_utf8_encoding(0x1F600);
}

Download utf8_encode.c

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

Download spawn.c

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

Download exec.c

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

Download fork.c

simple example of classic fork/exec run date --utc to print current UTC
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;
}

Download fork_exec.c

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

Download system.c

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

Download lsld_spawn.c

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

Download lsld_system.c

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]);
    }
}

Download environ.c

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

Download getenv.c

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

Download setenv.c

simple example of using environment variableto change program behaviour run date -to print time
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;
}

Download spawn_environment.c

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) {
    }
}

Download busy_wait_for_signal.c

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

Download wait_for_signal.c

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

Download send_signal.c

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

Download ignore_control_c.c

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

Download laugh_at_control_c.c

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");
}

Download stop_with_control_c.c

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");
}

Download catch_error.c

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

Download spawn_read_pipe.c

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

Download read_popen.c

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

Download spawn_write_pipe.c

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

Download write_popen.c

Threads

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

Download two_threads.c


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

Download two_threads_broken.c


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

Download n_threads.c


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
$
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 printed depends on how many threads we use - becayse we break up the computation differently depending 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;
}

Download thread_sum.c


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

Download bank_account_broken.c


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

Download bank_account_mutex.c


simple example demonstrating ensuring safe access to a global variable from threads using a semaphore
$ 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;
}

Download bank_account_semphore.c

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 occur
    // 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;
}

Download bank_account_deadlock.c

Exam