在series of articles中,Dan Saks在C中介绍了虚拟函数的可能实现。更多地依赖于静态类型检查,这是一种不同的方法,而不是the solution of A.-T. Schreiner与void *
指针和动态类型检查。
这是一个精简的示例,没有Saks'的vptr
和vtable
s。版本(为简单起见,函数指针只是struct Base
和struct 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,但我不确定这种情况。
答案 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
的严格别名,也许作者正在做出这样的假设,尽管我在文章中找不到。这确实禁用了一些优化,因此它不是无成本标志。