我读到
char *
- 以及它们的有符号和无符号对应物 - 可以在不违反严格别名规则的情况下为任何类型别名。但是,让 char *
指向 int
变量并将该 char *
转换为 double *
会违反规则,因为底层对象的类型为 int
。但如果记忆来自malloc
呢?例如:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
void *buffer = malloc(32);
unsigned char *ptr = buffer;
*ptr = 10;
*((double *)(ptr + 1)) = 3.14;
*((double *)(ptr + 9)) = 2.718;
printf("*ptr: %d\n", *ptr);
printf("*(ptr + 1): %lf\n", *((double *)(ptr + 1)));
printf("*(ptr + 9): %lf\n", *((double *)(ptr + 9)));
return 0;
}
这将打印以下内容:
*ptr: 10
*(ptr + 1): 3.140000
*(ptr + 9): 2.718000
如果我错了,请纠正我,但据我所知,malloc 的内存是无类型的,可以存储任何数据,不像
int
数组只能存储 int
类型的数据。
我没有收到来自 gcc 的任何警告,但显然当你违反严格的别名规则时,它警告你的方式不太可靠。那么我的例子会破坏它们吗?
由于未对齐的指针导致无效访问,您的代码具有未定义的行为。
malloc
返回的指针的内存地址被指定为最大对齐,以便该地址可以被不同类型使用而不会出现问题。这就是为什么这样做是有效的
double* ptr = malloc(8*sizeof(double));
另一方面,不能保证
ptr+1
是 double
正确对齐的指针。事实上,只要我们知道 ptr
本身已正确对齐,它很可能没有对齐。
下一个问题是,如果我们更改代码以使指针正确对齐,您的问题的答案是什么。
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;
}
对于上述修改后的代码,假设没有对分配的缓冲区进行出界访问,则行为是明确定义的。这个从cppreference中对应的描述可以看出:
如果对象是由分配函数创建的(包括 realloc),它没有声明类型。这样的对象获得了有效的 输入如下:
- 第一次通过类型为 other 的左值写入该对象 不是字符类型,此时该左值的类型变为 该对象对于该写入和所有后续读取的有效类型。
- memcpy 或 memmove 将另一个对象复制到该对象中,或者复制 另一个对象作为字符类型数组放入该对象中,位于 源对象的有效类型(如果有的话) 成为该对象对于该写入和所有的有效类型 后续阅读。
- 对未声明的对象的任何其他访问 类型,有效类型是用于的左值的类型 访问。
在标记为
(*)
的行,我们点击了上面引号中的第一个项目符号,对于现在位于double
地址的ptr+_Alignof(double)
对象,有效类型固定为double。
即使指针正确对齐,
(*)
之后的以下代码也会违反严格别名规则。
int i = *((int *)(ptr+_Alignof(double));