提问人:Ddystopia 提问时间:6/21/2023 更新时间:10/4/2023 访问量:157
有没有办法在不复制数据的情况下从 C 语言的左侧缩小内存分配?
Is there a way to shrink a memory allocation from the left in C without copying data?
问:
我知道 C 标准库允许通过使用 realloc 函数来调整内存分配的大小,如以下示例所示:
char *a = malloc(10);
char *b = realloc(a, 8);
在这种情况下,a 和 b 可能相等,并且分配实际上已从右侧缩小了两个字节。
但是,我想知道是否有办法从左侧缩小内存分配,如下所示:
char *a = malloc(10);
char *b = /* ... */; // shrink a from the left by 2 bytes
其中 ,和 a 开头的原始 2 个字节现在可供分配器使用。这应该发生,而不必将数据复制到内存中的新位置。只需缩小分配即可。(a + 2) == b
我知道使用 realloc 从右侧缩小内存或手动将数据复制到新的、较小的分配可能是一种选择,但这些方法不适合我的需求。
有没有办法使用 C 的标准库或提供此功能的任何其他库来实现这一点?
我不是要求 100% 保证,realloc 也可以返回指向不同位置的指针,但很可能会。
提前感谢您的见解。
我可以将所有字节向左移动并尝试缩小,但这涉及复制。
答:
realloc
或任何其他标准库函数都不允许你控制内存区域收缩的方向。
事实上,调用可能会从左侧收缩它,尽管这不是它通常的实现方式。realloc(a, 8)
解决方案 1 - 不要,只做指针算术realloc
不过,您可以使用指针算术手动执行此操作:
char *a = malloc(10);
char *b = a + 2;
// TODO: free(a), or
// free(b - 2)
请注意,这是不允许的,因为必须使用与 或 返回的指针完全相同的指针进行调用。free(b)
free
malloc
realloc
解决方案 2 - 从右侧收缩,反向查看数组
另一种可能性是始终与数组进行反向交互。
而不是用 , 来索引它。a[i]
a[end - i - 1]
int end = 10;
char *a = malloc(end);
// first byte of reverse array is at a a[9]
end = 8;
a = realloc(a, end);
// first byte is now at a[7]
这样,即使我们从右边收缩,就好像我们从左边收缩一样。
评论
realloc(a, 8)
malloc
realloc
当您在堆上分配某些内容时,除了用户数据之外,几乎每个实现都需要分配更多数据。你通过&朋友界面看到的字节只是用户数据。标准库/OS 实现堆分配的常用方法是首先分配段标头,然后分配数据。malloc
这意味着在段的数据部分的紧邻“左侧”(较低地址)放置标头。在标头和数据之间留出间隙没有任何意义。相反,您必须将整个内容移动到新的内存位置。
但这些方法并不适合我的需求。
这些需求究竟是什么?使用动态内存时,我们必须注意:
- 它比在分配和初始化时静态分配的内存慢得多。
- 它总是伴随着内存开销。标头/查找表等方面的开销,处理对齐方面的开销,堆碎片方面的开销。
例如:
char *a = malloc(10);
char *b = realloc(a, 8);
总体上可能消耗的RAM内存比比我们说的要多得多。而且几乎可以肯定的是,它的速度会慢 ~100 到 1000 倍。char c[12];
调用所涉及的执行时间开销也很可能远比 / 调用昂贵。realloc
memcpy
memmove
那么,您在这里要解决的实际问题是什么?
评论
malloc()
alloc()
rust #[repr(C)] struct RcBox<T: ?Sized> { strong: Cell<usize>, weak: Cell<usize>, value: T, }
Box
size_of::<T>()
value
RcBox
strong
weak
value
malloc
有了更多关于您的系统的信息,我们可以以更直接的方式回答这个问题(如果这是一个好主意是另一个问题)。
如果您使用的是 POSIX 系统,则可以使用 和 为此。这只有在您需要大量内存时才有意义,这些内存是页面大小的数倍并且值得释放。您可以使用 (check ) 保留内存。在不再需要它的前 N 个字节(N 必须大于一个页面)之后,您可以使用释放 M 个页面 (),之后仍然使用内存。mmap()
munmap()
mmap()
man mmap
munmap()
N>=M*pageSize
我为此做了一个简短的演示:
#include <stdio.h>
#include <sys/mman.h>
int main(void)
{
//Map pages for the memory needed
unsigned char *t=mmap(NULL,1024*1024,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
printf("mmap returned address %p for 1MiB\n",t);
if(!t)
{ perror("mmap"); return -1; }
printf("Writing to the mapped pages\n");
t[12]='A';
printf("unmap the first 16 KiB we don't need anymore\n",t);
if(munmap(t,16*1024)<0)
{ perror("munmap"); return -1; }
printf("Writing after the unmapped pages, to the still mapped pages\n");
t[16*1024+12]='A';
#if 0 //Set to 0 when you don't want to see the segement fault
printf("Create segment fault\n");
t[15]='A'; //accessing the unmapped pages will cause a segment fault or any other UB.
#endif
printf("unmap the rest of the memory\n");
if(munmap(t+16*1024,(1024-16)*1024)<0)
{ perror("munmap 2"); return -1; }
printf("Test done successfully\n");
return 0;
}
此演示不检查页面大小,对于真正的程序,您必须获取页面大小并确保仅映射和取消映射整个页面。当您想要增加保留内存时,您也会遇到困难(确保您不会与其他页面发生冲突,...,当您的系统可用时,也许会变得方便)。对于其他具有 MMU 的非 Posix 平台,可能也有类似的解决方案。move_pages()
编辑:正如下面的评论中提到的,我已经重写了其中的一些内容(很晚,但无论如何),尤其是关于分页和好友系统的部分非常不准确。
回到主题:
尽管自己管理这样的记忆是一个有趣的想法,但我也许可以提供一些解释,为什么在大多数情况下这也是一个坏主意:
有效地管理记忆本身就是一门真正的科学。本文提供了一个很好的概述,如果您对操作系统的操作方式感兴趣,则可以进行深入探讨。
假设您有一个如下所示的内存块,其中包含先前分配的区域。
现在想象一下,一些区域不再需要并被释放(蓝色方块)。突然间,你的记忆看起来不再那么漂亮了。必须分配新的区域,释放更多的旧区域,您的记忆看起来像瑞士奶酪 - 到处都是洞 - 很快。这是内存碎片。当然 - 您可以保留一个可用内存空间列表(或类似列表),您仍然可以在其中分配,但您的实现已经变得越来越大。如果您现在尝试从左侧释放,那么您的下一个内存标头将去哪里?(在这种情况下,甚至忽略内存的压缩)
更糟糕的是:如果释放内存,如何知道该可用块旁边的内存块是否仍在使用中?还有更多的管理工作要做。如果你不这样做,你的记忆可以是自由的,但仍然像这样支离破碎:
有解决方案,但我的建议是让 malloc() 和 realloc() 完成它们的工作。用你所拥有的。
评论
buddy-allocator
评论
b = a + 2
a