我可以对分配的内存做些什么限制?(标准方式)
例如
#include <stdio.h>
#include <stdlib.h>
struct str{
long long a;
long b;
};
int main(void)
{
long *x = calloc(4,sizeof(long));
x[0] = 2;
x[3] = 7;
//is anything beyond here legal( if you would exclude possible illegal operations)
long long *y = x;
printf("%lld\n",y[0]);
y[0] = 2;
memset (x,0,16);
struct str *bar = x;
bar->b = 4;
printf("%lld\n",bar->a);
return 0;
}
总结:
答案 0 :(得分:4)
从y[0]
读取违反严格别名规则。您使用类型为long long
的左值来读取有效类型long
的对象。
假设你省略了那条线;下一个麻烦的部分是memset(x,0,16);
。 This answer认为memset
不会更新有效类型。标准尚不清楚。
假设memset
保持有效类型不变;下一期是阅读bar->a
。
C标准也不清楚。有人说bar->a
暗示(*bar).a
,这是严格的别名违规,因为我们没有先将bar
对象写入该位置。
其他人(包括我)说它很好:用于访问的唯一左值是bar->a
;这是long long
类型的左值,它访问有效类型为long long
的对象(由y[0] = 2;
写入的对象)。
有一个C2X工作组致力于改进严格别名的规范,以澄清这些问题。
答案 1 :(得分:3)
我可以重命名指向其他数据类型的指针,只要大小合适吗?
您可以将 1 重新转换为与您分配的内存一样大的任何数据类型。但是,您必须根据6.5p6
更改值以更改所有涂层对象的有效类型我可以在写作之前阅读吗? 如果没有,我可以在写完后阅读吗?
没有。除非另有指定(否则为calloc
) 2 ,否则内存中的值是不确定的。它可能包含陷阱值。为了将值重新解释为另一种类型的强制转换是UB,违反严格别名(6.5p7)
我可以使用小于分配内存的结构吗?
是的,但那是浪费。
1 您需要先转换为void*
。否则,您将从编译器获得有关不兼容指针类型的合理投诉
2 即使这样,某些类型也可能陷入完全0位模式,因此它取决于。
答案 2 :(得分:0)
大多数编译器都提供一种模式,在这种模式下,指针的读取和写入将按照执行的顺序作用于底层存储,而不管涉及的数据类型如何。标准不要求编译器提供这样的模式,但据我所知,所有质量编译器都这样做。
根据他们公布的理由,标准的作者在语言中添加了别名限制,其目的是避免编译器在给定代码时做出悲观的别名假设:
float f;
float test(int *p)
{
f=1.0f;
*p = 2;
return f;
}
请注意,在基本原理中给出的示例[非常类似于上述],即使通过指针f
修改p
使用的存储是合法的,也是一个合理的人看着代码没有理由认为这样的事情可能会发生。另一方面,许多编译器编写者认识到如果给出类似的东西:
float f;
float test(float *p)
{
f=1.0f;
*(int*)p = 2;
return f;
}
人们必须刻意地认为代码不太可能修改float
使用的存储,因此没有理由认为质量编译器不应该将写入视为{{1作为对*(int*)p
的潜在写入。
不幸的是,在这几年中,编译器编写者越来越积极地使用基于类型的别名“优化”,有时会以明显无可否认的方式超出标准允许的范围。除非程序永远不需要在不同时间以不同类型访问任何存储,否则我建议在支持它的编译器上使用float
选项。否则,可能有一个符合标准的代码并且今天可以运行,但是在未来版本的编译器中失败了,它的“优化”变得更加激进。
PS - 在某些情况下,禁用基于类型的别名可能会影响代码的性能,但正确使用-fno-strict-aliasing
- 合格的变量和参数应该可以避免悲观别名假设的成本。稍微小心一点,使用这些限定符可以实现与积极混叠相同的优化,但更安全。