为什么要更改数组大小值来操纵十进制字节?
Why changing array size value manipulating bytes of decimal?
这是来自 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
等值相关,直到 fun(6)
,以及 (c) 为什么它恰好在 fun (6)
达到临界状态?有关我的问题的更多参考,请参阅 here 幻灯片编号 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]
崩溃了,这表明我们破坏了一些重要的东西。那可能是 return 地址,即告诉 fun
完成后跳转到哪里的保存位置。通过覆盖它,我们 fun
跳转到无效内存。
要确认这一点(并找出为什么它的索引 6
特别会破坏事物),您必须查看编译器生成的代码。没有通用的答案,因为它取决于所讨论的编译器、优化级别、它运行的系统等。
但是,在 C 语言中越界写入本地数组会在某些时候破坏 return 地址是很常见的。这是因为编译器几乎普遍使用堆栈实现函数调用和本地 ("automatic") 存储,因此堆栈包含本地变量和 return 地址混合。
这是来自 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
等值相关,直到 fun(6)
,以及 (c) 为什么它恰好在 fun (6)
达到临界状态?有关我的问题的更多参考,请参阅 here 幻灯片编号 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]
崩溃了,这表明我们破坏了一些重要的东西。那可能是 return 地址,即告诉 fun
完成后跳转到哪里的保存位置。通过覆盖它,我们 fun
跳转到无效内存。
要确认这一点(并找出为什么它的索引 6
特别会破坏事物),您必须查看编译器生成的代码。没有通用的答案,因为它取决于所讨论的编译器、优化级别、它运行的系统等。
但是,在 C 语言中越界写入本地数组会在某些时候破坏 return 地址是很常见的。这是因为编译器几乎普遍使用堆栈实现函数调用和本地 ("automatic") 存储,因此堆栈包含本地变量和 return 地址混合。