因此,严格的别名规则明确允许以下几种情况(为了清楚起见,让我们在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++相关问答推荐

自定义malloc实现上奇怪的操作系统依赖行为

从内联程序集调用Rust函数和调用约定

标准的C17标准是用括号将参数包装在函数声明中吗

SDL 2.0-从数组渲染纹理

编译器如何处理具有更复杂值的枚举?

处理EPOLL_WAIT中的接收数据和连接关闭信号

如何用C语言为CLI应用程序编写按键检测系统?

将字符串数组传递给C中的函数:`str[dim1][str_size]`vs`*str[dim1]`

从不兼容的指针类型返回&&警告,但我看不出原因

如何逐位读取二进制文件?

程序如何解释变量中的值

将不同类型的指针传递给函数(C)

如何不断地用C读取文件?

我错误地修复了一个错误,想了解原因

将char*铸造为空**

程序打印一些随机空行

多行表达式:C 编译器如何处理换行符?

无法在 C 中打开文本文件,我想从中读取文本作为数据并将其写入数组

定义 int a = 0, b = a++, c = a++;在 C 中定义了行为吗?

将帧从相机 (/dev/video0) 复制到帧缓冲区 (/dev/fb0) 会产生意外结果