Neil
Neil

Reputation: 1922

Pointer Alignment when Implementing a container_of

I have been using my own container_of-type-function that's not dependant on GNU or C99, (viz, it works on different versions of MSVC.)

#include <stddef.h> /* offsetof */
#include <stdlib.h> /* EXIT_ */
#include <stdio.h>  /* printf */

/* Abstract Animal. */
struct AnimalVt;
struct Animal {
    const struct AnimalVt *vt;
    char name[16];
};

/* Sloth extends Animal. */
struct Sloth {
    struct Animal animal;
    unsigned hours_slept;
};
/* Modifyable (unused):
 static struct Sloth *sloth_holds_animal(struct Animal *const animal) {
    return (struct Sloth *)
        ((char *)animal - offsetof(struct Sloth, animal));
}*/
static const struct Sloth *
    sloth_holds_const_animal(const struct Animal *const animal) {
    return (const struct Sloth *)
        ((const char *)animal - offsetof(struct Sloth, animal));
}
static void sloth_print(const struct Animal *const animal) {
    const struct Sloth *const sloth = sloth_holds_const_animal(animal);
    printf("Sloth %s has been sleeping %u hours.\n",
        animal->name, sloth->hours_slept);
}

/* Emu extends Animal. */
struct Emu {
    struct Animal animal;
    char favourite_letter;
};
static const struct Emu *
    emu_holds_const_animal(const struct Animal *const animal) {
    return (const struct Emu *)(const void *)
        ((const char *)animal - offsetof(struct Emu, animal));
}
static void emu_print(const struct Animal *const animal) {
    const struct Emu *const emu = emu_holds_const_animal(animal);
    printf("Emu %s has \"%c\" as their favourite letter.\n",
        animal->name, emu->favourite_letter);
}

/* Virtual tables. */
typedef void (*AnimalAction)(const struct Animal *const);
static const struct AnimalVt {
    const AnimalAction print;
} sloth_vt = { &sloth_print }, emu_vt = { &emu_print };

static void print(const struct Animal *const animal) {
    animal->vt->print(animal);
}

int main(void) {
    const struct Sloth bob = { { &sloth_vt, "Bob" }, 10 };
    const struct Emu alice = { { &emu_vt, "Alice" }, 'z' };
    const struct Animal *a[] = { &alice.animal, &bob.animal };
    const size_t a_size = sizeof a / sizeof *a;
    size_t i;
    for(i = 0; i < a_size; i++) print(a[i]);
    return EXIT_SUCCESS;
}

Prints out,

Emu Alice has "z" as their favourite letter.
Sloth Bob has been sleeping 10 hours.

Emu has an intermediary cast to void *; I use this to make it forget information about alignment. Lately I've been wondering whether this is suspect; https://wiki.sei.cmu.edu/confluence/display/c/EXP36-C.+Do+not+cast+pointers+into+more+strictly+aligned+pointer+types:

The C Standard, 6.3.2.3, paragraph 7 [ISO/IEC 9899:2011], states,

A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.

Without it,

warning: cast from 'const char *' to 'const struct Sloth *' increases required alignment from 1 to 8 [-Wcast-align]

Which is totally reasonable. I have been using Emu-style code. It seems to be working. Should I be worried about alignment, or is this really pedantic? Under what situations will this fail? Could I use an assert to make sure that this doesn't happen? Is there a way to make the container_of-type-functions more robust?

Upvotes: 1

Views: 260

Answers (1)

Stephan Lechner
Stephan Lechner

Reputation: 35154

If your pointer originally points to a valid object, then you may cast it to void* and than back to the original type as often as you want (cf, for example, this online C11 standard draft):

6.3.2.3 Pointers

(1) A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

So your sequence of casts does not introduce undefined behaviour.

Upvotes: 1

Related Questions