由于未对齐的指针进行无效访问,您的代码具有未定义的行为.
由malloc
返回的指针的内存地址被指定为具有最大对齐,以便该地址可以由不同类型使用而不会出现问题.这就是为什么这样做是合理的
double* ptr = malloc(42*sizeof(double));
另一方面,不能保证ptr+1
是double
的正确对齐的指针.事实上,它很可能没有对齐,只要我们知道ptr
本身是正确对齐的,正如您可以看到报告的未定义行为是here.
下一个问题是,如果我们改变代码,使指针正确对齐,你的问题的答案是什么.
int main(void)
{
void *buffer = malloc(32);
unsigned char *ptr = buffer;
*ptr = 10;
*((double *)(ptr + _Alignof(double))) = 3.14; //(*)
printf("*ptr: %d\n", *ptr);
printf("*(ptr + _Alignof(double)): %lf\n", *((double *)(ptr + _Alignof(double))));
return 0;
}
对于上面修改的代码,行为是定义良好的,假设对分配的缓冲区没有越界访问.这可以从以下引用的最新标准草案(6.5.6 in https://open-std.org/JTC1/SC22/WG14/www/docs/n3096.pdf)中看出:
访问其存储值的对象的有效类型为
对象的声明类型(如果有)98.如果将值存储到
没有声明类型的对象,而左值具有
不是非原子字符类型,则左值的类型
成为该访问的对象的有效类型
不修改存储值的后续访问.如果值为
使用Memcpy或MemMove复制到没有声明类型的对象中,
或被复制为字符类型的数组,则
该访问和后续访问的已修改对象
不修改的值是从其开始的对象的有效类型
如果有值,则会复制该值.对于所有其他对
对象没有声明类型,则该对象的有效类型为
只是用于访问的左值的类型.
其中脚注98说
分配的对象没有声明类型.
在标记为(*)
的行,我们点击上面引号中的第二句,对于现在位于地址ptr+_Alignof(double)
的double
对象,有效类型固定为DOUBLE.
即使指针正确对齐,(*)
之后的代码也会违反严格别名规则.
int i = *((int *)(ptr+_Alignof(double)));
根据上面引用的最后一句话,允许通过不同类型的左值向内存写入,并且通过明确提到"不修改所存储的值的后续访问"来非常清楚地表明其意图.事实上,这是内存池如何工作的基础,它通过回收其他对象先前使用的内存,并将它们重新分配给具有新类型的对象.因此,以下内容是有效的,并更新了关联内存的有效类型.(假设指针正确对齐)
*((int *)(ptr+_Alignof(double))) = 42;
然而,正如@JohnBollinger在 comments 中指出的那样,许多常见的C实现在通过类似于上面的写操作进行有效的类型更新方面存在缺陷.对于这样的实现,它们可能会执行不正确的类型别名分析并不正确地优化代码.因此,尽管C标准声明上述内容是有效的,但不直接这样做可能更明智.内存池实现的情况是不同的,因为更新有效类型的代码通常位于不同的TU中,并且这种有缺陷的实现的不正确的基于类型的别名分析不会造成太大的伤害.