读入堆内存我不应该访问吗?

时间:2019-12-20 18:53:23

标签: c heap-memory

在下面的示例中,我分配了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? (为什么我可以访问它?;分配的内存不应该位于我的编译器找到的第一个空闲内存块中,并且不连接到数组的缓冲区吗?)

5 个答案:

答案 0 :(得分:3)

在这种情况下,malloc() \ realloc()的行为是无关紧要的,因为在问题代码中,arr而不是ptr的内容被修改并显示,并且arr不会动态分配或重新分配。因此,动态内存中没有越界访问。对arr[]的越界访问具有未定义的行为。您将踩到未分配给arr的内存。在某些情况下,它将修改相邻的变量,但是在这种情况下,您没有任何变量,因此,由于堆栈最常向下生长,因此您可能正在修改调用函数的局部变量或破坏当前函数的返回地址-这是{{ 1}}甚至可能不会引起任何明显的错误。在其他情况下,它将导致崩溃。

但是,如果您修改了ptr [15]并重新分配,然后将内容显示在main()上,则很可能会看到类似的结果,因为避免了不必要的数据移动,ptr重用了相同的内容减少 分配时的内存块,并简单地减小其大小,将剩余部分返回堆。

将内存返回堆,不会更改其内容或使其不可访问,并且C不会执行任何边界检查,因此,如果您编写代码访问不属于分配的内存,它将使您满意。只是使返回的块可用于分配。

严格来说,这是未定义的行为,因此可能会发生其他行为,但是通常C不会生成代码来执行所需的最低限度的工作,除了在某些情况下可能会支持调试。

>

答案 1 :(得分:3)

您对程序正在执行的操作的描述都是错误的。

  

在下面的示例中,我分配了20个字节的内存以将数组扩展5个整数

不,你不知道。您不能扩展arr。只是不可能。

  

之后,我将 last 元素设置为15

否-因为您没有扩展数组,所以索引9不会表示 last 元素。您只需在数组之外写。

看看这些行:

int *ptr = &arr[0];
ptr = malloc(5 * sizeof(int));

首先,您使ptr指向arr中的第一个元素,但是在您使ptr指向某些动态分配的内存之后,它确实与arr无关。换句话说,第一行可以简单地删除(编译器可能会删除)。

在程序的其余部分,您永远不会使用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

答案 2 :(得分:1)

  

为什么数组的第9个元素仍为15?

“最可能的现实”是操作系统提供了一种方法来分配虚拟页面的区域(不一定是真实内存,应将其视为“伪装/伪造内存”),以及malloc()分配分配的“假装/伪造内存”(并在必要时分配更多的虚拟页面区域,并在方便时分配虚拟页面的区域)。

释放“由malloc()划分的假装/伪造内存”可能只不过是改变了一些用于管理堆的元数据;并且不太可能导致“假装/伪造内存”被释放(甚至不太可能影响实际的实际物理RAM)。

当然,所有这些都取决于软件的编译环境,并且可以完全不同。因此就C而言(在“ C抽象机”级别),所有都是未定义的行为(可能像我描述的那样工作,但可能没有);即使它确实像我所描述的那样工作,也无法保证您不了解的内容(例如,共享库中埋有其他线程)不会分配由malloc划分的“假装/伪造内存” ()”,将其释放,并且不会覆盖您留下的数据。

  

为什么我可以访问它?

部分原因是由于性能原因,C不是托管(或“安全”)语言。通常,不会检查“数组索引超出范围”,也不会检查“释放后使用的数组”。相反,错误会导致未定义的行为(并且可能是严重的安全漏洞)。

