Contents

Lecture 2 draft

Contents
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// Basic concepts: stack and heap (not data structure)

int fib(int n) {
    if (n < 1) return 0;
    if (n == 1 || n == 2) return 1;
    int sum = fib(n - 1) + fib(n - 2);
    return sum;
}

int f() {
    int *x = malloc(sizeof(int));
    int ret = fib(3);
    *x = ret;
    return 0;
}

// Extensible array: vector

struct vec {
    int *arr;
    size_t len;
    size_t cap;
};

struct vec new_vec(size_t init_cap) {
    assert(init_cap != 0);
    return (struct vec) {
        .arr = malloc(init_cap * sizeof(int)),
        .len = 0,
        .cap = init_cap,
    };
}

void vec_push(struct vec *vec, int value) {
    if (vec->len >= vec->cap) {
        vec->cap *= 2;
        vec->arr = realloc(vec->arr, vec->cap * sizeof(int));
    }
    vec->arr[vec->len++] = value;
}

void vec_destroy(struct vec *vec) {
    free(vec->arr);
}

void borrowed_for_whatever_reason(struct vec *vec) {
    puts("content of the vec:");
    for (size_t i = 0; i < vec->len; ++i)
        printf("%d ", vec->arr[i]);
    putchar('\n');
    fflush(stdout);
}

void consume_for_whatever_reason(struct vec vec) {
    borrowed_for_whatever_reason(&vec);
    vec_destroy(&vec);
}

