结构可以为其自己的初始和唯一成员设置别名吗?
例如,此代码是否有效,还是通过违反别名规则来调用未定义的行为?
int x; struct s { int i; } y; x = 1; y = *(struct s *)&x; printf("%d\n", yi);
我的兴趣在于使用基于此的技术来开发用于执行别名读取的可移植方法。
更新:这是预期的用例,略有不同,但当且仅当上述内容有效时才有效:
static inline uint32_t read32(const unsigned char *p) { struct a { char r[4]; }; union b { struct ar; uint32_t x; } tmp; tmp.r = *(struct a *)p; return tmp.x; }
GCC根据需要将其编译为单个32位负载,并且它似乎避免了如果p
实际指向除char
之外的类型可能发生的混叠问题。 换句话说,它似乎充当了GNU C __attribute__((__may_alias__))
属性的可移植替代品。 但我不确定它是否真的很明确……
我相信这仍然会违反有效的打字规则。 您希望访问未明确声明的内存位置(或在动态分配的情况下通过存储隐式声明)作为通过该类型的表达式包含struct a
的内存位置。
其他答案中引用的部分都不能用于逃避此基本限制。
但是,我相信您的问题有一个解决方案:使用__builtin_memcpy()
,即使在独立环境中也可以使用(请参阅-fno-builtin
上的手册条目 )。
请注意,这个问题不像我说的那样清晰。 C11第6.5节第7节告诉我们,通过左值表达式访问对象是可以的,该表达式具有聚合或联合类型,其中包含其成员中的上述类型之一 。
C99的基本原理清楚地说明了这个限制,因此指向聚合的指针和指向其成员之一的指针可能是别名。
我相信以第一个例子的方式使用这个漏洞的能力(但不是第二个例子,假设p
没有指向实际的char [4]
)是一个意想不到的结果,标准只是不允许因为措辞不精确。
另请注意,如果第一个示例有效,我们基本上可以将结构类型转换为其他名义上键入的语言。 除了共同的初始子序列的联合中的结构(即使这样,成员名称也很重要),相同的内存布局也不足以使类型兼容。 我相信同样的理由适用于此。
我在阅读别名规则(C99,6.5p7)时出现了这句话:
“在其成员中包括上述类型之一的聚合或联合类型(包括递归地,子聚合或包含联盟的成员),或”
导致我认为它没有违反C别名规则。
但是,它不违反别名规则这一事实不足以使此代码段有效。 由于其他原因,它可能会调用未定义的行为。
(struct s *) &x
不保证指向有效的struct s
对象。 即使我们假设x
的对齐适合于struct
类型的对象,但是强制转换后的结果指针可能不会指向足以容纳结构对象的空间(因为struct在其最后一个成员之后可能有填充)。
编辑:答案已从其初始版本完全重做
不确定这是一个正确的答案,但可能发生的事情(在你的第二个例子中)是这样的:
- 编译器将
struct a
定义为一个8字节的对象,在数组中的4个字节之后填充(为什么?因为它可以)。 - 然后使用
tmp.r = *(struct a *)p;
将p视为struct a
的地址(即8字节对象)。 它尝试将此对象的内容复制到tmp.r
,即p
保存的地址中的8个字节。 但是你只能从那里读取4个字节。
实现不必复制填充字节,但允许它们这样做。
在你的第二个例子中
struct a { char r[4]; };
此结构类型可能具有一些对齐限制。 编译器可能决定struct a
总是4字节对齐,例如,它总是可以使用4字节对齐的读指令,而不查看实际地址。 作为read32
的参数接收的指针p
没有这样的限制,所以
*(struct a*)p;
可能会导致总线错误。
我注意到这种类型的论证是一种“实用”的论证。
从标准的角度来看,这是UB (struct a*)p
是对具有更严格的对齐要求的类型的转换。
从C标准:
指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。 如果结果指针未针对指向类型正确对齐(57),则行为未定义。
在这种情况下,结果指针保证正确对齐(因为结构的第一个成员必须与结构重合),因此此限制不适用于此处。 适用的是对指针使用的额外限制,要求只能通过与对象的“有效类型”兼容的指针访问对象…在这种情况下, x
的有效类型是int
,因此无法通过结构指针。
注意,与一些权利要求相反,指针类型之间的转换不限于往返使用。 该标准表示可以转换指针,但附带条件是何时此类转换会导致未定义的行为。 在其他地方,它给出了使用结果类型的指针的语义。 标准中的往返保证是附加规格……如果没有明确说明,您可以依靠的事项:
否则,当再次转换回来时,结果应该等于原始指针。
这指定了往返的保证,它不是往返的限制。
但是,如上所述,“有效类型”语言是对转换产生的指针使用的限制。