读入我不应该访问的堆内存?
Reading into heap memory I shouldn't have access to?
在下面的示例中,我分配了 20 个字节的内存来将数组扩展 5 个整数。之后,我将最后一个元素设置为 15,然后将指针重新分配给 4 个字节(1 个整数)。然后我打印数组的前 10 个元素(此时它只包含 6 个)和第 9 个(我之前设置为 15)被打印而没有警告或错误。
代码:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int arr[5] = {0};
int *ptr = &arr[0];
ptr = malloc(5 * sizeof(int));
arr[9] = 15;
ptr = realloc(ptr, 1 * sizeof(int));
for (int i = 0; i < 10; ++i)
{
printf("%d\n", arr[i]);
}
free(ptr);
return 0;
}
编译后的结果运行:
0
0
0
0
0
32766
681279744
-1123562100
-1261131712
15
我的问题如下:为什么数组的第9个元素还是15? (为什么我能够访问它?;分配的内存不应该位于我的编译器找到的第一个空闲内存块并且不连接到数组的缓冲区吗?)
Why is the 9th element of the array still 15?
"most likely reality" 是 OS 提供了一种方法来分配 area/s 虚拟页面(不一定是真实内存,应该考虑 "pretend/fake memory") ,并且 malloc()
瓜分分配的 "pretend/fake memory"(并分配更多 area/s 必要的虚拟页面 if/when,并释放虚拟页面区域 if/when 方便)。
释放 "pretend/fake memory that was carved up by malloc()" 可能只是改变一些用于管理堆的元数据;并且不太可能导致 "pretend/fake memory" 被释放(更不可能影响实际的实际物理 RAM)。
当然,所有这些都取决于软件编译的环境,而且可能完全不同;所以就 C 而言(在 "C abstract machine" 级别),它都是未定义的行为(可能像我描述的那样工作,但可能不会);即使它确实像我描述的那样工作,也不能保证你不知道的东西(例如埋在共享库中的不同线程)不会在你释放它后立即分配相同的 "pretend/fake memory that was carved up by malloc()" 并且不会覆盖您留下的数据。
why am I able to access it?
部分原因是出于性能原因,C 不是托管(或 "safe")语言;通常不检查 "array index out of bounds",也不检查 "used after it was freed"。相反,错误会导致未定义的行为(并且可能是严重的安全漏洞)。
int arr[5] = {0}; // these 5 integers are kept on the stack of the function
int *ptr = &arr[0]; // the pointer ptr is also on the stack and points to the address of arr[0]
ptr = malloc(5 * sizeof(int)); // malloc creates heap of size 5 * sizeof int and returns a ptr which points to it
// the ptr now points to the heap and not to the arr[] any more.
arr[9] = 15; //the array is of length 5 and arr[9] is out of the border of maximum arr[4] !
ptr = realloc(ptr, 1 * sizeof(int)); //does nothing here, since the allocated size is already larger than 1 - but it depends on implementation if the rest of 4 x integer will be free'd.
for (int i = 0; i < 10; ++i) // undefined behavior!
{
printf("%d\n", arr[i]);
}
free(ptr);
return 0;`
简而言之:
- 无论你做什么with/to指针变量中数组地址的副本,它对数组没有影响。
- 地址副本在数组和由稍后的 malloc 分配(并由指针引用)的内存之间没有任何关系。
- 分配不会在数组之后。
- 具有数组访问副本的指针的重新分配不起作用。 Realloc 仅适用于携带成功 malloc 结果的指针。 (这可能就是您插入 malloc 的原因。)
更长:
以下是关于您的代码的一些重要事实,请参阅我的评论:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int arr[5] = {0}; /* size 5 ints, nothing will change that */
int *ptr = &arr[0]; /* this value will be ignored in the next line */
ptr = malloc(5 * sizeof(int)); /* overwrite value from previous line */
arr[9] = 15; /* arr still only has size 5 and this access beyond */
ptr = realloc(ptr, 1 * sizeof(int)); /* irrelevant, no influence on arr */
for (int i = 0; i < 10; ++i) /* 10 is larger than 5 ... */
{
printf("%d\n", arr[i]); /* starting with 5, this access beyond several times */
}
free(ptr);
return 0;
}
现在让我们讨论一下您的描述:
In the example below I have allocated 20 bytes of memory ....
正确,在 ptr = malloc(5 * sizeof(int));
行中(假设 int 有 4 个字节;不能保证,但让我们假设)。
... to extend an array by 5 integers.
没有。数组的任何属性都不受此行的影响。尤其是尺码。
请注意,对于 malloc,行 int *ptr = &arr[0];
几乎完全被忽略。只有 int *ptr;
部分仍然相关。 malloc 确定指针中的值,与数组没有任何关系。
After that I have set the last element to 15 ...
不,你访问的内存超出了数组。最后一个可用的数组元素是 arr[4]
到目前为止,noce 代码已经改变了它。从仍然包含“15”的输出来看,你得到了 "lucky",该值没有杀死任何东西并且仍然在内存中。但它实际上与数组无关,并且实际上也保证在 ptr
.
引用的分配内存之外
... and then reallocated the pointer to 4 bytes (1 integer).
没错。但是我真的不明白你想表达的意思。
Then I print the first 10 elements of the array ...
不,您打印数组的前 5 个元素,即所有元素。
然后你打印 3 个恰好在你根本不应该访问的内存中的值。之后你在数组外打印第五个值,你也不应该访问它,但它恰好仍然是你之前在那里写的 15 - 而且一开始也不应该有。
... (it only consists of 6 at this point) ...
你可能表示数组中的 5 个值和 ptr
中的 1 个值,但它们不相关且不太可能连续。
... and the 9th (which I've previously set to 15) is printed without warnings or errors.
没有第 9 个,见上文。关于没有错误,你并不总是幸运地被编译器或运行时告知你犯了一个错误。如果他们能够可靠地通知您所有的错误,生活会容易得多。
让我们继续您的评论:
But isn't arr[9] part of the defined heap?
没有。我不确定 "the defined heap" 是什么意思,但它肯定既不是数组的一部分,也不是指针引用的已分配内存。在数组尽可能接近于零之后立即进行分配的可能性 - 可能不完全为 0,但您根本不允许这样假设。
I have allocated 20 bytes, ...
在许多当前的机器上,但是假设一个 int 有四个字节也不是一个安全的假设。但是,是的,假设 5 个整数有 20 个字节。
... so arr should now consist of 10 integers, instead of 5.
同样不,无论您通过 ptr
做什么,它都不会影响数组,而且 ptr 引用的内存几乎不可能恰好位于数组之后。您似乎假设将数组的地址复制到指针中会对数组产生影响。事实并非如此。它曾经有一个数组地址的副本,但即使是在一行之后也被覆盖了。即使它没有被覆盖,重新分配 ptr 也会出错(这就是你插入 malloc 行的原因,不是吗?)但仍然不会对数组或其大小产生任何影响。
... But I don't think I am passing the barrier of the defined heap.
同样,让我们假设 "the defined heap" 是指数组或 ptr 引用的已分配内存。两者都不能假定包含您访问的 arr[9]
。所以是的,您正在访问您被允许访问的任何内存之外。
I shouldn't be able to access arr[9], right?
是也不是。是的,你不能这样做(有或没有 realloc to 1)。
不,您不能指望得到任何有用的错误消息。
让我们看看您对另一个答案的评论:
My teacher in school told me that using realloc() with a smaller size than the already allocated memory frees it until it becomes n bytes.
没错。它被释放了,这意味着你不能再使用它了。
它在理论上也被释放,以便下一个 malloc 可以使用它。然而,这并不意味着下一个 malloc 会。在任何情况下都不会暗示释放内存对已释放内存内容的任何更改。它肯定会改变,但你不能指望它,甚至不能依赖它。 Tom Kuschels 对这条评论的回答也是正确的。
malloc()
\ realloc()
的行为在这种情况下无关紧要,因为在问题的代码中修改和显示了 arr
而不是 ptr
的内容, 并且 arr
不是动态分配或重新分配的。所以在动态内存中不存在越界访问。对 arr[]
的越界访问具有未定义的行为。您将踩踏未分配给 arr
的内存。在某些情况下,这会修改相邻变量,但在这种情况下,您有 none,因此由于堆栈通常向下增长,您可能正在修改调用函数的局部变量或破坏 return 地址当前函数 - 这是 main()
即使这样也不会导致任何明显的错误。在其他情况下,它会导致崩溃。
但是,如果您修改了 ptr[15] 并重新分配,然后显示 ptr
处的内容,您很可能会看到类似的结果,因为避免了不必要的数据移动,realloc()
重用当分配减少时,相同的内存块只是减少其大小,return将剩余部分放入堆中。
将内存返回堆,不会更改其内容或使其不可访问,并且 C 不会执行任何边界检查,因此如果您编写代码来访问不属于分配的一部分的内存,它会让您。它只是使 returned 块可用于分配。
严格来说,它是未定义的行为,所以其他行为也是可能的,但通常 C 不会生成代码来做除最低要求之外的任何事情——除了在某些情况下可能支持调试。
你对程序正在做什么的描述都是错误的。
In the example below I have allocated 20 bytes of memory to extend an array by 5 integers
不,你不知道。您不能扩展 arr
。简直不可能。
After that I have set the last element to 15
否 - 因为您没有扩展数组,所以索引 9 not 表示 last 元素。你只需在数组外写。
看看这些行:
int *ptr = &arr[0];
ptr = malloc(5 * sizeof(int));
首先你让 ptr
指向 arr
中的第一个元素,但在你让 ptr
指向一些与 [=12 完全无关的动态分配内存之后=].换句话说 - 第一行可以简单地删除(编译器可能会)。
在程序的其余部分中,您永远不会使用 ptr
进行任何操作。换句话说 - 您可以使用 ptr
简单地删除所有代码。没有效果。
所以程序可以简单地是:
int main()
{
int arr[5] = {0};
arr[9] = 15;
for (int i = 0; i < 10; ++i)
{
printf("%d\n", arr[i]);
}
return 0;
}
并且它具有未定义的行为,因为您访问 arr
越界。
在下面的示例中,我分配了 20 个字节的内存来将数组扩展 5 个整数。之后,我将最后一个元素设置为 15,然后将指针重新分配给 4 个字节(1 个整数)。然后我打印数组的前 10 个元素(此时它只包含 6 个)和第 9 个(我之前设置为 15)被打印而没有警告或错误。
代码:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int arr[5] = {0};
int *ptr = &arr[0];
ptr = malloc(5 * sizeof(int));
arr[9] = 15;
ptr = realloc(ptr, 1 * sizeof(int));
for (int i = 0; i < 10; ++i)
{
printf("%d\n", arr[i]);
}
free(ptr);
return 0;
}
编译后的结果运行:
0
0
0
0
0
32766
681279744
-1123562100
-1261131712
15
我的问题如下:为什么数组的第9个元素还是15? (为什么我能够访问它?;分配的内存不应该位于我的编译器找到的第一个空闲内存块并且不连接到数组的缓冲区吗?)
Why is the 9th element of the array still 15?
"most likely reality" 是 OS 提供了一种方法来分配 area/s 虚拟页面(不一定是真实内存,应该考虑 "pretend/fake memory") ,并且 malloc()
瓜分分配的 "pretend/fake memory"(并分配更多 area/s 必要的虚拟页面 if/when,并释放虚拟页面区域 if/when 方便)。
释放 "pretend/fake memory that was carved up by malloc()" 可能只是改变一些用于管理堆的元数据;并且不太可能导致 "pretend/fake memory" 被释放(更不可能影响实际的实际物理 RAM)。
当然,所有这些都取决于软件编译的环境,而且可能完全不同;所以就 C 而言(在 "C abstract machine" 级别),它都是未定义的行为(可能像我描述的那样工作,但可能不会);即使它确实像我描述的那样工作,也不能保证你不知道的东西(例如埋在共享库中的不同线程)不会在你释放它后立即分配相同的 "pretend/fake memory that was carved up by malloc()" 并且不会覆盖您留下的数据。
why am I able to access it?
部分原因是出于性能原因,C 不是托管(或 "safe")语言;通常不检查 "array index out of bounds",也不检查 "used after it was freed"。相反,错误会导致未定义的行为(并且可能是严重的安全漏洞)。
int arr[5] = {0}; // these 5 integers are kept on the stack of the function
int *ptr = &arr[0]; // the pointer ptr is also on the stack and points to the address of arr[0]
ptr = malloc(5 * sizeof(int)); // malloc creates heap of size 5 * sizeof int and returns a ptr which points to it
// the ptr now points to the heap and not to the arr[] any more.
arr[9] = 15; //the array is of length 5 and arr[9] is out of the border of maximum arr[4] !
ptr = realloc(ptr, 1 * sizeof(int)); //does nothing here, since the allocated size is already larger than 1 - but it depends on implementation if the rest of 4 x integer will be free'd.
for (int i = 0; i < 10; ++i) // undefined behavior!
{
printf("%d\n", arr[i]);
}
free(ptr);
return 0;`
简而言之:
- 无论你做什么with/to指针变量中数组地址的副本,它对数组没有影响。
- 地址副本在数组和由稍后的 malloc 分配(并由指针引用)的内存之间没有任何关系。
- 分配不会在数组之后。
- 具有数组访问副本的指针的重新分配不起作用。 Realloc 仅适用于携带成功 malloc 结果的指针。 (这可能就是您插入 malloc 的原因。)
更长:
以下是关于您的代码的一些重要事实,请参阅我的评论:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int arr[5] = {0}; /* size 5 ints, nothing will change that */
int *ptr = &arr[0]; /* this value will be ignored in the next line */
ptr = malloc(5 * sizeof(int)); /* overwrite value from previous line */
arr[9] = 15; /* arr still only has size 5 and this access beyond */
ptr = realloc(ptr, 1 * sizeof(int)); /* irrelevant, no influence on arr */
for (int i = 0; i < 10; ++i) /* 10 is larger than 5 ... */
{
printf("%d\n", arr[i]); /* starting with 5, this access beyond several times */
}
free(ptr);
return 0;
}
现在让我们讨论一下您的描述:
In the example below I have allocated 20 bytes of memory ....
正确,在 ptr = malloc(5 * sizeof(int));
行中(假设 int 有 4 个字节;不能保证,但让我们假设)。
... to extend an array by 5 integers.
没有。数组的任何属性都不受此行的影响。尤其是尺码。
请注意,对于 malloc,行 int *ptr = &arr[0];
几乎完全被忽略。只有 int *ptr;
部分仍然相关。 malloc 确定指针中的值,与数组没有任何关系。
After that I have set the last element to 15 ...
不,你访问的内存超出了数组。最后一个可用的数组元素是 arr[4]
到目前为止,noce 代码已经改变了它。从仍然包含“15”的输出来看,你得到了 "lucky",该值没有杀死任何东西并且仍然在内存中。但它实际上与数组无关,并且实际上也保证在 ptr
.
... and then reallocated the pointer to 4 bytes (1 integer).
没错。但是我真的不明白你想表达的意思。
Then I print the first 10 elements of the array ...
不,您打印数组的前 5 个元素,即所有元素。
然后你打印 3 个恰好在你根本不应该访问的内存中的值。之后你在数组外打印第五个值,你也不应该访问它,但它恰好仍然是你之前在那里写的 15 - 而且一开始也不应该有。
... (it only consists of 6 at this point) ...
你可能表示数组中的 5 个值和 ptr
中的 1 个值,但它们不相关且不太可能连续。
... and the 9th (which I've previously set to 15) is printed without warnings or errors.
没有第 9 个,见上文。关于没有错误,你并不总是幸运地被编译器或运行时告知你犯了一个错误。如果他们能够可靠地通知您所有的错误,生活会容易得多。
让我们继续您的评论:
But isn't arr[9] part of the defined heap?
没有。我不确定 "the defined heap" 是什么意思,但它肯定既不是数组的一部分,也不是指针引用的已分配内存。在数组尽可能接近于零之后立即进行分配的可能性 - 可能不完全为 0,但您根本不允许这样假设。
I have allocated 20 bytes, ...
在许多当前的机器上,但是假设一个 int 有四个字节也不是一个安全的假设。但是,是的,假设 5 个整数有 20 个字节。
... so arr should now consist of 10 integers, instead of 5.
同样不,无论您通过 ptr
做什么,它都不会影响数组,而且 ptr 引用的内存几乎不可能恰好位于数组之后。您似乎假设将数组的地址复制到指针中会对数组产生影响。事实并非如此。它曾经有一个数组地址的副本,但即使是在一行之后也被覆盖了。即使它没有被覆盖,重新分配 ptr 也会出错(这就是你插入 malloc 行的原因,不是吗?)但仍然不会对数组或其大小产生任何影响。
... But I don't think I am passing the barrier of the defined heap.
同样,让我们假设 "the defined heap" 是指数组或 ptr 引用的已分配内存。两者都不能假定包含您访问的 arr[9]
。所以是的,您正在访问您被允许访问的任何内存之外。
I shouldn't be able to access arr[9], right?
是也不是。是的,你不能这样做(有或没有 realloc to 1)。 不,您不能指望得到任何有用的错误消息。
让我们看看您对另一个答案的评论:
My teacher in school told me that using realloc() with a smaller size than the already allocated memory frees it until it becomes n bytes.
没错。它被释放了,这意味着你不能再使用它了。
它在理论上也被释放,以便下一个 malloc 可以使用它。然而,这并不意味着下一个 malloc 会。在任何情况下都不会暗示释放内存对已释放内存内容的任何更改。它肯定会改变,但你不能指望它,甚至不能依赖它。 Tom Kuschels 对这条评论的回答也是正确的。
malloc()
\ realloc()
的行为在这种情况下无关紧要,因为在问题的代码中修改和显示了 arr
而不是 ptr
的内容, 并且 arr
不是动态分配或重新分配的。所以在动态内存中不存在越界访问。对 arr[]
的越界访问具有未定义的行为。您将踩踏未分配给 arr
的内存。在某些情况下,这会修改相邻变量,但在这种情况下,您有 none,因此由于堆栈通常向下增长,您可能正在修改调用函数的局部变量或破坏 return 地址当前函数 - 这是 main()
即使这样也不会导致任何明显的错误。在其他情况下,它会导致崩溃。
但是,如果您修改了 ptr[15] 并重新分配,然后显示 ptr
处的内容,您很可能会看到类似的结果,因为避免了不必要的数据移动,realloc()
重用当分配减少时,相同的内存块只是减少其大小,return将剩余部分放入堆中。
将内存返回堆,不会更改其内容或使其不可访问,并且 C 不会执行任何边界检查,因此如果您编写代码来访问不属于分配的一部分的内存,它会让您。它只是使 returned 块可用于分配。
严格来说,它是未定义的行为,所以其他行为也是可能的,但通常 C 不会生成代码来做除最低要求之外的任何事情——除了在某些情况下可能支持调试。
你对程序正在做什么的描述都是错误的。
In the example below I have allocated 20 bytes of memory to extend an array by 5 integers
不,你不知道。您不能扩展 arr
。简直不可能。
After that I have set the last element to 15
否 - 因为您没有扩展数组,所以索引 9 not 表示 last 元素。你只需在数组外写。
看看这些行:
int *ptr = &arr[0];
ptr = malloc(5 * sizeof(int));
首先你让 ptr
指向 arr
中的第一个元素,但在你让 ptr
指向一些与 [=12 完全无关的动态分配内存之后=].换句话说 - 第一行可以简单地删除(编译器可能会)。
在程序的其余部分中,您永远不会使用 ptr
进行任何操作。换句话说 - 您可以使用 ptr
简单地删除所有代码。没有效果。
所以程序可以简单地是:
int main()
{
int arr[5] = {0};
arr[9] = 15;
for (int i = 0; i < 10; ++i)
{
printf("%d\n", arr[i]);
}
return 0;
}
并且它具有未定义的行为,因为您访问 arr
越界。