int main() {
    struct vec vec = new_vec(10);

    int tmp;
    puts("input values:");
    while (scanf("%d", &tmp) == 1)
        vec_push(&vec, tmp);

    puts("content of the vec:");
    for (size_t i = 0; i < vec.len; ++i)
        printf("%d ", vec.arr[i]);
    putchar('\n');
    fflush(stdout);

    consume_for_whatever_reason(vec);

    // double free here
    vec_destroy(&vec);
    return 0;
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <vector>
#include <iostream>

int fib(int n) {
    if (n < 1) return 0;
    if (n == 1) return 1;
    if (n == 2) return 1;

    int sum = fib(n - 1) + fib(n - 2);

    return sum;
}

int f() {
    int ret = fib(3);

    int *x = (int*)malloc(sizeof(int));
    *x = ret;

    return 0;
}

class Vec {
public:
    int *arr;
    size_t len;
    size_t cap;

    Vec(size_t init_cap) {
        assert(init_cap != 0);
        arr = (int*)malloc(init_cap * sizeof(int));
        len = 0;
        cap = init_cap;
    }

    Vec(const Vec &other) {
        *this = other;
        printf("In copy constructor, other.arr %p, this->arr %p\n", other.arr, this->arr);
    }
    // Vec(Vec &&other) { ... }

    ~Vec() {
        printf("In destructor, this->arr %p\n", arr);
        free(arr);
    }

    Vec &operator=(const Vec &other) {
        free(this->arr);
        len = other.len;
        cap = other.cap;
        arr = (int*)memcpy(malloc(cap * sizeof(int)), other.arr, len * sizeof(int));
        printf("In copy assignment, other.arr %p, this->arr %p\n", other.arr, this->arr);
        return *this;
    }

    // rvalue reference: often used for move semantic
    Vec &operator=(Vec &&other) {
        free(this->arr);
        len = other.len;
        cap = other.cap;
        arr = other.arr;
        // no need to realloc
        printf("In move operator=, other.arr %p, this->arr %p\n", other.arr, this->arr);

        other.arr = nullptr;
        other.cap = 0;
        other.len = 0;

        return *this;
    }

    void push(int value) {
        if (len >= cap) {
            cap = cap ? cap * 2 : 1;
            // cap *= 2;
            arr = (int*)realloc(arr, cap * sizeof(int));
        }
        arr[len++] = value;
    }

};

void consume_for_whatever_reason(Vec &&vec) {
    printf("In consume_for_whatever_reason, vec.arr %p\n", vec.arr);

    puts("content of the vec:");
    for (size_t i = 0; i < vec.len; ++i)
        printf("%d ", vec.arr[i]);
    putchar('\n');
    fflush(stdout);
}

int main() {
    Vec vec(10);

    int tmp;
    puts("input values:");
    while (scanf("%d", &tmp) == 1)
        vec.push(tmp);

    puts("content of the vec:");
    for (size_t i = 0; i < vec.len; ++i)
        printf("%d ", vec.arr[i]);
    putchar('\n');
    fflush(stdout);

    printf("In main, vec.arr %p\n", vec.arr);

    // scenario: finding a good value in the for loop, and assign to a outer
    // variable
    //
    // Vec best_result;
    // for (...) {
    //      if (is_best) {
    //          best_result = std::move(current_result);
    //          break;
    //      }
    // }

    Vec vec1(5);

    vec1 = std::move(vec);

    // Vec vec2(vec);

    consume_for_whatever_reason(std::move(vec1));


    // Notes:
    // trial 1. consume_for_whatever_reason: double free
    //      - write a copy constructor
    // trial 2. Vec vec2 = vec1
    //      - ok, works
    // trial 3. vec2 = vec1: double free
    //      - write a copy assignment operator
    // trial 4. now we want to move value
    //      - try std::move, still copying
    //      - write a move operator=: double free
    //      - fix the move operator


    // automatically destroys vec
    // RAII: Resource Acquisition Is Initialization
    // i.e. Resource Release Is Destruction

    // Rule of 3
    // If you have to manage how an object is destroyed, you should also manage
    // how it’s copied
    // (technical version) If you have a non-trivial destructor, you should
    // also define a copy constructor and operator=


    // Try to duplcate the vector at its end with the value already in it.
    std::vector<int> stdv{100, 200};
    /* for (auto it = stdv.begin(); it != stdv.end(); ++it)
        stdv.push_back(*it); */
    for (size_t i = 0; i < stdv.size(); ++i)
        stdv.push_back(stdv[i]);
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::alloc::{alloc, dealloc, realloc, Layout};
use std::mem::size_of;

struct MyVec {
    arr: *mut u8,
    len: usize,
    cap: usize,
}

impl MyVec {
    fn new(init_size: usize) -> MyVec {
        MyVec {
            arr: unsafe { alloc(arr_layout(init_size)) },
            len: 0,
            cap: init_size,
        }
    }

    fn push(&mut self, value: i32) {
        unsafe {
            if self.len >= self.cap {
                self.arr = realloc(
                    self.arr,
                    arr_layout(self.cap),
                    self.cap * 2 * size_of::<i32>(),
                );
                self.cap *= 2;
            }

            *(self.arr as *mut i32).offset(self.len as isize) = value;
            self.len += 1;
        }
    }

    fn as_slice(&self) -> &[i32] {
        unsafe { std::slice::from_raw_parts(self.arr as *mut i32, self.len) }
    }
}

impl Clone for MyVec {
    fn clone(&self) -> MyVec {
        unsafe {
            let arr = alloc(arr_layout(self.cap));
            std::ptr::copy_nonoverlapping(self.arr, arr, self.len);
            MyVec { arr, ..*self }
        }
    }
}

fn arr_layout(cap: usize) -> Layout {
    Layout::from_size_align(cap * size_of::<i32>(), size_of::<i32>()).unwrap()
}

impl Drop for MyVec {
    fn drop(&mut self) {
        println!("hi drop");
        unsafe { dealloc(self.arr as *mut u8, arr_layout(self.cap)) };
    }
}

fn consume(v: MyVec) {
    println!("Consuming {:?}!", v.as_slice());
}

fn main() {
    // Ownership:
    // - Each value in Rust has an owner.
    // - There can only be one owner at a time.
    // - When the owner goes out of scope, the value will be dropped.

    let mut v = MyVec::new(10);

    v.push(100);
    v.push(200);

    // Semantic 1: types that does not have Copy trait have "move semantic":
    //      all the operator = , argument passing, and `return` are move operatons
    // Semantic 2: moved value cannot be accessed anymore
    // Semantic 3: moved variable can bind to a new value
    // Semantic 4: re-assigning to a variable will cause the old value to be dropped
    //
    // Semantic 5: types that have Copy trait have "copy semantic":
    //      all the operator = , argument passing, and `return` are copy
    //
    // Usage: clone must be explicit!

    let v2 = v.clone();

    consume(v);

    v = MyVec::new(4);

    // how drop is implemented
    // alternative 1
    fn mydrop(v: MyVec) {
        println!("In mydrop");
    }
    // alternative 2
    {
        let move_into_scope = v;
    }

    // mydrop(v);
    println!("After mydrop");

    // let v2 = v.clone();

    // dbg!(v.len);

    let t = 10;
    let t_cloned = 5.clone();
    let t_moved_actually_copied = t;

    #[derive(Clone, Copy)]
    struct MyInt {
        x: i32,
    }

    let x = MyInt { x: 2000 };
    let y = x;
    println!("x.x = {}, y.x = {}", x.x, y.x);

    // Copy: special marker trait
    // Clone: trait that provide method to duplicate something
    // Drop: non-trivial destructor
    //
    // Rule 1: Copy xor Drop
    // Rule 2: Copy requires Clone
    // Rule 3: Drop and Clone are orthogonal

    dbg!(t);
}

/*
// Ownership works not only for allocation, but also for any **resources**
struct File {
    fd: i32,
}

impl File {
    fn open(filename) -> File {
        File { fd: [>call C open(filename)<] }
    }
}

impl Drop for File {
    fn drop(&mut self) {
        // close(self.fd)
    }
} */