C 实践中的严格混叠

C 实践中的严格混叠,c,compilation,C,Compilation,类似以下的C11代码是未定义的行为: // test.c #include <stdio.h> struct point2d { int x, y; }; struct point3d { int x, y, z; }; typedef struct point2d point2d; typedef struct point3d point3d; int foo(point2d *p, point3d *q) { p->x = -1; p->y =

类似以下的C11代码是未定义的行为:

// test.c
#include <stdio.h>

struct point2d {
  int x, y;
};

struct point3d {
  int x, y, z;
};

typedef struct point2d point2d;
typedef struct point3d point3d;

int foo(point2d *p, point3d *q) {
  p->x = -1;
  p->y = -2;
  q->x = 1;
  q->y = 2;
  q->z = 3;
  return p->x;
}

int main(void) {
  point3d r;
  int n = foo((point2d *) &r, &r);

  printf("%d\n", n);
  return 0;
}
C11标准规定(6.5/7):

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

  • 与对象的有效类型兼容的类型

  • 与对象的有效类型兼容的类型的限定版本

  • 与对象的有效类型相对应的有符号或无符号类型

  • 一种类型,它是与对象的有效类型的限定版本相对应的有符号或无符号类型

  • 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含的联合的成员),或

  • 字符类型

我的问题是关于一种技术,通过将指向较大结构的指针转换为指向较小结构的指针来隐藏信息,该较小结构不包含较大结构的所有成员

以下是相关结构定义的摘要:

struct _GRealArray
{
  guint8 *data;
  guint   len;
  guint   alloc;
  guint   elt_size;
  guint   zero_terminated : 1;
  guint   clear : 1;
  gint    ref_count;
  GDestroyNotify clear_func;
};

struct _GArray
{
  gchar *data;
  guint len;
};

typedef struct _GRealArray GRealArray;
typedef struct _GArray      GArray;

这种技术是否已经违反了标准?如果否:有什么区别?如果是:为什么在这里不重要?在这种情况下,是否有一些实用的指导原则允许您事实上违反标准而不会产生不良后果(与上面的
test.c
示例相反)?

Garray示例将遵守相同的规则,如果它会强调这些规则,但它似乎谨慎地避免了这些规则

对于用户代码,这两种类型之间永远不会存在别名冲突,因为如果我看得正确的话,
GRealArray
类型不可见,因此这两种类型的用户代码中不会出现所有这些别名问题

对于实现的内部代码,似乎没有出现别名的地方,原因很简单,每个函数中始终只有一个可见的数组。或者更直接地说,

仅当存在潜在的别名时,别名规则才适用

即使它会这样做,如果该实现对它处理的两个不同指针始终使用相同的类型,编译器仍然必须假设两个这样的指针可以指向同一个对象


顺便说一句,这只回答了您的直接问题,而不是您所指向的代码的行为是否定义良好。这将要求对该代码进行深入审查。

如果显然无法执行,则无所谓,访问具有不兼容类型的对象是未定义的行为。即使函数接收到一个T类型的指针,如果向该函数传递了一个不兼容的指针,您也会得到ub。在示例中,当指针返回
return(GArray*)数组时,就会发生这种情况
然后被调用方使用GArray类型进行访问。@2501,是的,这很重要。提出的问题是关于别名规则,而不是其他。那么我也认为你的反对意见可能不适用,但我没有时间更详细地研究他们的代码。我认为唯一真正作用于数组的代码是TU,TU总是使用扩展数组定义。用户代码只看到指向该内容的指针,但所有修改都是通过有效类型进行的。写入
array
将其有效类型设置为
GRealArray
,并保持该类型。如果从该对象读取,则有效类型不会更改。如果用户在任何时候读取结构成员(使用返回的类型
GArray
),则为ub。如果指针只传递给使用正确类型的函数,而用户从未读取成员,则代码有效。我没有看到完整的代码,但我相信这样做的目的是让用户从这两个成员处读取数据(否则函数只需返回一个不透明的指针,就可以完全避免这个问题)。@2501,你可能是对的,这意味着用户代码读取一些字段。但不要误解UB。它只是没有定义,没有被C标准定义发生了什么。平台可以提供不同的保证,而且看起来确实如此。同样有效的是,用户代码无法知道写操作使用的是不同的有效类型,TU也无法看到有人通过使用不同的类型达到数据峰值。如果这与LTO一起使用,所有这些都可能发生变化,即一个TU在某种意义上会知道另一个TU做什么。我明白你的观点。如果答案包括你在上一篇评论中所写的内容,我可能不会有任何异议,因为问题是关于C11的,而不是一个特定的平台。
struct _GRealArray
{
  guint8 *data;
  guint   len;
  guint   alloc;
  guint   elt_size;
  guint   zero_terminated : 1;
  guint   clear : 1;
  gint    ref_count;
  GDestroyNotify clear_func;
};

struct _GArray
{
  gchar *data;
  guint len;
};

typedef struct _GRealArray GRealArray;
typedef struct _GArray      GArray;