Search code examples
cpointersstructreferencedesignated-initializer

Why is address operator (&) needed when creating struct?


I have some code that works, but I don't understand why. Here is the code in question:

struct SceneInterface *TitleAsScene = &(struct SceneInterface) {
    .update = (void (*)(void *)) title_update,
    .render = (void (*)(void *)) title_render,
};

I get the function pointer and designated initializer stuff, but what is the &(struct SceneInterface) part doing? Normally it means address of, but the thing in parentheses is a type, not a variable, so what is it pointing to? If it's returning a pointer to struct SceneInterface, then the left-hand side is already that, so I don't understand why it's needed and why I get a segmentation fault if I remove it.

Here is the full working code for reference:

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

struct SceneInterface {
    void (*update)(void *instance);
    void (*render)(void *instance);
};

struct Scene {
    void *instance;
    const struct SceneInterface *interface;
};

struct Scene *scene_create(void *instance, struct SceneInterface *interface)
{
    struct Scene *scene = (struct Scene *) malloc(sizeof(struct Scene));
    scene->instance = instance;
    scene->interface = interface;

    return scene;
}

void scene_update(struct Scene *scene)
{
    // return this if function has a return type
    (scene->interface->update)(scene->instance);
}

void scene_render(struct Scene *scene)
{
    (scene->interface->render)(scene->instance);
}

struct Boot {
    double x;
};

struct Boot *boot_create(double sideLength)
{
    struct Boot *boot = (struct Boot *) malloc(sizeof(struct Boot));
    boot->x = sideLength;

    return boot;
}

void boot_update(struct Boot *boot)
{
    printf("boot update\n");
}

void boot_render(struct Boot *boot)
{
    printf("boot render\n");
}

struct Title {
    double radius;
};

struct Title *title_create(double radius)
{
    struct Title *title = (struct Title *) malloc(sizeof(struct Title));
    title->radius = radius;

    return title;
}

void title_update(struct Title *title)
{
    printf("title update\n");
}

void title_render(struct Title *title)
{
    printf("title render\n");
}

int main(int argc, char **argv)
{
    struct Boot *boot = boot_create(10.0);
    struct Title *title = title_create(5.0);

    struct SceneInterface *BootAsScene = &(struct SceneInterface) {
        .update = (void (*)(void *)) boot_update,
        .render = (void (*)(void *)) boot_render,
    };
    struct SceneInterface *TitleAsScene = &(struct SceneInterface) {
        .update = (void (*)(void *)) title_update,
        .render = (void (*)(void *)) title_render,
    };

    struct Scene *bootScene = scene_create(boot, BootAsScene);
    struct Scene *titleScene = scene_create(title, TitleAsScene);

    boot_update(boot);
    scene_update(bootScene);

    boot_render(boot);
    scene_render(bootScene);

    title_update(title);
    scene_update(titleScene);

    title_render(title);
    scene_render(titleScene);

    return 0;
}

Solution

  • The syntax you're referring to, with what appears to be a cast followed by an initializer list, is a compound literal. It creates a temporary object of the given type. The line in question is then taking the address of that temporary object an placing it in a pointer.

    There's not much point however to using a compound literal here. The code could just as easily be written like this:

    struct SceneInterface BootAsScene = {
        .update = (void (*)(void *)) boot_update,
        .render = (void (*)(void *)) boot_render,
    };
    struct SceneInterface TitleAsScene = {
        .update = (void (*)(void *)) title_update,
        .render = (void (*)(void *)) title_render,
    };
    
    struct Scene *bootScene = scene_create(boot, &BootAsScene);
    struct Scene *titleScene = scene_create(title, &TitleAsScene);
    

    There's another problem with this code however. Function pointers are being casted to a different type and subsequently called via that casted type. Calling a function through an incompatible function pointer type triggers undefined behavior.

    The functions should be changed to accept a void * argument and cast the argument to the appropriate type inside of the function.