例如在下面的代码中:
#include <stdio.h>
int main()
{
char i = 0;
char j = 0;
char *p = &i;
void *q = p;
int *pp = q;
printf("%d %dn", i, j);
*pp = -1;
printf("%d %dn", i, j);
return 0;
}
如果我使用 gcc 版本 8.1.0x64(Ubuntu 8.1.0-5ubuntu1~16.04(编译,输出是:
0 0-1
-1
现在,我使用 cast 到 int *
#include <stdio.h>
int main()
{
char i = 0;
char j = 0;
char *p = &i;
void *q = p;
int *pp = (int *)q;
printf("%d %dn", i, j);
*pp = -1;
printf("%d %dn", i, j);
return 0;
}
结果与上一个相同。
使用 clang-6.0 x64 时,输出为:0
0-1 0
代码显然是缓冲区溢出吗?
我希望我已经解释清楚了。
你实际上有两个问题:
首先是你打破了严格的别名并具有未定义的行为。
第二个问题是,在大多数现代平台上,int
的大小为四个字节,指针pp
仅指向单个字节。因此,您向*pp
所做的赋值将超出界限,并导致未定义的行为。
强制转换或不强制转换,您本质上是尝试使用不兼容的类型,导致违反严格的别名规则。
根据C11
第6.5章,
对象的存储值只能由具有以下 以下类型:88(
— 与对象的有效类型兼容的类型,
— 与对象的有效类型兼容的类型的限定版本,
— 一种类型,它是与 的有效类型对应的有符号或无符号类型 对象
— 一种类型,它是与 的限定版本相对应的有符号或无符号类型 对象的有效类型,
— 包含上述类型之一的聚合或联合类型 成员(递归地包括子聚合或包含的联合的成员(,或
— 字符类型。
但是,您尝试通过int
类型访问为char
分配的内存。这违反了严格的别名。
也就是说,该标准保证char
的大小为 1 字节,int
的大小为>=
char
。因此,访问将超出界限并导致未定义的行为。