14

例如,这段代码是否有效,或者它是否通过违反别名规则来调用未定义的行为?

int x;
struct s { int i; } y;
x = 1;
y = *(struct s *)&x;
printf("%d\n", y.i);

我的兴趣是使用基于此的技术来开发一种可移植的方法来执行别名读取。

更新:这是预期的用例,有点不同,但当且仅当上述有效时,它才应该有效:

static inline uint32_t read32(const unsigned char *p)
{
    struct a { char r[4]; };
    union b { struct a r; uint32_t x; } tmp;
    tmp.r = *(struct a *)p;
    return tmp.x;
}

GCC 根据需要将其编译为单个 32 位加载,并且它似乎避免了如果p实际指向的类型不是char. 换句话说,它似乎充当了 GNU C__attribute__((__may_alias__))属性的可移植替代品。但我不确定它是否真的很好定义......

4

5 回答 5

5

我相信这仍然会违反有效的打字规则。您希望访问未显式声明(或在动态分配的情况下通过存储隐式声明)包含 astruct a通过该类型的表达式的内存位置。

其他答案中引用的任何部分都不能用来逃避这个基本限制。

但是,我相信您的问题有一个解决方案: Use __builtin_memcpy(),即使在独立环境中也可用(请参阅手册条目-fno-builtin)。


请注意,这个问题没有我说的那么明确。C11 第 6.5 节第 7 节告诉我们,可以通过左值表达式访问对象,该左值表达式具有聚合或联合类型,其成员中包含上述类型之一

C99 的基本原理清楚地表明存在此限制,因此指向聚合的指针和指向其成员之一的指针可以别名。

我相信以第一个例子的方式使用这个漏洞的能力(但不是第二个例子,假设p没有碰巧指向一个实际的char [4])是一个意想不到的结果,标准只是因为措辞不准确而不允许这样做。

另请注意,如果第一个示例有效,我们基本上可以将结构类型潜入到另一种名义上的类型语言中。除了具有公共初始子序列的联合中的结构(即使那样,成员名称也很重要),相同的内存布局不足以使类型兼容。我相信同样的推理也适用于这里。

于 2013-06-29T22:53:24.083 回答
3

我对别名规则(C99,6.5p7)的阅读与这句话的存在:

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

导致我认为它不违反 C 别名规则。

但是它不违反别名规则的事实不足以使此代码段有效。由于其他原因,它可能会调用未定义的行为。

(struct s *) &x

不保证指向一个有效的struct s对象。即使我们假设 的对齐方式x适用于 type 的对象,struct强制转换后的结果指针也可能不会指向足够大的空间来容纳结构对象(因为 struct 可能在其最后一个成员之后有填充)。

编辑:答案已从其初始版本完全修改

于 2013-06-30T00:10:42.717 回答
0

不确定这是一个正确的答案,但可能发生的事情(在你的第二个例子中)是这样的:

  1. 编译器定义struct a为一个 8 字节的对象,在数组中的 4 个字节之后进行填充(为什么?因为它可以)。
  2. 然后使用tmp.r = *(struct a *)p;which 将 p 视为 a 的地址struct a(即 8 字节对象)。它尝试将此对象的内容复制到 中tmp.r,即从p所持有的地址开始的 8 个字节。但是你只能从那里读取 4 个字节。

实现不必复制填充字节,但允许这样做。

于 2013-06-29T22:04:21.747 回答
0

在你的第二个例子中

struct a { char r[4]; };

这种结构类型可能有一些对齐限制。编译器可能决定它struct a总是 4 字节对齐,例如,它总是可以使用 4 字节对齐的读取指令,而无需查看实际地址。p您作为参数接收的指针read32没有这样的限制,所以

*(struct a*)p;

可能会导致总线错误。

我注意到这种类型的论点是一种“实用的”论点。

从标准的角度来看,这是 UB,只要(struct a*)p转换为具有更严格对齐要求的类型。

于 2013-06-29T23:05:34.760 回答
-1

来自 C 标准:

指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。如果结果指针未正确对齐 (57) 指向的类型,则行为未定义。

在这种情况下,结果指针保证正确对齐(因为结构的第一个成员必须与结构重合),因此此限制不适用于此处。适用的是对指针使用的附加限制,要求只能通过与对象的“有效类型”兼容的指针来访问对象......在这种情况下,有效类型xint,因此不能通过结构访问指针。

请注意,与某些声明相反,指针类型之间的转换不限于往返使用。该标准说可以转换指针,但附带条件是此类转换何时会导致未定义的行为。在其他地方,它给出了使用结果类型指针的语义。标准中的往返保证附加规范……如果没有明确说明,您可以指望的事情是:

否则,当再次转换回来时,结果将等于原始指针。

这指定了关于往返的保证,而不是对往返的限制。

然而,如前所述,“有效类型”语言对指针使用的限制,该指针由转换产生。

于 2013-06-29T21:59:01.360 回答