试图学习如何使用malloc/calloc/free并弄乱初始化。代码的第一个版本有效,第二个版本给了我堆的损坏。但是,它们都正确打印了数组。当我在初始化指向数组中第一个元素地址的指针后尝试"free(pData)"时,该问题发生在第二个版本中。
谁能解释这种行为?有没有办法清除这样的初始化指针?
工作版本:
#include <iostream>
using namespace std;
int main(){
double * pData;
pData = (double*) calloc (4,sizeof(double));
if (pData==NULL) exit (1);
double uc[] = {10.0, 20.0, 30.0, 40.0};
pData[0] = uc[0];
pData[1] = uc[1];
pData[2] = uc[2];
pData[3] = uc[3];
printf ("You have entered: ");
for (int n=0;n<4;n++) cout << pData[n];
free (pData);
cin.get();
return 0;
}
堆损坏错误:
#include <iostream>
using namespace std;
int main(){
double * pData;
pData = (double*) calloc (4,sizeof(double));
if (pData==NULL) exit (1);
double uc[] = {10.0, 20.0, 30.0, 40.0};
pData = &uc[0];
printf ("You have entered: ");
for (int n=0;n<4;n++) cout << pData[n];
free (pData);
cin.get();
return 0;
}
pData = (double*) calloc (4,sizeof(double));
if (pData==NULL) exit (1);
double uc[] = {10.0, 20.0, 30.0, 40.0};
pData = &uc[0];
printf ("You have entered: ");
for (int n=0;n<4;n++) cout << pData[n];
free (pData);
cin.get();
首先,您将pData
分配给calloc
提供给您的指针,这是您唯一可以调用free
的指针。
然后,重新分配pData
以指向具有自动生存期的变量的地址,在此过程中丢失calloc
给您的地址,然后尝试free
该地址。这不是它的工作原理,您必须保留calloc
提供给您的地址的副本才能free
它:
pData = (double*) calloc (4,sizeof(double));
if (pData==NULL) exit (1);
double uc[] = {10.0, 20.0, 30.0, 40.0};
double* originalData = pData;
pData = &uc[0];
printf ("You have entered: ");
for (int n=0;n<4;n++) cout << pData[n];
free (originalData);
cin.get();
旁注:在C++中使用new
/delete
而不是malloc
/calloc
/free
。
旁注:如果真的有必要,根本不要使用动态分配或使用智能指针。
pData 是一个指针。 这意味着它是一个变量(它自己的内存片段),可以保存另一块内存的地址。
在第一个示例中,当您调用 calloc 时,它会返回内存区域的地址(至少与请求的大小一样大),并将其存储在 pData 中。 然后,通过一次一个地复制 uc 数组中的值来写入该区域。 然后你调用free(pData),它表示你已经完成了使用之前从calloc获得的内存区域。
在第二个示例中,您分配一个内存区域,并像以前一样将其存储在 pData 中。 但是,然后更改存储在 pData 中的内容 - 将其显式设置为保存 uc 数组的内存区域。 当您调用 free(pData) 时,您不是在 calloc 返回的区域中调用 free,而是在分配给 uc 的区域上调用 free。 这将导致未定义的行为 - 在您的情况下,free() 调用可以检测到这不是 calloc 之前返回的内存,并且(大概)记录错误并终止程序。
为了说明,请尝试打印 dData 的内容
printf("pData points to address %pn", pData);
在 calloc() 之后和 free() 之前。 在第二种情况下,您会看到它发生了变化。
在第二种情况下,您正在尝试释放堆栈上的地址!
这会分配 pData 指向 uc数组:pData = &uc[0];
由于 uc 数组位于堆栈上,因此您不得释放它。只允许堆分配/释放
此代码段尝试通过打印地址来显示差异:
#include <cstdio>
#include <iostream>
using namespace std;
int main(){
double * pData;
double uc[] = {10.0, 20.0, 30.0, 40.0};
pData = (double*) calloc (4,sizeof(double));
if (pData==NULL) exit (1);
printf("Heap allocated pointer to: %pn", pData);
printf("Local stack pointer to: %p", uc);
pData = &uc[0];
printf("pData now points to: %p", pData);
printf ("You have entered: ");
for (int n=0;n<4;n++) cout << pData[n];
free (pData);
cin.get();
return 0;
}
我将解释您的第二个代码片段的作用:
double * pData;
pData = (double*) calloc (4,sizeof(double));
if (pData==NULL) exit (1);
double uc[] = {10.0, 20.0, 30.0, 40.0};
pData = &uc[0];
printf ("You have entered: ");
for (int n=0;n<4;n++) cout << pData[n];
free (pData);
- 声明指向双精度的指针
- 在堆上分配和初始化 4 个双精度块,
pData
指向块的第一个元素 - 如果分配失败,请退出
- 声明并初始化 4 个双精度数组,称为
uc
pData
指向uc
的第一个元素 - 泄漏先前分配的块。- 打印一点
- 循环遍历
pData
(现在是uc
的别名)并打印元素 - 尝试取消分配堆栈上的内存 (
uc
) - 未定义的行为会导致错误
您尝试做的,也就是第一个代码段成功执行的操作,是将uc
复制到pData
。如您所见,这不是此代码的作用。
它与"堆的损坏"有什么关系?
C(C++继承自C)具有"堆栈">和"堆">的抽象概念。您使用malloc
、calloc
(以及 C++ 中的new
关键字)创建的所有内容都位于堆中,其余所有内容都位于堆栈中。
当您从堆中获取资源时,您有责任释放它。编译器会为您处理堆栈。 当尝试取消分配不在您的管辖范围内的内存时(正如我所说,堆栈是编译器的领域),您已经在其轮子上放置了一根棍子。 于是,一个复杂的失败过程开始了。
释放堆栈上的内存是未定义的行为,这意味着标准没有指定如果这样做应该发生什么。如果你编写了未定义的行为,每个编译器和每台机器都可以自由地做任何事情 - 包括抛出不相关的错误,突然燃烧,甚至正常工作。
这里的关键是指针指向其他某个数据对象。因此,您使用指针执行的操作会影响该数据对象。在第一个示例中,数据对象是在免费存储上分配的数组。完成该数组后,您应该释放它;这就是free(pData)
所做的。在第二个示例中,指针最终指向一个数据对象,该对象是堆栈上分配的数组,即它指向uc
。由于uc
是在堆栈上分配的,因此当代码离开当前范围时,它将被释放。它不是在堆上创建的,因此不得释放它。