简单的结构继承&伪多态性与严格别名

时间:2015-01-16 09:35:31

标签: c pointers inheritance struct strict-aliasing

如果有人回答我的问题,请不要告诉我使用C ++

所以,我在C中创建了一个使用面向对象方法的小型库。我选择在C中使用两种主要的继承方法中较不常见的方法:将基类型的成员复制到派生类型的开头。像这样:

struct base {
  int a;
  int b;
  char c;
};

struct derived {
  int a;
  int b;
  char c;
  unsigned int d;
  void (*virtual_method)(int, char);
};

这种方法不如另一种方法(基类型的实例作为派生类型的第一个成员)受欢迎,因为

  1. 从技术上讲,没有标准保证基础和派生结构的第一个共同成员将具有相同的偏移量。但是,除了其中一个结构被打包而另一个结构没有被打包的情况之外,它们将在大多数(如果不是全部)已知编译器上具有相同的偏移量。
  2. 这种方法最严重的缺陷:它违反了严格的别名。将指向派生结构的指针强制转换为其基类型,然后解除引用指针在技术上是未定义的行为。
  3. 然而,与其他方法相比,它也有其优点:

    1. 更少详细:访问已继承的派生结构的成员与访问尚未继承的结构相同,而不是转换为基类型,然后然后访问所需成员;
    2. 这实际上是真正的继承而不是组合;
    3. 虽然可能需要一点点预处理程序滥用,但它与其他方法一样容易实现;
    4. 我们可以得到一个实际多重继承的半生不熟的形式,我们可以从几个基类型继承,但只能转换为其中一个。
    5. 我一直在寻找使我的库编译和使用强制执行严格别名的编译器(如 gcc )正确工作的可能性,而无需用户手动关闭它。以下是我研究过的可能性:

      1. 联盟。遗憾的是,由于以下几个原因,这些是禁忌的:

        1. 详细程度返回!要遵循通过联合访问2个结构的第一个共同成员的标准规则,必须(从C99开始)明确使用联合来访问第一个共同成员。我们需要特殊的语法来访问union中每种类型的成员!
        2. 空间。考虑一个继承层次结构。我们有一个类型,我们希望能够从其派生类型的每个转换为。我们希望为每个类型执行此操作。我看到的唯一可行的联合使用解决方案是整个层次结构的并集,它必须用于将派生类型的实例转换为基类型。它必须与整个层次结构中派生类型最多的一样大......
      2. 使用memcpy代替直接解除引用(例如here)。这看起来是个不错的解决方案。但是,函数调用会产生开销,是的,再一次,冗长。据我所知,memcpy所做的事情也可以通过将指向结构的指针强制转换为指向char的指针,然后取消引用它来完成,如下所示:(member_type)(*((char*)(&struct_pointer->member))) = new_value; Gah,再次详述。好吧,这可以用宏包裹。但是,如果我们将指针转换为指向不兼容类型的指针,然后然后将其转换为char*并取消引用它,那么它仍然有用吗?像这样:(member_type)(*((char*)(&((struct incompatible_type*)struct_pointer)->member))) = new_value;

      3. 声明我们要转换为volatile的所有类型实例。我想知道为什么这不经常出现。据我所知,volatile用于告诉编译器指针指向的内存可能会意外更改,从而根据一段指向内存不会改变的假设取消优化,是所有严格别名问题的原因。当然,这仍然是未定义的行为;但对于某些类型的某些实例的“hackishly”禁用严格的别名优化,它不是一个可行的跨平台解决方案吗?

      4. 除上述问题外,还有两个问题:

        1. 我上面说的是错误的吗?
        2. 我错过了一些可能对我有帮助的事情吗?

2 个答案:

答案 0 :(得分:4)

我不认为您通过char*投射的想法是有效的。 规则是:

  

对象的存储值只能由左值访问   具有以下类型之一的表达式

表达式的子表达式是兼容的,但整体表达式不兼容。

我认为唯一现实的方法是构图:

struct base {
  int a;
  int b;
  char c;

  void (*virtual_method)(base*/*this*/,int, char);

};

struct derived {
    struct base;
    unsigned int d;
};

我意识到这是一种在理智上没有吸引力的方式来实现继承。

PS:我没有把你的虚拟成员函数指针放在我的派生类中。它需要从base访问,因此需要在那里声明(假设它是basederived都存在的多态函数)。 我还添加了一个this参数来充实模型。

答案 1 :(得分:1)

memcpy应该是要走的路。 不要担心函数调用开销。通常情况下,没有。 memcpy通常是编译器内在的,这意味着编译器应该为它编写最有效的代码,并且它应该知道它可以优化memcpies的位置。

不要将指针转换为不兼容的指针然后取消引用。这是走向未定义行为的道路。

如果您接受表达式语句和gcc的##__VA_ARGS__,那么您可以使用MC_base_method(BaseType,BaseMethod,Derived_ptr,...)宏来正确调用BaseMethod Derived_ptr...因为你可以使用结构的副本,就像它是原始的一样(例如,没有指向结构自己的成员的指针)。

这是一个支持一些额外支持OOP的宏糖的例子:

//Helper macros for some C++-like OOP in plain C 
#define MC_t_alias(Alias, ...)  typedef __VA_ARGS__ Alias               //like C++'s  using 
#define Struct(Nm,...) MC_t_alias(Nm, struct Nm); struct Nm __VA_ARGS__ //autypedefed structs

#define ro const //readonly -- I don't like the word const

//Helper macros for method declarations following my 
//Type__method(Type* X, ...) naming convention
#define MC_mro(Tp,Meth, ...) Tp##__##Meth(Tp ro*X, ##__VA_ARGS__)

#include <stdio.h>
#include <string.h>
//I apend my data structs with _d to know they're data structs
Struct(base_d, {
  int a;
  int b;
  char c;
});

Struct(derived_d, {
  int a;
  int b;
  char c;
  unsigned int d;
  void (*virtual_method)(derived_d*, int, char);
});

//print method is unaware of derived_d 
//it takes a `base_d const *X` (the mro (method, readonly) macros hides that argument (X==`this` in common OOP speak))
int MC_mro(base_d,print) 
{
    return printf("{ a=%d b=%d c=%d }", X->a, X->b, X->c);
}

/*
    Call a (nonvirtual) base method 
*/

#define MC_base_method(BaseType, Method, Derived_p, ...)                       \
({                                                                             \
    int _r; /*if you conventionally return ints*/                                \
            /*otherwise you'll need __typeof__ to get the type*/               \
    BaseType _b;                                                               \
    memcpy(&_b, Derived_p, sizeof(_b));                                        \
    _r = BaseType##__##Method(&_b, ##__VA_ARGS__);                             \
    /*sync back -- for non-readonly methods */                                 \
    /*a smart compiler might be able to get rid of this for ro method calls*/  \
    memcpy(Derived_p, &_b, sizeof(_b));                                        \
    _r;                                                                        \
})


int main()
{
    derived_d d = {1,2,3,4};
    MC_base_method(base_d, print, &d);
}

我认为编译器的工作是优化memcpies。但是,如果它没有,你的结构是巨大的,那你就搞砸了。如果你的结构包含指向它们自己的成员的指针(例如,如果你不能使用每字节副本的字节,就好像它是原始的那样)。