答案 3 :(得分:1)

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;`

答案 4 :(得分:0)

简而言之:

  1. 无论您对指针变量中的数组地址的副本进行操作/对其进行复制,都不会对该数组产生影响。
  2. 地址副本不会在以后的malloc分配(并由指针引用)的内存之间建立任何关系。
  3. 分配不会紧接在数组之后。
  4. 使用数组访问副本重新分配指针不起作用。 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;
}

现在让我们讨论您的描述:

  

在下面的示例中,我分配了20个字节的内存....

是的,在ptr = malloc(5 * sizeof(int));行中(假设一个int有4个字节;不保证,但让我们假设它)。

  

...将数组扩展为5个整数。

不。该行不影响数组的属性。特别是没有尺寸。
请注意,使用malloc,行int *ptr = &arr[0];几乎被完全忽略。仅int *ptr;部分保持相关。 malloc确定指针中的值,并且与数组没有任何关系。

  

之后,我将最后一个元素设置为15 ...

否,您无法访问阵列之外的内存。到目前为止,最后一个可用的数组元素是arr[4] Noce代码。从输出(仍然包含“ 15”)判断,您得到了“ lucky”(幸运),该值没有杀死任何内容,并且仍在内存中。但这实际上与数组无关,并且在ptr所引用的已分配内存之外也得到了保证。

  

...,然后将指针重新分配为4个字节(1个整数)。

是的。但是我并没有真正理解你要提出的观点。

  

然后我打印数组的前10个元素...

否,您要打印数组的前5个元素,即所有元素。
然后,您将打印3个值,它们恰好在内存中,您根本不应访问。然后,您在数组外部打印第五个值,您也不应访问该值,但恰好仍然是您先前在其中写入的15个值-也不应该放在首位。

  

...(目前仅由6个组成)...

您可能会从数组中平均获得5个值,而从ptr中获得1个值,但是它们是无关的并且不可能是连续的。

  

...和第9个(我之前设置为15)的打印没有警告或错误。

没有第9个,请参见上文。关于缺少错误,好吧,您并不总是很幸运地被编译器或运行时告知您犯了错误。如果他们能够可靠地将所有错误通知您,生活将会变得更加轻松。

让我们继续您的评论:

  

但是arr [9]不是已定义堆的一部分吗?

不。我不确定“定义的堆”是什么意思,但是它肯定既不是数组的一部分,也不是指针引用的已分配内存。数组之后的分配恰好接近其获得的机会-可能不是精确地为0,但是完全不允许您假设。

  

我已经分配了20个字节,...

在许多当前的机器上,但是也不假定int有四个字节。但是,是的,假设5个整数有20个字节。

  

...因此arr现在应该由10个整数组成,而不是5个。

同样,不行,无论您通过ptr进行什么操作,它都不会对数组产生影响,并且几乎没有机会将ptr引用的内存偶然放在数组之后。似乎您假设将数组的地址复制到指针中会对数组产生影响。事实并非如此。它曾经有一个数组地址的副本,但即使后来也被一行覆盖。即使没有覆盖它,重新分配ptr也会出错(这就是为什么要插入malloc行的原因,对吗?),但仍然对数组或其大小没有任何影响。

  

...但是我认为我没有越过已定义堆的障碍。

同样,假设“定义的堆”是指ptr引用的数组或分配的内存。不能假定两者都包含您访问的arr[9]。所以,是的,您正在访问允许访问的任何内存之外。

  

我应该无法访问arr [9],对吧?

是,不是。是的,您不可以这样做(无论是否将重新分配为1)。 不,您不能指望得到任何有用的错误消息。

让我们看看您对另一个答案的评论:

  

我在学校的老师告诉我,使用大小小于已经分配的内存的realloc()可以释放它,直到它变为n个字节为止。

没错。它已释放,这意味着您不再可以使用它。
理论上也将其释放,以便下一个malloc可以使用它。但是,这并不意味着下一个malloc将会。在任何情况下都不意味着释放内存对该释​​放内存内容的任何更改。它肯定会改变,但是您无法期望甚至依赖它。汤姆·库舍尔斯(Tom Kuschels)对这个评论的回答也是正确的。