这是来自计算机系统的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
教授解释说,访问fun(2)会操纵double d
的字节。但是,我没有得到:(a)为什么这会操纵双字节开始fun(2)
,(b)每个字节操作如何与fun(2) ➙ 3.1399998664856
,fun(3) ➙ 2.00000061035156
之类的值相关联,直到{{ 1}},和(c)为什么它恰好在fun(6)
达到临界状态?有关我的问题的更多参考,请参阅here幻灯片编号8和9.此外,幻灯片上有一个我不明白的说明图。感谢您是否可以花些时间解释一下。
答案 0 :(得分:0)
幻灯片9上的图表表示对fun
的调用中的本地内存。每行代表4个字节(从右到左列出),内存地址随着下降而减少。如果您要以此格式列出地址0,1,2,...,它将如下所示:
|...
+--+--+--+--+
|11|10| 9| 8|
+--+--+--+--+
| 7| 6| 5| 4|
+--+--+--+--+
| 3| 2| 1| 0|
+--+--+--+--+
幻灯片9上的图表显示了s
(类型为struct_t
的变量)如何在内存中布局。系统使用4字节int
和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个元素),这样就可以正常工作。 / p>
但如果i
更大,那么代码最终会访问内存的其他部分。 s.a[2]
(图中的第2行)指的是s.d
的一部分内存,因此覆盖它会破坏存储在那里的值(s.a[3]
相同)。确切的结果值取决于所使用的浮点格式的内部结构(可能是IEEE 754)。 (我对此并不熟悉,所以我不知道这些位是如何被解释为获得3.1399998664856
的。)
s.a[4]
显然并不重要,因为覆盖它没有任何明显的效果。但覆盖s.a[6]
崩溃,表明我们摧毁了至关重要的东西。这可能是返回地址,即保存的位置告诉fun
完成后跳转到哪里。通过覆盖它,我们使fun
跳转到无效的内存。
要确认这一点(并找出为什么它的索引6
特别破坏了东西),你必须查看编译器生成的代码。没有一般性的答案,因为它取决于所讨论的编译器,优化级别,运行的系统等等。
但是,在C中写出超出本地数组的边界通常会在某些时候破坏返回地址。这是因为编译器通常使用堆栈来实现函数调用和本地(“自动”)存储,因此堆栈包含局部变量和返回地址混合。