I'm currently limited to coding in C and I want to do C object oriented programming.
One thing that comes to mind is how to correctly downcast a type in C without violating strict aliasing.
Imagine I have an animal struct with a vtable (meaning a struct of function pointers) and a dog like this:
typedef void (*sound_func)(const animal_t *animal);
struct animal_vtable {
sound_func sound;
};
typedef struct animal_vtable animal_vtable_t;
typedef struct animal {
animal_vtable_t * vtable;
int size;
} animal_t;
typedef struct dog {
animal_t animal;
} dog_t;
There will be cases when I want to know whether my animal is a dog, this is how I currently think of making an animal instance a dog, but I'm unsure if this will trigger undefined behavior or not.
dog_t *to_dog(animal_t *a) {
if (a->vtable != &dog_table) {
return NULL;
}
size_t offset = offsetof(dog_t, animal);
uintptr_t animal_offset = (uintptr_t) a;
return (dog_t *) (animal_offset - offset);
}
The key part here is that both the memory of dog_t *
and animal_t *
are on the same memory location for obvious reasons, but will this be a problem for optimizers? Currently I have -fno-strict-aliasing
enabled and thus I know it works, but is it safe to turn that off?
Below is the full working example which does not trigger errors when compiled with address and unefined behavior sanitizers.
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
* Animal section
*/
struct animal_vtable;
typedef struct animal_vtable animal_vtable_t;
typedef struct animal {
animal_vtable_t * vtable;
int size;
} animal_t;
typedef void (*sound_func)(const animal_t *animal);
struct animal_vtable {
sound_func sound;
};
void animal_sound(const animal_t* animal) {
animal->vtable->sound(animal);
}
int animal_size(const animal_t* animal) {
return animal->size;
}
/*
* dog section
*/
void dog_bark(const animal_t *animal);
static animal_vtable_t dog_table = {
.sound = dog_bark
};
typedef struct dog {
animal_t animal;
} dog_t;
dog_t* make_dog(int size) {
dog_t* dog = malloc(sizeof(dog_t));
if (dog == NULL) {
return dog;
}
dog->animal = (animal_t) { .vtable = &dog_table, .size = size };
return dog;
}
void dog_bark(const animal_t *animal) {
printf("wuff!\n");
}
dog_t *to_dog(animal_t *a) {
if (a->vtable != &dog_table) {
return NULL;
}
size_t offset = offsetof(dog_t, animal);
uintptr_t animal_offset = (uintptr_t) a;
return (dog_t *) animal_offset - offset;
}
/*
* main tests
*/
int main(int argc, char** argv) {
dog_t *dog = make_dog(10);
if (dog == NULL) {
exit(-1);
}
animal_t *animal = &(dog->animal);
animal_sound(animal);
dog_t *d2 = to_dog(animal);
printf("dog addr: %p, d2 addr: %p\n", dog, d2);
printf("dog size: %d\n", animal_size(&d2->animal));
printf("dog size: %d\n", animal_size(&dog->animal));
free(dog);
}