这是来自计算机系统的CMU课程。在以下示例中:
typedef struct {
int a[2];
double d; } struct_t;
double fun(int i) {
volatile struct_t s;
s.d = 3.14;
s.a[i] = 1073741824; /* Possibly out of bounds */
return s.d; }
fun(0) ➙ 3.14
fun(1) ➙ 3.14
fun(2) ➙ 3.1399998664856
fun(3) ➙ 2.00000061035156
fun(4) ➙ 3.14
fun(6) ➙ Segmentation fault
教授解释说,访问乐趣(2)操纵double d
的字节。但是,我没有得到:(a)为什么要操纵双重字节启动 fun(2)
,(b)每个字节如何处理每个字节都与诸如 fun(2) ➙ 3.1399998664856
, fun(3) ➙ 2.00000061035156
等的值相关,直到 fun(6)
,以及(c)为什么它准确地达到关键状态fun (6)
?有关我的问题的更多参考,请参见此处的第8和9号幻灯片。此外,关于幻灯片的说明图我不理解。感谢您是否可以花一些时间来解释。
幻灯片9上的图表示fun
调用中的本地内存。每一行代表4个字节(从右至左列出),并且随着您下降时的内存地址减小。如果要列出地址0、1、2,...以这种格式,它看起来像这样:
|...
+--+--+--+--+
|11|10| 9| 8|
+--+--+--+--+
| 7| 6| 5| 4|
+--+--+--+--+
| 3| 2| 1| 0|
+--+--+--+--+
幻灯片9上的图显示了如何在内存中布置s
(类型struct_t
的变量)。该系统使用4字节int
s和8字节double
s。因此,s.a[0]
占据4个字节(图中的第0行),s.a[1]
另一个4(第1行)和s.d
8字节(第2和3行)。
函数访问s.a[i]
。编译器将其变成代码,该代码采用s.a
的起始地址,并将i*4
字节添加到其到达所选元素。在图中,这对应于从第0行开始,然后进行i
行。只要i
实际上是数组中的有效索引(示例:0
或1
,因为a
只有2个元素)。
但是,如果i
更大,则代码最终将访问内存的其他部分。s.a[2]
(图中的第2行)是指s.d
的一部分的内存,因此覆盖它会损坏存储在此处的值(s.a[3]
相同)。确切的结果值取决于所使用的浮点格式的内部(可能是IEEE 754)。(我对此不熟悉,所以我不知道这些部分是如何解释为获得3.1399998664856
的。)
s.a[4]
显然并不重要,因为覆盖它没有任何明显的效果。但是覆盖s.a[6]
崩溃了,表明我们摧毁了重要的东西。那可能是返回地址,即告诉fun
的保存位置,该地址在完成后跳到哪里。通过覆盖它,我们使fun
跳到无效的内存。
要确认这一点(并找出为什么要特别破坏事物的索引6
),您必须查看编译器生成的代码。没有一般答案,因为它取决于所讨论的编译器,优化级别,其运行的系统等等。
但是,很常见的是,在C中向本地数组的界限写入界限会在某个时候破坏返回地址。这是因为编译器几乎普遍实现函数调用和局部("自动")通过堆栈存储,因此包含本地变量并将返回地址交互。