Home > database >  aliasing issue with OO inheritance in C
aliasing issue with OO inheritance in C

Time:10-27

I'm doing inheritance ( i.e. calling super class's function with subclass data type) with C, but encountered the aliasing issue.

In below, shape function is called with rectangle object.

In the case of me->super, the x and y are correct. However, they're wrong in that of (Shape*)me.

The reason I prefer (Shape*)me over me->super is that I want to hide struct implementation from clients.

Shouldn't C standard guarantee that? As per Section 6.7.2.1.13

“… A pointer to a structure object, suitably converted, points to its initial member. There may be unnamed padding within a structure object, but not at its beginning”.

/*shape.h*/
#ifndef SHAPE_H
#define SHAPE_H

typedef struct Shape Shape;

Shape* Shape_ctor(int x, int y);
int Shape_getX(Shape* me);
int Shape_getY(Shape* me);

#endif

/*shape.c*/
#include <stdlib.h>
#include "shape.h"

struct Shape
{
    int x;
    int y;
};

Shape* Shape_ctor(int x, int y)
{
    Shape* me = malloc(sizeof(struct Shape));
    me->x = x;
    me->y = y;

    return me;
}

/*rectangle.c*/
#include <stdlib.h>
#include "rectangle.h"
#include "stdio.h"

struct Rectangle
{
    Shape* super;
    unsigned int width;
    unsigned int height;
};

Rectangle* Rectangle_ctor(int x, int y, unsigned int width, unsigned int height)
{
    Rectangle* me = malloc(sizeof(struct Rectangle));
    me->super = Shape_ctor(x, y);
    me->width = width;
    me->height = height;

    printf("x: %d\n", Shape_getX(me->super)); //correct value
    printf("y: %d\n", Shape_getY(me->super)); //correct value

    printf("x: %d\n", Shape_getX((Shape*)me)); // wrong value
    printf("y: %d\n", Shape_getY((Shape*)me)); // wrong value

    return me;
}

/*main.c*/
#include <stdio.h>
#include "rectangle.h"

int main(void) {

  Rectangle* r1 = Rectangle_ctor(0, 2, 10, 15);
  printf("r1: (x=%d, y=%d, width=%d, height=%d)\n", Shape_getX((Shape*)r1)
                                                  , Shape_getY((Shape*)r1)
                                                  , Rectangle_getWidth(r1)
                                                  , Rectangle_getHeight(r1));

  return 0;
}

CodePudding user response:

You should place a base type as a first member. Not a pointer to the base type.

struct Rectangle {
    Shape super;
    ...
}

Moreover, you should redesign Shape_ctor. I suggest taking a pointer as a parameter and delegate the memory management to the caller.


Shape* Shape_ctor(Shape *me, int x, int y)
{
    me->x = x;
    me->y = y;

    return me;
}

The constructor of rectangle would be:

Rectangle* Rectangle_ctor(Rectangle *me, int x, int y, unsigned int width, unsigned int height)
{
    Shape_ctor(&me->super, x, y); // call base constructor
    me->width = width;
    me->height = height;

    printf("x: %d\n", Shape_getX(&me->super)); //correct value
    printf("y: %d\n", Shape_getY(&me->super)); //correct value

    return me;
}

Typical usage:

Rectangle rect;
Rectangle_ctor(&rect, ...);

or a bit more exotic variants like:

Rectangle* rect = malloc(sizeof *rect);
Rectangle_ctor(rect, ...);

// or
Rectangle* rect = Rectangle_ctor(malloc(sizeof *rect), ...);

// or even kind of automatic pointer
Rectangle* rect = Rectangle_ctor(&(Rectangle){0}, ...);

The cast would only be needed for implementation of virtual methods like Shape_getArea().

struct Shape {
  ...
  double (*getArea)(struct Shape*);
};

double Shape_getArea(Shape *me) {
  return me->getArea(me);
}
double Rectangle_getArea(Shape *base) {
  Rectangle *me = (Rectangle*)base; // the only cast
  return (double)me->width * me->height;
}

Rectangle* Rectangle_ctor(Rectangle *me, int x, int y, unsigned int width, unsigned int height) {
  ...
  me->super.getArea = Rectangle_getArea;
  ...
}

// usage:
Rectangle rect;
Rectangle_ctor(&rect, 0, 0, 3, 2);

Shape *shape = &rect.super;

Shape_getArea(shape); // should return 6

EDIT

In order to hide internals of Shape place a pointer to its private data in the structure. Initialize this pointer with relevant data in Shape_ctor.

struct Shape {
  void *private_data;
  // non private fields
};

CodePudding user response:

The initial member of Rectangle is a Shape*, not a Shape. And the initial member of Shape is an int, not a Shape*`. The commonality you assume just isn't there.

If you look at how C implementations implement inheritance, in case of single inheritance they will place the base subobject at offset 0 inside the full object. Pointers to base class subobjects are used for virtual inheritance, but that rapidly gets complex.

  • Related