需要明确的是:我确实知道malloc
和free
是在C库中实现的,它通常从操作系统分配大量内存,并进行自己的管理,以将较小的内存分配给应用程序并跟踪分配的字节数。这个问题不是自由如何知道释放多少。
相反,我想知道为什么免费首先是这样做的。作为一种低级语言,我认为要求C程序员不仅要跟踪分配了什么内存,还要跟踪分配了多少内存是完全合理的(事实上,我通常发现我最终还是要跟踪malloced的字节数)。我还发现,明确给出释放的字节数可能会允许一些性能优化,例如,对于不同的分配大小,具有单独池的分配器将能够通过查看输入参数来确定释放哪个池,并且总体空间开销将更少。
那么,简而言之,为什么malloc
和free
被创建为需要在内部跟踪分配的字节数?这只是历史事故吗?
一个小编辑:一些人提供了一些观点,如“如果你释放的金额不同于你分配的金额怎么办”。我想象中的API可能只需要释放分配的字节数;释放或多或少可以简单地由UB或实现定义。不过,我不想阻碍关于其他可能性的讨论。
实际上,在古老的Unix内核内存分配器中,mfree()
采用了size
参数。malloc()
和mfree()
保留了两个数组(一个用于核心内存,另一个用于交换),其中包含有关空闲块地址和大小的信息。
在Unix V6之前,没有用户空间分配器(程序将只使用sbrk())。在Unix V6中,iolib包括一个具有alloc(size)的分配器和一个不带size参数的调用。每个内存块前面都有其大小和指向下一个块的指针。当遍历空闲列表时,指针仅用于空闲块,并在使用中的块上重新用作块内存。
在Unix 32V和Unix V7中,这被一个新的malloc()
和free()
实现所取代,其中free()
没有采用size
参数。实现是一个循环列表,每个块前面都有一个包含指向下一个块的指针和一个“忙”(分配)位的单词。因此,malloc()/free()
甚至没有跟踪显式大小。
“为什么C中的free不接受要释放的字节数?”
因为没必要,而且也没什么意义。
当您分配某物时,您想告诉系统要分配多少字节(原因很明显)。
但是,当您已经分配了对象时,您返回的内存区域的大小现在已确定。它是隐式的。它是一个连续的内存块。您不能释放它的一部分(让我们忘记realloc()
,反正它不是这样做的),您只能释放整个东西。您也不能“释放X字节”——您要么释放从malloc()
获得的内存块,要么不释放。
现在,如果你想释放它,你可以告诉内存管理器系统:“这是一个指针,free()
它所指向的块。”-内存管理器将知道如何做到这一点,要么因为它隐式地知道大小,要么因为它甚至可能不需要大小。
例如,malloc()
的大多数典型实现都维护一个指向空闲和分配内存块的指针链接列表。如果将指针传递给free(),它只会在“已分配”列表中搜索该指针,取消链接相应节点并将其附加到“free”列表。它甚至不需要区域大小。它只在可能尝试重新使用所讨论的块时才需要该信息。
一个参数free(val*)
(在Unix V7中引入)与之前的两个html" target="_blank">参数mfree(val*,size_t)
相比有另一个主要优势,我在这里没有看到提到:一个参数free
显着简化了所有其他与堆内存一起工作的API。例如,如果free
需要内存块的大小,那么strdup
将不得不以某种方式返回两个值(指针大小)而不是一个(指针),并且C使多值返回比单值返回繁琐得多。我们必须编写char*strdup(char*)
,而不是char*strdup(char*,size_t*)
或者struct CharPSusSize{char*val;size_tsize}; CharPSusSize strdup(char*)
。(如今,第二个选项看起来很诱人,因为我们知道以NUL结尾的字符串是“计算历史上最灾难性的设计错误”,但这是事后看来。早在70年代,C将字符串处理为简单的char*
的能力实际上被认为是相对于Pascal和Algol等竞争对手的决定性优势。)此外,受此问题困扰的不仅仅是strdup
——它会影响分配堆内存的每个系统或用户定义的函数。
早期的Unix设计者都是非常聪明的人,有很多原因可以解释为什么免费优于mfree,所以基本上我认为问题的答案是他们注意到了这一点,并相应地设计了他们的系统。我怀疑你能找到任何直接的记录,记录下他们做出那个决定时脑子里发生了什么。但我们可以想象。
假设您正在用C编写在V6 Unix上运行的应用程序,其中有两个参数mfree。到目前为止,您的管理还不错,但是随着您的程序变得越来越雄心勃勃,并且需要越来越多地使用堆分配的变量,跟踪这些指针大小变得越来越麻烦。但是,你有一个绝妙的想法:你可以只编写一些实用函数,直接将大小存储在分配的内存中,而不是一直复制这些大小:
void *my_alloc(size_t size) {
void *block = malloc(sizeof(size) + size);
*(size_t *)block = size;
return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
block = (size_t *)block - 1;
mfree(block, *(size_t *)block);
}
你用这些新函数写的代码越多,它们看起来就越棒。它们不仅让你的代码更容易写,还让你的代码更快——这两件事并不经常一起发生!在你传递这些之前size_t
到处都是,这增加了复制的CPU开销,意味着你必须更频繁地溢出寄存器(特别是。对于额外的函数参数)和浪费内存(因为嵌套函数调用通常会导致size_t
的多个副本存储在不同的堆栈帧中)。在您的新系统中,您仍然需要花费内存来存储size_t
,但只有一次,并且永远不会复制到任何地方。这些效率似乎很小,但请记住,我们谈论的是具有256 KiB RAM的高端机器。
这让你快乐!因此,您可以与正在开发下一个Unix版本的留胡子男士分享您的酷把戏,但这并不会让他们高兴,反而会让他们难过。你看,他们只是在添加一些新的实用函数,比如strdup,他们意识到使用你的酷技巧的人将无法使用他们的新函数,因为他们的新函数都使用了笨重的指针大小API。这也让你感到难过,因为你意识到你必须在你编写的每个程序中自己重写好的strdup(char*)函数,而不是使用系统版本。
但是等等!这是1977年,向后兼容性在未来5年内不会发明!此外,没有一个严肃的人真正使用这个不起眼的“Unix”名称。K的第一版
几天后,胡子男人#2看到了新的API并说,嘿,等等,这比以前好了,但它仍然需要在每个分配中花费整整一个字来存储大小。他认为这是下一件亵渎神明的事。其他人都看着他,好像他疯了一样,因为你还能做什么?那天晚上,他熬夜并发明了一个新的分配器,它根本不存储大小,而是通过对指针值执行黑魔法位移来即时推断大小,并在保持新API不变的情况下进行交换。新的API意味着没有人注意到开关,但他们确实注意到第二天早上编译器使用的RAM减少了10%。
现在每个人都很高兴:你得到了更容易写和更快的代码,胡子男人1得到了一个很好的简单的strdup,人们将实际使用,而胡子男人2-相信他已经赚了一点钱-回到了与奎因的胡闹。发货!
或者至少,这是怎么发生的。
我的webpack加载器如下所示: 但尝试包含文件时出错 ./node_modules/css-loader中出错?{“sourceMap”:true,“modules”:true,“importloaders”:1,“localidentname”:“[local]_[hash:base64:3]”}!./node_modules/postcss-loader/lib?{“plugins”:[n
问题内容: 我写了下面的代码 因此,当我运行它时,它可以正常运行并打印输出“ Hello”。 但是,如果JVM规范要求该main方法应该是公开的,因为“否则它就看不到main”,那么它也不适用于该类吗?如果JVM在未声明为public时“看不到” Hello.main(),它将如何看到类A本身。 除了“因为规范这样说”以外,对此还有什么解释吗? 并且,如果JVM能够看到所有类和方法,因为它本身就是
问题内容: 在 CSS3 ,有包括如多发性字体类型,,,和。 我们为什么要使用所有这些类型? 如果它们专用于不同的浏览器,为什么它们的数量大于主要Web浏览器的数量? 问题答案: 在2019年回答: 仅使用WOFF2,或者如果需要传统支持,请使用WOFF。不要使用任何其他格式 (和是死的格式,并有完整的系统字体,并且不应该被用于Web的目的) 2012年的原始答案: 简而言之,font-face很
问题内容: 出于好奇,我只是用C,C ++和Java编写了“ Hello Worlds”。 我理解Java类文件只有423B,因为运行时未包含在二进制文件中。 C和C ++分别是8.5K和9.2K。 为什么它们这么大?我一直认为stdio或iostream是动态链接的,并且不会增加可执行文件的大小。 那么,所有千字节来自何处?通过查看hexdump,我发现有很多填充,我想是出于性能原因。为什么确切
问题内容: 我宣布了以下课程 当我编译并运行它时,它运行良好并打印输出“ done”。即使我声明它在“ package a”中,也具有相同的行为; 但是,如果JVM规范要求该main方法应该是公开的,因为“否则它就看不到main”,那么它也不适用于该类吗?如果JVM在未声明为public时“看不到” A.main(),它将如何看到A类本身。 除了“因为规范这样说”以外,对此还有什么解释吗? 问题答
我在一个R数据帧中有一个向量,它字面上包含一年中月份的缩写形式(1月、2月、3月、4月、5月、6月、7月、8月、9月、10月、11月、12月),我想用它们的传统等价形式替换它们[1:12] 提出了以下想法,所有这些想法都给出了一个填充了不可用(NA)值的向量。 前两个只使NA值分别为JAN,第三个使所有月份NA值 任何想法为什么这不起作用,以及如何让它工作?