【问题标题】:Union of different types and sizes of pointers不同类型和大小指针的联合
【发布时间】:2016-09-16 17:50:03
【问题描述】:
typedef struct  {  char   ch;  int   num;  }  st_t;
typedef union   {  char *pch;  st_t *pst;  }  un_t;

st_t st;
st.ch = 's';

un_t un = { &st.ch };
*un.pch = 'u';

printf("%c\n", un.pst->ch);  // expect: print the letter 'u'

据我所知,结构的第一个成员的地址和结构本身的地址是相同的,所以un可以通过访问同时指向stst.ch pstpch。然而,C99 标准似乎从未明确表示过the sizes of different types of pointers are identical。然后,我担心的是代码是否会被破坏,例如违反严格的别名规则、未定义的行为等?

【问题讨论】:

  • 注意:C99 不是当前的 C 标准。那将是C11。您可能想了解指针转换等。您可能遇到的一个问题是违反了您提到的有效类型(也称为严格别名)规则。这会引发未定义的行为(UB 本身不是问题,而是违反规则/约束的结果。

标签: c pointers undefined-behavior unions strict-aliasing


【解决方案1】:

行:

un_t un = {&st.ch};
*un.pch = 'u';

将结构的成员 ch 的地址分配给联合的成员 pch,然后使用该指针将字符写入该地址。这是完全正确的。

以下行有问题:

printf("%c\n", un.pst->ch);

读取联合的成员,而不是最后存储的成员。
下面可能是一个trap1表示2

un.pst

1(引自:ISO/IEC 9899:201x 6.2.6.1 General 5)
某些对象表示不需要表示对象类型的值。如果存储 对象的值具有这样的表示,并由左值表达式读取 没有字符类型,行为未定义。如果产生这样的表示 通过通过左值表达式修改对象的全部或任何部分的副作用 没有字符类型,行为未定义。 50) 这种表示称为 陷阱表示。

2(引自:ISO/IEC 9899:201x 6.5.2.3 结构和工会成员 3 脚注 95)
如果用于读取联合对象内容的成员与上次用于读取的成员不同 在对象中存储一个值,该值的对象表示的适当部分被重新解释 作为 6.2.6 中描述的新类型中的对象表示(有时称为“类型 双关语'')。这可能是一个陷阱表示。

【讨论】:

  • 典型 32/64 位 CPU 上更常见的问题是对齐问题。
  • @Olaf 在这种情况下不是,因为指针指向结构。
  • 嗯,是的,你在这里是对的。我看了更一般的情况。取消引用会违反有效类型规则(我更喜欢标准中使用的术语)。
  • @2501:您可以双关指针类型,但不能双关它们指向的对象。现在是星期五晚上,我太累了,现在无法检查标准,但我对此有一种不好的感觉。通常我可以相信我的蜘蛛感觉(好吧,足够的电影报价)。我不会使用这种结构。我先把它留在这里。
  • @KevinDong 如果您担心项目中的代码不同,请发布一个新问题,其中包含重现问题的最小示例。并提出一个明确的问题。
【解决方案2】:

虽然这样的结构并不严格符合指针大小的标准,但实际上最常见的实现(即 msvc、gcc/linux)确实对非函数指针使用相同的表示。

因此,在这些实现中,它应该可以按您的预期工作。

【讨论】:

  • 我会非常小心“大多数实现”。大多数实现都是针对具有各种特性的 MCU。例如对于 x86-16bit 有不同的指针宽度。但我们不必走那么远。指针的对齐方式可以不同。
  • @Olaf 够公平的。编辑为“最常见的实现”,即初学者最有可能遇到的。
  • 嗯,我的嵌入式课程的初学者可以从 PIC、C51 等开始 ;-) (是的,那是吹毛求疵,我会在这里停下来;-)
  • @Olaf:即使在通过添加额外的存储类来扩展 C 语言的平台上,并且指向不同存储类的指针具有不同的大小(例如,在 8086 上,“near”指针是两个字节,而“ far" 指针是四个字节),实现非常很少有数据指针的大小或表示根据目标对象的类型(而不是存储类)而变化。
【解决方案3】:

C 标准试图定义所有符合要求的实现所需的特性、特性和保证;它没有尽力描述 99% 的实现共有但不是必需的大部分有用的特性和特性。

在绝大多数实现中,所有数据指针都使用相同的表示,因此给定thingType *foo=xxx; void *vfoo = foo;,两者都是foovfoo 将保持相同的位模式。在激进的日子之前 优化器,这样的实现将非常有用地允许任何类型的数据 要使用void** 访问的指针,因此可以编写如下函数:

int realloc_if_possible(void **p, int newsize)
{
  void *temp = realloc(*p, newsize);
  if (!temp) return -1;
  *p = temp;
  return 0;
}

但由于 C 标准不要求编译器在以下情况下识别别名 函数被传递给除 void* 以外的任何类型指针的地址, 许多现代编译器不再支持这样的结构,除非所有的都是基于类型的 别名优化被禁用。

【讨论】:

  • 包含 sometype* 和 void* 的联合怎么样?这种类型的双关语是否符合标准,因为 void* 应该与任何其他类型的指针兼容?
  • @KevinDong:这属于 99% 的实现都支持但标准不要求的行为类别。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-29
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多