我可以对分配的内存做什么有限制吗?(标准)
例如
#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("%lldn",y[0]);
y[0] = 2;
memset (x,0,16);
struct str *bar = x;
bar->b = 4;
printf("%lldn",bar->a);
return 0;
}
总结:
- 只要大小合适,我可以将指针重新转换为其他数据类型和结构吗
- 那么,我可以先读后写吗
- 如果不能,我可以在写作后阅读吗
- 我可以将它与小于分配内存的结构一起使用吗
从y[0]
读取违反了严格的混叠规则。使用类型为long long
的左值来读取有效类型为long
的对象。
假设你省略了那一行;下一个麻烦的部分是CCD_ 4。这个答案认为memset
不会更新有效类型。标准不明确。
假设CCD_ 6保持有效类型不变;下一个问题是CCD_ 7的读取。
C标准对此也不明确。有些人说bar->a
意味着(*bar).a
,这是一种严格的混叠冲突,因为我们没有首先将bar
对象写入该位置。
其他人(包括我)说这很好:唯一用于访问的左值是bar->a
;即long long
类型的左值并且它访问有效类型long long
的对象(由y[0] = 2;
写入的对象)。
有一个C2X工作组正在改进严格混叠的规范,以澄清这些问题。
只要大小合适,我可以将指针重新转换为其他数据类型吗?
您可以将1重写为最多与您分配的内存一样大的任何数据类型。但是,您必须写入一个值,才能根据6.5p6 更改全涂层对象的有效类型
那么,我可以先读后写吗
如果不能,我可以在写完之后阅读吗?
否。除非另有规定(calloc
为其他)2,否则存储器中的值是不确定的。它可能包含陷阱值。将一个值重新解释为另一种类型的强制转换是UB,违反了严格的混叠(6.5p7)
我可以将它与小于分配内存的结构一起使用吗?
是的,但那是浪费。
1您需要先转换为void*
。否则,编译器会对不兼容的指针类型提出合理的抱怨
2即使这样,某些类型也可能捕获完全为0位的模式,因此这取决于情况。
大多数编译器都提供了一种模式,在这种模式下,指针的读写将按照执行顺序作用于底层存储,而不考虑所涉及的数据类型。标准不要求编译器提供这样的模式,但据我所知,所有高质量的编译器都这样做
根据他们发表的基本原理,标准的作者在语言中添加了混叠限制,目的是避免编译器在给定以下代码时做出悲观的混叠假设:
float f;
float test(int *p)
{
f=1.0f;
*p = 2;
return f;
}
请注意,在基本原理中给出的示例中[非常像上面],即使通过指针p
修改f
使用的存储是合法的,一个理性的人在查看代码时也没有理由认为这种事情可能会发生。另一方面,许多编译器编写者认识到,如果给定以下内容:
float f;
float test(float *p)
{
f=1.0f;
*(int*)p = 2;
return f;
}
人们必须故意迟钝地认为代码不太可能修改float
使用的存储,因此,高质量编译器没有理由不将对*(int*)p
的写入视为对float
的潜在写入。
不幸的是,在这几年里,编译器编写人员对基于类型的别名"优化"越来越积极,有时甚至明显且不可否认地超出了标准的允许范围。除非一个程序永远不需要在不同的时间以不同的类型访问任何存储,否则我建议在支持它的编译器上使用-fno-strict-aliasing
选项。否则,一个程序可能有符合标准的代码,现在可以使用,但在未来版本的编译器中失败,因为它的"优化"变得更加激进。
PS——在某些情况下,禁用基于类型的混叠可能会影响代码的性能,但正确使用restrict
限定的变量和参数应该避免悲观混叠假设的代价。只要稍微注意一下,使用这些限定符将实现与激进别名相同的优化,但要安全得多。