将整数地址转换为双指针并读取它,但是整数的大小小于double类型,并且读取操作将读取超过对象大小。我认为这是未定义的行为,但我没有在C标准中找到描述,所以我发布这个问题以寻求答案来证实我的观点。
#include <stdio.h>
#include <stdint.h>
int main() {
int32_t a = 12;
double *p = (double*)(&a);
printf("%lf\n", *p);
return 0;
}
答案 0 :(得分:3)
每个C11 6.5的未定义行为(&#34;严格的别名规则&#34;):
6访问其存储值的对象的有效类型是对象的声明类型(如果有) ...
在这种情况下,有效类型为int32_t
(与int
或long
相对应的typedef。)
7对象的存储值只能由具有以下类型之一的左值表达式访问:
- 与对象的有效类型兼容的类型,
...
double
与int32_t
不兼容,因此当代码访问此处的数据时:*p
,它违反此规则并调用UB。
有关详细信息,请参阅What is the strict aliasing rule?。
答案 1 :(得分:1)
从C99 Committee Draft 6.5 Expressions
第7点开始:
对象的存储值只能由具有以下类型之一的左值表达式访问:76)
- 与对象的有效类型兼容的类型,
- 与对象的有效类型兼容的类型的限定版本,
- 与对象的有效类型对应的有符号或无符号类型的类型,
- 对应于对象有效类型的限定版本的有符号或无符号类型的类型,
- 在其成员中包含上述类型之一的聚合或联合类型(包括递归地,子聚合或包含联合的成员),或者
- 字符类型。
int
类型的对象使用double
类型的左值表达式访问它的地址。 int
和double
类型无论如何都不兼容,它们不是聚合的,double
不是字符类型。取消引用指向类型为int的对象的类型double的指针(左值表达式)是未定义的行为。此类操作称为严格别名违规。
答案 2 :(得分:1)
如果使用与该类型没有可见关系的左值访问类型为“int”的对象,则标准不要求编译器行为可预测。然而,在理由中,作者指出,将某些行为分类为未定义行为旨在让市场决定在质量实施中哪些行为被认为是必要的。通常,将指针转换为另一种类型然后立即对其执行访问的行为属于将被配置为适合于系统编程的质量编译器支持的操作类别,但编译器可能不支持这是一种愚蠢的行为。
即使忽略左值类型的问题,标准也没有要求如果应用程序试图从它不拥有的内存中读取会发生什么。同样,行为的选择有时可能是一个实施质量问题。这里有五种主要的可能性:
在某些实现中,存储的内容可能是 可预测的通过标准没有描述的手段和阅读 会产生这种存储的内容。
阅读行为的行为可能就好像它会产生一些比特 未指定的值,但没有其他副作用。
尝试阅读可能会终止该程序。
在使用内存映射I / O的平台上,越界读取可以 执行意外操作,后果不明;这个 可能性仅适用于某些平台。
尝试以各种方式“聪明”的实现可能会尝试 因此,根据读取不能发生的概念得出推论 导致超出时间和因果关系的副作用。
如果您知道您的代码将在具有读取功能的平台上运行 没有副作用,实施不会试图“聪明”,而且你的代码 准备好读取可能产生的任何位模式,然后根据那些 这种阅读的情况可能有用,但你会受到限制 可以使用代码的情况。
请注意,虽然需要定义__STDC_ANALYZABLE__
的实现
大多数行为都遵守时间和因果关系的法则,即使在这种情况下也是如此
标准不会强加任何其他要求,越界读取
归类为关键未定义行为,因此应予以考虑
对任何未明确说明的实施都是危险的。
顺便提一下,某些平台上存在另一个问题,即使例如代码使用了int[3]
而不是单个int
:alignment。在某些平台上,某些类型的值只能从某些地址读取或写入某些地址,而某些适用于较小类型的地址可能不适合较大的地址。在int
需要32位对齐但double
需要64位对齐的平台上,给定int foo[3]
,编译器可能会任意放置foo
,以便(double*)foo
是一个适合存储double
的地址,或者(double*)(foo+1)
是一个合适的地方。熟悉实现细节的程序员可能能够确定哪个地址有效并利用该地址,但如果foo
double
,那么盲目地认为socket.connect
地址有效的代码可能会失败具有64位对齐要求。