用C编写代码,从未正式学习过任何代码,使用GNU的GSL库,快速的基本问题。
如果我错了,请纠正我,但按照我的理解,当我为矩阵分配内存(使用内置的var = gsl_matrix_alloc(x,x)
)并将其存储在变量中时,我本质上是在创建指针,它只是内存的某个地址,例如:x01234749162
其指向我的GSL矩阵的第一个指针/存储器位置。跟踪何时解除分配与指针(同样是内置的gsl_matrix_free(x,x,x)
)关联的结构的内存是没有问题的,我知道在重新分配结构的指针之前我需要这样做,否则我会造成内存泄漏。
现在我的问题是,我知道这是基本的,但请听我说完——我在stackoverflow上找不到一个特别直接的答案,主要是因为很多答案都涉及C++而不是C——我如何释放指向结构本身的指针?
每个人都说"哦,把它设置为NULL"。为什么会这样?这只是更改指向已释放结构的内存地址。这是否告诉MMU该内存位置现在可以使用?例如,当我在XCode中调试程序时,gsl_matrix结构的所有属性都被成功释放;所有的东西都变成了随机十六进制字符的垃圾串,这就是释放内存应该做的。但是,在调试程序中,我仍然可以看到变量名(指针)。。。即使我将变量设置为NULL。我认为这意味着我没有释放指针,我只是释放了结构并将其设置为00010000(NULL)。
我做的每件事都正确吗?这只是XCode的一个功能,还是我错过了一些基本的东西?
我意识到,如果一个结构被交易,那么指向该结构的一个指针可能被认为不是什么大不了的事情,但它很重要。
这里有一些代码来说明我的想法。
gsl_matrix* my_matrix;
// create single memory address in memory, not pointing to anything yet
my_matrix = gsl_matrix_alloc(5, 5);
// allocates 25 memory spaces for the values that the pointer held by my_matrix
// points too
// Note: so, now there's 26 memory spots allocated to the matrix, excluding other
// properties created along with the my-matrix structure, right?
gsl_matrix_free(my_matrix); // deallocates those 25 spaces the structure had,
// along with other properties that may have been automatically created
free(my_matrix); // SIGBRT error. Is the pointer to the deallocated structure
// still using that one memory address?
my_matrix = NULL; // this doesn't make sense to me.I get that any future referral
// to the my_matrix pointer will just return garbage, and so setting a pointer to
// that can help in debugging, but can the pointer--that is just one memory
// address--be completely deallocated such that in the debugger the variable name
// disappears?
这里缺少的是关于"局部变量"如何在机器级别工作的知识以及"堆栈"的概念。
堆栈是程序启动时为其分配的一块可用内存。为了一个简单的例子,假设您的程序被分配了一个大小为1MB的堆栈。堆栈附带一个特殊的寄存器,称为"堆栈指针",它最初指向堆栈的末尾(不要问为什么不指向开头,历史原因)。它看起来是这样的:
[---------- stack memory, all yours for taking ------------]
^
|
Stack pointer
现在假设您的程序在main
函数中定义了一组变量,即类似的变量
int main() {
int x;
这意味着,当在程序开始时调用main
函数时,编译器将生成以下指令:
sp = sp - 4; // Decrement stack pointer
x_address = sp;
并且记住(为了进一步编译的目的)CCD_ 5现在是位于存储器位置CCD_。您的堆栈现在如下所示:
[---------- stack memory, all yours for taking --------[-x--]
^
|
Stack pointer
接下来,假设您从main中调用某个函数f
。假设f
在其中定义了另一个变量
int f() {
char z[8];
猜猜现在发生了什么?在进入f
之前,编译器将执行:
sp = sp - 8;
z_address = sp;
也就是说,你会得到:
[---------- stack memory, all yours for taking -[--z----][-x--]
^
|
Stack pointer
如果现在调用另一个函数,堆栈指针将深入堆栈,为局部变量"创建"更多空间。不过,每次退出函数时,堆栈指针都会恢复到调用函数之前的位置。例如,在您退出f
后,您的堆栈将如下所示:
[---------- stack memory, all yours for taking -[--z----][-x--]
^
|
Stack pointer
请注意,z
数组实际上并没有被释放,它仍然在堆栈中,但您不在乎。你为什么不在乎?因为当应用程序终止时,整个堆栈会自动解除分配。这就是为什么您不需要手动解除分配"堆栈上的变量"的原因,即那些被定义为函数和模块本地的变量。特别是,my_matrix
指针只是另一个类似的变量。
附言:堆栈上发生的事情比我描述的要多。特别是,堆栈指针值在递减之前存储在堆栈上,以便在退出函数后可以恢复。此外,函数参数通常是通过将它们放入堆栈来传递的。从这个意义上说,它们看起来像内存管理的局部变量,您不需要释放它们。
PPS:原则上,编译器可以自由地优化代码(尤其是如果您使用
-O
标志进行编译),而不是在堆栈上分配本地变量,它可能会:- 决定完全避免分配它们(例如,如果它们被证明是无用的)
- 决定将它们临时分配到寄存器中(寄存器是处理器中不需要释放的固定内存插槽)。这通常是针对循环变量(
for (int i = ...)
中的变量)执行的 - 。。好吧,只要结果不与语义相矛盾,就做他扭曲的头脑中出现的任何其他事情
PPPS:现在您已经准备好学习缓冲区溢出是如何工作的。去读一读吧,真的,这是一个神奇的把戏。哦,而且,一旦你做到了,就要看看堆栈溢出的含义。)
为什么要为5x5矩阵分配26个内存点?我想说,相信库提供的gsl_matrix_free
函数会做正确的事情,并释放整个结构。
通常,如果您呼叫了malloc
或calloc
,则只需要呼叫free
。提供分配器的库函数通常提供匹配的解除定位器,这样您就不必跟踪内部。
如果你担心的第26个位置是指针本身(换句话说,存储矩阵地址所需的内存),那么这个空间是函数堆栈帧的一部分,当函数返回时,它会自动弹出。
每个人都说"哦,把它设置为NULL"。为什么会这样?
它们可能意味着,这将解决您在指向一些已经取消分配的数据的指针上调用free
的问题,这就是您在这里所做的:
gsl_matrix_free(my_matrix); // deallocate
free(my_matrix); // Mistake, BIG PROBLEM: my_matrix points to de-allocated data
它修复了这个问题,因为在空ptr上调用free
是一个无操作:
gsl_matrix_free(my_matrix); // deallocate
my_matrix = NULL;
free(my_matrix); // Mistake, but no problem
注意:my_matrix
本身具有自动存储,因此无需手动取消分配。当它超出范围时,它的内存将被回收。唯一需要取消分配的是动态分配的内存(以及my_matrix
指向的内存)