让我们把它分解一下,看看我们可以对每一行代码说些什么,一个接一个:
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);
buf
同样, type的表达式char[4097]
被转换为 a char*
,即&buf[0]
,并且传递给printf
which 需要一个int
参数。类型不匹配会调用未定义的行为,通常sizeof(int)
指针值的字节被解释为有符号整数。结果是输出
buf 2264159
这强烈暗示buf
indoing
已分配 4144 字节远离main
's 并且堆栈向下增长。
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
它打印与第一个printf
in相同doing
,这并不奇怪(但不能保证,因为涉及未定义的行为)。
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
's first中打印的数字大两个printf
。
}
返回main
:
printf("(unsigned) buf %d\n", (unsigned) buf);
该行与第一个printf
调用相同main
,因此它具有相同的语义,如上所述,
(unsigned) buf 2268303
并产生相同的输出。
return 0;
}