因此,严格的别名规则明确允许以下几种情况(为了清楚起见,让我们在C23中这样做):

第一点也是最明显的一点是,允许 struct 使用指向其初始成员的指针作为别名:

typedef struct {
  int data;
} parent;

typedef struct {
  parent _base;
  int metadata;
} child;

int main() {
  child child_obj = {};
  parent* parent_ptr = (parent*) &child_obj; // Fine to read and write
  int* int_ptr = (int*) parent_ptr; // Also fine
}

第二,具有公共初始序列的对象的联合允许访问那些公共元素,并且指向联合的指针可以自由地转换为/作为指向其任何元素的指针来访问.

typedef struct {
  int type_id;
  size_t size;
  char* buffer;
} dynamic_string;

typedef struct {
  int type_id;
  size_t number;
} just_a_number;

typedef union {
  dynamic_string dystr;
  just_a_number num;
  struct {
    int type_id;
    char fixed_string[32];
  };
} string_or_num;

int main() {
  string_or_num obj = {};

  if(obj.type_id == 2) // Fine, common initial sequence
    memcpy("Hello World!", obj.fixed_string, 13);

  // Fine
  dynamic_string* ds_ptr = (dynamic_string*) &obj;
  just_a_number* num_ptr = (just_a_number*) &obj;

  // Also fine, pointer to initial common member
  int* int_ptr = (int*) &obj;
}

直觉上,我认为我可以将这些组合成如下所示.然而,我对我的标准不够有信心,不能百分百肯定地说这是犹太教的

typedef struct {
  int type_id;
  char data[4];
} parent_a;

typedef struct {
  int type_id;
  float decimal;
} parent_b;

// No initial sequence
typedef struct {
  double ccccombo_breaker;
} parent_c;

typedef struct {
  union {
    parent_a _base_a;
    parent_b _base_b;
    parent_c _base_c;
  };
  int look_at_me_ive_got_three_parents;
} child;

int main() {
  child child_obj = {};

  // Are these kosher?
  parent_a* a_ptr = (parent_a*) &child_obj;
  parent_b* b_ptr = (parent_b*) &child_obj;

  // How about this?
  parent_c* c_ptr = (parent_c*) &child_obj;
  double* db_ptr = (double*) &child_obj;
}

需要澄清的是,我并不是在问parent_c是不是一个好主意,而是问标准是怎么说的.通过这些指针进行的读取和写入是否遵循别名规则?

如果你有来自标准的准确语言或标准部分的组合,从而构成令人信服的理由,那么你就会获得加分.

推荐答案

这些是独立的,但大部分是兼容的规则:

  • 指向 struct 的第一个成员/联合的任何成员的指针(6.7.2.1第15节第16节),特别是:

    union的大小足以容纳其最大的成员.At的价值 大多数成员可以随时存储在union对象中.指向一个 经过适当转换的union个对象指向其每个成员(或者如果成员是位域, 然后到它所在的单元),反之亦然.

  • 使用字符指针逐字节判断任何类型的字节(6.3.2.3 §7)

  • "严格混叠"(第6.5章第6节和第7节)

  • 通用首字母序列(6.5.2.3)

此外,还有关于"联合类型双关"(6.5.2.3§3)的规则,该规则允许将联合的成员转换/表示为不同的类型,尽管这可能会在未对齐、超出范围的值、无效/trap 表示等情况下引发各种定义不佳的行为.

你的问题主要是关于第一条规则:

parent_a* a_ptr = (parent_a*) &child_obj;
parent_b* b_ptr = (parent_b*) &child_obj;
parent_c* c_ptr = (parent_c*) &child_obj;

根据"联盟的任何成员"规则,这些都是可以接受的.匿名 struct /联合意味着_base_a_base_b_base_c中的任何一个都是"联合的任何成员",因此指针类型由强制转换"适当地转换".这些类型碰巧在更低的位置有一个共同的初始序列,这并不是真正相关的.我们可以拥有非常不兼容的类型的联合.只有通过可能不兼容的类型或不是别名的类型访问实际数据时,才会出现潜在问题.

然而,double* db_ptr = (double*) &child_obj;有点可疑,因为child_obj的第一个对象不是double.指针转换本身几乎总是很好的,C几乎允许在对象指针之间进行任何疯狂的转换(6.3.2.3§7).

但如果稍后取消对db_ptr的引用,那么您将处于更有问题的领域--"指向任何联盟成员的指针"规则不适用,因此它变成了一个严格的别名问题.反过来,它并不反对对可能是double的东西进行double左值访问.如果存储在那里的二进制内容(全零)也可以表示为double,那么理论上一切都很好.

值得注意的是,这些规则的真实编译器实现的历史并不美观(尤其不是严格的别名和常见的初始序列).该标准保留了许多不清楚的东西,最好不要依赖标准所说/似乎所说的任何东西,因为这不一定是某个特定编译器解释它的方式.此外,一些编译器甚至没有成为高质量实现的雄心.最佳实践是不要相信编译器会正确处理这些问题.例如,最新的gcc 13.2 goes completely bananas面对前面提到的"逐字节判断"规则,这至少从C99开始就在C中了.

C++相关问答推荐

C中的指针增量和减量(*--*++p)

在WSL关闭/重新启动后,是什么原因导致共享对象依赖关系发生更改?

为什么双精度d=flt_max+flt_max;在c语言中得到inf的结果

如何将另一个数组添加到集合中,特别是字符串?

关于scanf()和空格的问题

为什么未初始化的 struct 的数组从另一个数组获取值?

使用正则表达式获取字符串中标记的开始和结束

赋值两侧的后置增量,字符指针

如何在C中处理流水线中的a、n命令?

C中的空指针是什么(_N)?

在我的第一个C语言中观察到的错误';你好世界';程序

计算时出现奇怪的计算错误;N Select K;在C中

为什么写入关闭管道会返回成功

是否可以在多字 C 类型中的任何位置混合存储和类型限定符?

Codewars Kata 掷骰子的不稳定行为

c中数组上显示的随机元素

牛顿分形优化

在C中理解半动态数组的声明

如何按特定方式排列哈夫曼树

编程时什么时候使用原子操作?