c - 纯 C 多态性、类型双关和严格的别名。这有多合法?

标签 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;
}

据我所知,这样做是类型双关,应该违反严格的别名规则。是吗?传递的指针是无效的。您可以随心所欲地解释 void 指针,对吗?

另外,我了解到可能存在内存对齐问题。但是结构对齐是确定性的。如果初始成员相同,那么它们将以相同的方式对齐,并且从 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 的多态性,编译器可以接受。那是对的吗?这是否意味着通过关闭严格别名进行编译,您不必使用 union ,而只能使用结构?

编辑:union baz 现在可以编译。

最佳答案

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

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

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 访问, 所以 p1不会别名任何东西。同样与 p2p3 .因此,由于没有别名,应该如果 i==j 没有问题.在 test2 , 但是,如果 i==j , 然后创建 p1以及最后一次使用它来访问 p1->x将被另一个操作分隔,该操作使用不是从 p1 派生的指针访问该存储.因此,如果i==j , 然后通过 p2 访问将别名 p1 ,并且根据 N1570 5.6p7,编译器不需要允许这种可能性。

如果 5.6p7 的规则即使在不涉及实际别名的情况下也适用,那么结构和 union 将毫无用处。如果它们仅适用于确实涉及实际别名的情况,那么可以消除许多不必要的复杂性,例如“有效类型”规则。不幸的是,一些编译器如 gcc 和 clang 使用规则来证明“优化”上面的第一个函数是合理的,然后假设他们不必担心结果别名存在于他们的“优化”版本中但不在原创。

您的代码将在其作者努力识别派生左值的任何编译器中正常工作。然而,gcc 和 clang 甚至会破坏上面的 test1() 函数,除非使用 -fno-strict-aliasing 调用它们。旗帜。鉴于标准甚至不允许 someStruct.member = 4; ,我建议您不要使用 test2() 中出现的那种别名上面而不是打扰甚至无法处理test1()的编译器.

关于c - 纯 C 多态性、类型双关和严格的别名。这有多合法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48772697/

相关文章:

c - 探索有障碍的网格

c - 为什么求和 floats 到一个 int temporary 运行比当一切都是 int 时慢得多?

c# - 多态 MVC View

c++ - 继承和多态的底层细节

c++ - 函数模板参数推导适用于 gcc,但不适用于 msvc 和 clang

c - 写入文件问题

c - FuncA 未在此范围内声明 C 编程错误

C++ 虚函数基返回类型建议

c++ - 允许编译器优化堆内存分配吗?

c++ - 这段代码,为什么它必须显示未定义的行为?