让我们把它分解一下,看看我们可以对每一行代码说些什么,一个接一个:
int main (void){
char buf[4096 + 1];
您声明了一个char[4097],它通常通过在输入main 时调整堆栈指针来分配到堆栈上。数组的内容是不确定的。
printf("(unsigned) buf %d\n", (unsigned) buf);
数组类型的表达式,除非它是地址(&)、sizeof 或_Alignof 运算符的操作数,或者是用于初始化字符数组的字符串字面量,否则将转换为指针到数组的第一个元素(6.3.2.1 p. 3),因此该行相当于
printf("(unsigned) buf %d\n", (unsigned) &buf[0]);
获取buf 中第一个字节的地址,将其转换为unsigned 并将结果数字打印为有符号整数。请注意,如果生成的 unsigned 值不能表示为 int,则行为未定义。指针 &buf[0] 到 unsigned 的转换是实现定义的,并且可能会调用未定义的行为 (6.3.2.3 p. 6)。通常,指针值的sizeof(unsigned) 字节被解释为无符号整数(通常包括指针的大小不小于unsigned 的大小)。在你的情况下,结果是
(unsigned) buf 2268303
已打印。下一个
doing();
那么让我们看看doing:
void doing(){
char buf[4096 + 1];
声明了另一个char[4097],通常在进入doing 时通过调整堆栈指针来分配它。这个数组的内容也是不确定的。
printf("buf %d\n", buf);
同样,char[4097] 类型的表达式buf 被转换为char*,即&buf[0],然后传递给printf,它需要int 参数。类型不匹配会调用未定义的行为,通常sizeof(int) 指针值的字节被解释为有符号整数。结果是输出
buf 2264159
这强烈暗示doing 中的buf 被分配到距离main 4144 字节的位置,并且堆栈向下增长。
printf("buf %f\n", buf);
我们再次进行了数组到指针的转换,现在printf 需要一个double 参数,但得到一个char*。更多未定义的行为,表现就是
buf 0.000000
被打印出来。一般无法回答这个问题(毕竟,这是未定义的行为),在 64 位系统上,常见行为是指针或整数类型的 printf 参数在通用寄存器中传递,浮点参数在浮点点寄存器,以便printf 会读取浮点寄存器 - 它恰好包含一个 0 值。
printf("buf %d\n", (unsigned) buf);
这一行与main对应的行语义相同,但由于是不同的数组buf,所以转换得到的(无符号)整数是不同的。
buf 2264159
它与doing 中的第一个printf 打印相同,这并不奇怪(但不能保证,因为涉及未定义的行为)。
printf("buf %s\n", buf+2);
buf 被转换为&buf[0],然后加上 2,得到&buf[2]。这被传递给printf,由于%s 转换需要一个指向char 的0 终止数组的指针作为参数。这是整个程序中唯一的printf 调用,其中printf 的第二个参数的类型与转换说明符所期望的类型完全匹配。但是buf的内容是不确定的,所以如果数组中没有0字节,就会导致printf读取无效。但是,显然buf[2] 是 0,所以只是
buf
已打印。
printf("buf %d\n", buf+2);
buf + 2 再次计算为&buf[2],并且在printf 期望int 的地方通过。类型不匹配会调用未定义的行为,但输出
buf 2264161
表明没有发生任何恶意事件,并且由于&buf[2] 比&buf[0] 落后两个字节,因此打印的数字比doing 的第一个printf 中打印的数字大两个。
}
返回main:
printf("(unsigned) buf %d\n", (unsigned) buf);
该行与main 中的第一个printf 调用相同,因此具有与上面讨论的相同的语义,
(unsigned) buf 2268303
并产生相同的输出。
return 0;
}