Pedro Mendes
With great power, comes great responsibility
– Spider Man probably
Segmentation fault (core dumped)
typedef struct {
int* values;
size_t capacity;
size_t used;
} IntVec;
typedef struct {
int* values;
size_t capacity;
size_t used;
} IntVec;
IntVec int_vec_make(size_t initial_cap) {
return (IntVec) {.capacity = initial_cap, .used = 0,
.values = malloc(sizeof(int) * initial_cap)};
}
void int_vec_drop(IntVec a) { free(a.values); }
int main(void) {
// a owns the memory
IntVec a = int_vec_make(5);
// a's memory is dropped/freed
int_array_drop(a);
}
IntVec int_vec_make(size_t initial_cap) {
return (IntVec) {.capacity = initial_cap, .used = 0,
.values = malloc(sizeof(int) * initial_cap)};
}
void int_vec_drop(IntVec a) { free(a.values); }
int main(void) {
// a owns the memory
IntVec a = int_vec_make(5);
// oh no, now both a and b own the memory
IntVec b = a;
// a's memory is dropped/freed
int_vec_drop(a);
// use after free because there were two "owners"
printf("%d\n", b.values[1]);
}
IntVec int_vec_make(size_t initial_cap) {
return (IntVec) {.capacity = initial_cap, .used = 0,
.values = malloc(sizeof(int) * initial_cap)};
}
void int_vec_drop(IntVec a) { free(a.values); }
int main(void) {
// a owns the memory
IntVec a = int_vec_make(5);
// oh no, now both a and b own the memory
IntVec b = a;
// a's memory is dropped/freed
int_vec_drop(a);
// use after free because there were two "owners"
printf("%d\n", b.values[1]); // Undefined behaviour
}
Vec<i32>
Este tipo é equivalente ao IntVec de
fn main() {
// a owns the memory
let a: Vec<i32> = Vec::with_capacity(5);
// a's memory is dropped
drop(a);
}
fn main() {
// a owns the memory
let a: Vec<i32> = Vec::with_capacity(5);
// Move a --to--> b
let b = a;
// a's memory is dropped
drop(a);
// try to use b
println!("{}", b[1]);
}
fn main() {
// a owns the memory
let a: Vec<i32> = Vec::with_capacity(5);
// Move a --to--> b
let b = a;
// a's memory is dropped
drop(a);
// try to use b
println!("{}", b[1]);
}
fn main() {
// a owns the memory
let a: Vec<i32> = Vec::with_capacity(5);
// Move a --to--> b
let b = a;
// a doesn't own the memory anymore and is 'uninitialized'
drop(a);
// try to use b
println!("{}", b[1]);
}
Isto é um pouco restritivo
E se quisermos partilhar um objecto por varias
partes do codigo?
Pointers?
Mas assim introduzimos "use after free" bugs outra vez...
Algumas "construções" são consideradas UB pelo standard de C e o compilador tem a liberdade fazer o que quiser com o programa assim que encontra UB.
Por exemplo, em gcc version 10.2.0 compilar o programa anterior, com -O2, resulta no seguinte assembly para a função foo.
xor %eax,%eax
retq
<foo>:
xor %eax,%eax
retq
<foo>:
xor %eax,%eax
retq
<main>:
mov 0x0,%eax
ud2
? ud2 ?
Em qualquer ponto do programa...
typedef struct {
int* values;
size_t capacity;
size_t used;
} IntVec;
typedef struct {
int* values;
size_t capacity;
size_t used;
} IntVec;
void int_vec_push(IntVec* self, int value) {
// Caso o vector esteja cheio
if(self->capacity == self->used) {
// Duplicar a capacidade do vector
self->capacity = self->capacity ? self->capacity * 2 : 1;
// Realocar o array, usando agora o dobro da capacidade
self->values = realloc(self->values, sizeof(int) * self->capacity);
}
// Adicionar ao vector
self->values[self->used++] = value;
}
A linha mais importante deste código e a 13. Nesta linha podemos ver
que o pointer self->values
muda!
typedef struct { int* values; size_t capacity, used; } IntVec;
void int_vec_push(IntVec* self, int value);
int main(void) {
IntVec v = int_vec_make(0);
// Fazemos push de um valor
int_vec_push(&v, 1); // isto realoca porque capacity(0) == used(0)
// Criamos um pointer para o primeiro valor do vector
int* first_value_ptr = &v.values[0];
// Fazemos push de mais um valor
int_vec_push(&v, 2); // capacity(1) == used(1) então realocamos
// Tentamos alterar o primeiro valor do vector
*first_value_ptr = 42;
}
typedef struct { int* values; size_t capacity, used; } IntVec;
void int_vec_push(IntVec* self, int value);
int main(void) {
IntVec v = int_vec_make(0);
// Fazemos push de um valor
int_vec_push(&v, 1); // isto realoca porque capacity(0) == used(0)
// Criamos um pointer para o primeiro valor do vector
int* first_value_ptr = &v.values[0];
// Fazemos push de mais um valor
int_vec_push(&v, 2); // capacity(1) == used(1) então realocamos
// Tentamos alterar o primeiro valor do vector
*first_value_ptr = 42; // Undefined behaviour
}
fn main() {
let mut v = Vec::new();
// Fazemos push de um valor
v.push(1);
// Criamos um pointer para o primeiro valor do vector
let first_value_ptr = &mut v[0];
// Fazemos push de mais um valor
v.push(2);
// Tentamos alterar o primeiro valor do vector
*first_value_ptr = 42;
}
fn main() {
let mut v = Vec::new();
// Fazemos push de um valor
v.push(1);
// Criamos um pointer para o primeiro valor do vector
let first_value_ptr = &mut v[0];
// Fazemos push de mais um valor
v.push(2);
// Tentamos alterar o primeiro valor do vector
*first_value_ptr = 42;
}
fn main() {
let mut v = Vec::new();
// Fazemos push de um valor
v.push(1);
// Criamos um pointer para o primeiro valor do vector
let first_value_ptr = &mut v[0];
// Fazemos push de mais um valor
v.push(2);
// Tentamos alterar o primeiro valor do vector
*first_value_ptr = 42;
}
fn main() {
let mut v = Vec::new();
// Fazemos push de um valor
Vec::push(&mut v, 1); // equivalente a v.push(1);
// Criamos um pointer para o primeiro valor do vector
let first_value_ptr = &mut v[0];
// Fazemos push de mais um valor
Vec::push(&mut v, 2); // equivalente a v.push(2);
// Tentamos alterar o primeiro valor do vector
*first_value_ptr = 42;
}