我可以用分配的内存做我想做的事吗
Can I do what I want with allocated memory
我对已分配内存的操作是否有限制?(标准方式)
例如
#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;
}
总结一下:
- 我可以将指针重铸为其他数据类型和结构,只要大小合适吗?
- 那我可以先读后写吗?
- 如果不能写完还能看吗?
- 我可以将它用于小于分配内存的结构吗?
Can I recast the pointer to other datatypes, as long as the size fits?
您可以将1 重铸为最大与您分配的内存一样大的任何数据类型。但是,您必须写入一个值才能根据 6.5p6
更改 allcoated 对象的有效类型
Can I read before I write, then?
If not can I read after I wrote?
没有。除非另有说明(calloc
否则为2,内存中的值是不确定的。它可能包含陷阱值。为了将值重新解释为另一种类型而进行的转换是 UB,并且违反了严格的别名 (6.5p7)
Can I use it with a struct smaller than the allocated memory?
是的,但那是一种浪费。
1 您需要先转换为 void*
。否则你会从编译器那里得到关于不兼容指针类型的正当投诉。
2 即使如此,某些类型可能会陷入完全为 0 的位模式,所以这取决于。
从 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 工作组正在致力于改进严格别名的规范以澄清这些问题。
大多数编译器都提供一种模式,在这种模式下,无论涉及何种数据类型,指针的读写都将按执行顺序作用于底层存储。该标准不要求编译器提供这种模式,但据我所知,所有高质量的编译器都会这样做。
根据他们公布的基本原理,该标准的作者向语言添加了别名限制,其目的是避免编译器在给定代码时做出悲观的别名假设:
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
.
的潜在写入
不幸的是,在这期间,编译器编写者在 type-based 别名 "optimizations" 方面变得越来越激进,有时其方式明显且不可否认地超出了标准所允许的范围。除非程序永远不需要在不同时间访问不同类型的任何存储,否则我建议在支持它的编译器上使用 -fno-strict-aliasing
选项。否则,可能会有符合标准并在今天运行的代码,但在编译器的未来版本中会失败,该编译器的 "optimizations".
变得更加激进。
PS--禁用type-based别名可能会在某些情况下影响代码的性能,但正确使用restrict
限定的变量和参数应该避免悲观别名的代价假设。稍加小心,使用这些限定符将实现与激进别名相同的优化,但更安全。
我对已分配内存的操作是否有限制?(标准方式)
例如
#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;
}
总结一下:
- 我可以将指针重铸为其他数据类型和结构,只要大小合适吗?
- 那我可以先读后写吗?
- 如果不能写完还能看吗?
- 我可以将它用于小于分配内存的结构吗?
Can I recast the pointer to other datatypes, as long as the size fits?
您可以将1 重铸为最大与您分配的内存一样大的任何数据类型。但是,您必须写入一个值才能根据 6.5p6
更改 allcoated 对象的有效类型Can I read before I write, then?
If not can I read after I wrote?
没有。除非另有说明(calloc
否则为2,内存中的值是不确定的。它可能包含陷阱值。为了将值重新解释为另一种类型而进行的转换是 UB,并且违反了严格的别名 (6.5p7)
Can I use it with a struct smaller than the allocated memory?
是的,但那是一种浪费。
1 您需要先转换为 void*
。否则你会从编译器那里得到关于不兼容指针类型的正当投诉。
2 即使如此,某些类型可能会陷入完全为 0 的位模式,所以这取决于。
从 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 工作组正在致力于改进严格别名的规范以澄清这些问题。
大多数编译器都提供一种模式,在这种模式下,无论涉及何种数据类型,指针的读写都将按执行顺序作用于底层存储。该标准不要求编译器提供这种模式,但据我所知,所有高质量的编译器都会这样做。
根据他们公布的基本原理,该标准的作者向语言添加了别名限制,其目的是避免编译器在给定代码时做出悲观的别名假设:
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
.
不幸的是,在这期间,编译器编写者在 type-based 别名 "optimizations" 方面变得越来越激进,有时其方式明显且不可否认地超出了标准所允许的范围。除非程序永远不需要在不同时间访问不同类型的任何存储,否则我建议在支持它的编译器上使用 -fno-strict-aliasing
选项。否则,可能会有符合标准并在今天运行的代码,但在编译器的未来版本中会失败,该编译器的 "optimizations".
PS--禁用type-based别名可能会在某些情况下影响代码的性能,但正确使用restrict
限定的变量和参数应该避免悲观别名的代价假设。稍加小心,使用这些限定符将实现与激进别名相同的优化,但更安全。