C中的虚拟继承示例,利用未定义的行为?

时间:2014-11-18 16:50:41

标签: c type-conversion undefined-behavior virtual-inheritance strict-aliasing

series of articles中,Dan Saks在C中介绍了虚拟函数的可能实现。更多地依赖于静态类型检查,这是一种不同的方法,而不是the solution of A.-T. Schreinervoid *指针和动态类型检查。

这是一个精简的示例,没有Saks'的vptrvtable s。版本(为简单起见,函数指针只是struct Basestruct Derived的成员。)

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

typedef struct Base Base;

// Base "class"
struct Base {
    int (*get_param)(Base const *self);
};

inline int Base_get_param(Base const *self)
{
    return  self->get_param(self);
}

typedef struct Derived Derived;

// Derived "class"
struct Derived {
    int (*get_param)(Derived const *self);
    int param;
};

Derived * Derived_new(int param)
{
    Derived *self = malloc(sizeof(Derived));
    if (!self) abort();
    self->get_param = Derived_get_param;
    self->param = param;
    return self;
}

void Derived_delete(Derived *self)
{
    free(self);
}

inline int Derived_get_param(Derived const *self)
{
    return self->param;
}


int main()
{
    Derived *d = Derived_new(5);
    printf("%d\n", Derived_get_param(d));
    printf("%d\n", Base_get_param((Base *) d));  // <== undefined behavior?
    Derived_delete(d);
    return 0;
}

要点是函数调用(和强制转换)Base_get_param((Base *) d)。这是否意味着函数指针int (*get_param)(Derived const *self)得到&#34;隐式转换&#34;到int (*get_param)(Base const *self)?我是否在这里利用未定义的行为(根据C99和C11标准),因为类型不兼容?

我使用GCC 4.8和clang 3.4得到了正确的输出。是否存在上述实施可能被破坏的情况?

关于函数指针转换和兼容类型有一个详细的答案here,但我不确定这种情况。

1 个答案:

答案 0 :(得分:1)

这个程序确实调用了undefined behavior,你在这里违反了strict aliasing rules

printf("%d\n", Base_get_param((Base *) d));
                               ^^^^^^^^^

严格的别名规则使得通过不同类型的指针访问对象的行为不确定,尽管 char * 有一个例外,我们可以在不调用未定义的行为的情况下使用它来别名。

基本上,编译器可以围绕不同类型的指针不指向同一内存的假设进行优化。一旦调用未定义的行为,程序的结果就变得不可预测了。

实际上在其他情况下,例如在question中,我无法让编译器做错事,但在其他更复杂的情况下,事情可能会出错。有关确实导致问题的案例,请参阅gcc, strict-aliasing, and horror stories。文章Type Punning, Strict Aliasing, and Optimization提供了以下代码:

#include <stdio.h>

void check (int *h, long *k)
{
  *h = 5;
  *k = 6;
  printf("%d\n", *h);
}

int main (void)
{
  long k;
  check((int *)&k, &k);
  return 0;
}

违反严格别名并使用-O1 V -O2生成不同的输出。

可以使用gcc关闭-fno-strict-aliasing的严格别名,也许作者正在做出这样的假设,尽管我在文章中找不到。这确实禁用了一些优化,因此它不是无成本标志。