普通C多态,类型惩罚和严格别名。这有多合法?

时间:2018-02-13 17:47:55

标签: c polymorphism language-lawyer strict-aliasing type-punning

我一直试图弄清楚以下是合法的,我真的可以使用一些帮助。

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

typedef struct foo {
    int foo;
    int bar;
} foo;

void make_foo(void * p)
{
    foo * this = (foo *)p;

    this->foo = 0;
    this->bar = 1;
}

typedef struct more_foo {
    int foo;
    int bar;
    int more;
} more_foo;

void make_more_foo(void * p)
{
    make_foo(p);

    more_foo * this = (more_foo *)p;
    this->more = 2;
}

int main(void)
{
    more_foo * mf = malloc(sizeof(more_foo));

    make_more_foo(mf);
    printf("%d %d %d\n", mf->foo, mf->bar, mf->more);

    return 0;
}

据我所知,这样做就是打字,并且应该违反严格的别名规则。不过吗?传递的指针无效。您可以按照自己的意愿解释无效指针,对吗?

另外,我读到可能存在内存对齐问题。但结构对齐是确定性的。如果初始成员是相同的,那么它们将以相同的方式对齐,并且从more_foo指针访问所有foo成员应该没有问题。这是对的吗?

GCC在没有警告的情况下编译-Wall,程序按预期运行。但是,我不确定它是否是UB以及为什么。

我也看到了这个:

typedef union baz {
    struct foo f;
    struct more_foo mf;
} baz;

void some_func(void)
{
    baz b;
    more_foo * mf = &b.mf; // or more_foo * mf = (more_foo *)&b;

    make_more_foo(mf);
    printf("%d %d %d\n", mf->foo, mf->bar, mf->more);
}
似乎是允许的。由于联合的多态性,编译器可以使用它。那是对的吗?这是否意味着通过编译严格别名,你不必使用联合而只能使用结构?

编辑:union baz现在编译。

2 个答案:

答案 0 :(得分:1)

标准的作者认为没有必要指定任何可以使用结构或联合成员类型的左值来访问底层结构或联合的方法。编写N1570 6.5p7的方式甚至不允许someStruct.member = 4;,除非member字符类型。然而,能够将&运算符应用于struct和union成员是没有任何意义的,除非标准的作者期望结果指针对某些东西有用。给出脚注88:&#34;此列表的目的是指定对象可能或可能没有别名的情况&#34;,最合乎逻辑的期望是它仅用于适用于lvalues&#39的情况。 ;有用的生命周期会以涉及混叠的方式重叠。

考虑以下代码中的两个函数:

struct s1 {int x;};
struct s2 {int x;};
union {struct s1 v1; struct s2 v2;} arr[10];

void test1(int i, int j)
{
  int result;
  { struct s1 *p1 = &arr[i].v1; result = p1->x; }
  if (result)
    { struct s2 *p2 = &arr[j].v2; p2->x = 2; }
  { struct s1 *p3 = &arr[i].v1; result = p3->x; }
  return result;
}

void test2(int i, int j)
{
  int result;
  struct s1 *p1 = &arr[i].v1; result = p1->x;
  if (result)
    { struct s2 *p2 = &arr[j].v2; p2->x = 2; }
  result = p1->x; }
  return result;
}

test1中,即使i == j,在p1生命期内将访问的所有指针都将通过p1访问,因此{{ 1}}赢得别的任何东西。与p1p2相同。因此,由于没有别名,如果p3 应该没有问题。但是,在i==j test2中,i==j的创建以及最后一次使用它来访问p1的行为将由另一个访问该存储的行为分开指针不是从p1->x派生的。因此,如果p1,那么通过i==j的访问将别名p2,并且根据N1570 5.6p7,将不需要编译器来允许这种可能性。

如果5.6p7的规则即使在不涉及实际混叠的情况下也适用,那么结构和联合将毫无用处。如果它们仅适用于涉及实际混叠的情况,那么很多不必要的复杂性,例如&#34;有效类型&#34;规则可以废除。不幸的是,像gcc和clang这样的编译器使用规则来证明&#34;优化&#34;上面的第一个函数,然后假设他们不必担心在他们的&#34;优化&#34;中出现的结果别名。版本但不是原版。

您的代码可以在任何编写者努力识别派生左值的编译器中正常工作。然而,gcc和clang都会破坏上面的test1()函数,除非用p1标志调用它们。鉴于该标准甚至不允许-fno-strict-aliasing,我建议您不要在上面的someStruct.member = 4;中看到那种混叠,也不要打扰那些可能会出现问题的编译器。甚至处理test2()

答案 1 :(得分:0)

我认为它并不严格,因为如果改变“foo”结构,“更多foo”结构将不得不随之改变。 “foo”必须成为“更多foo”的基础,这是继承,而不是多态。但是你可以使用函数指针来引入多态来帮助这些结构。

实施例

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

#define NEW(x) (x*)malloc(sizeof(x));

typedef struct 
{
    void(*printme)(void*);

    int _foo;
    int bar;

} foo;

typedef struct 
{
    // inherits foo
    foo base;
    int more;
} more_foo;


void foo_print(void *t)
{
    foo *this = (foo*)t;

    printf("[foo]\r\n\tfoo=%d\r\n\tbar=%d\r\n[/foo]\r\n", this->bar, this->_foo);
}

void more_foo_print(void *t)
{
    more_foo *this = t;

    printf("[more foo]\r\n");

    foo_print(&this->base);

    printf("\tmore=%d\r\n", this->more);

    printf("[/more foo]\r\n");
}


void foo_construct( foo *this, int foo, int bar )
{
    this->_foo = foo;
    this->bar = bar;

    this->printme = foo_print;
}

void more_foo_construct(more_foo *t, int _foo, int bar, int more)
{
    foo_construct((foo*)t, _foo, bar);

    t->more = more;

    // Overrides printme
    t->base.printme = more_foo_print;
}

more_foo *new_more_foo(int _foo, int bar, int more)
{
    more_foo * new_mf = NEW(more_foo);

    more_foo_construct(new_mf, _foo, bar, more);

    return new_mf;
}

foo *new_foo(int _foo, int bar)
{
    foo *new_f = NEW(foo);

    foo_construct(new_f, _foo, bar);

    return new_f;
}

int main(void)
{
    foo * mf = (foo*)new_more_foo(1, 2, 3);
    foo * f = new_foo(7,8);

    mf->printme(mf);

    f->printme(f);

    return 0;
}
  • 在创建“more foo”时会覆盖printme()。 (多态性)

  • more_foo包含foo作为基本结构(继承)所以当“foo”结构发生变化时,“more foo”会随之改变(添加示例新值)。

  • more_foo可以演员为“foo”。