在没有memcpy的情况下连接两个内存缓冲区

时间:2009-02-12 14:59:41

标签: c memory-management

在C中我有一个接受内存指针的函数foo(char *)。 在调用者中,我有两个不同的内存缓冲区, 我需要连接,所以我可以传递一个指针foo()。 有没有办法让我在没有实际复制一个缓冲区的情况下这样做 到另一个缓冲区的末尾而不改变foo()本身? 即,使两个缓冲区显示为foo()

的一个虚拟连续缓冲区

出于性能原因,我需要这个。一个O(n)解决方案(其中n是缓冲区长度之一) 对我来说是不可接受的。 此外,Linux特定的解决方案很好,如果它有帮助。

感谢。 尼尔

9 个答案:

答案 0 :(得分:5)

是的,有办法。

以与内存中相邻的方式为缓冲区分配内存。

示例:

char* a = malloc(a_size + b_size);
char* b = a + a_size;

答案 1 :(得分:5)

这个问题似乎要问是否可以将两个缓冲区(A和B)的内容与以下约束连接起来:

  • 您无法复制A或B的内容。
  • 您无法更改A的地址。
  • 操作必须具有最差的情况复杂度<为O(n)。
  • 据推测,B的地址是A和B尚未连接的地址。 (正如J.F. Sebastian在他的回答中指出的那样,如果你能够首先连续分配A和B,那么你就完成了。但这似乎是一种堕落的情况。)
  • 您必须能够从Linux内核驱动程序执行此操作(请参阅Jerome的回答中的评论)。
  • A和B都没有页面对齐(请参阅原始问题下的评论)。
  • A和B都不是页面大小的倍数(请参阅原始问题下的评论)。

鉴于这一切,我的答案是否定的:这是不可能的。

是的,OS内核可以使用CPU的MMU(内存管理单元,在具有一个内存的架构上)重新映射内核虚拟地址空间或用户虚拟地址空间中的内存。分配一个连续的虚拟地址空间块,然后通过修改虚拟地址空间块的页表条目将A和B重新映射到该缓冲区中,以指向A和B的物理地址。

这不会更改A本身的虚拟地址(因为旧虚拟地址仍然有效),但它确实要求您通过不同的虚拟地址访问它。这可能是个问题。

今天典型CPU架构上的这种重新映射的粒度基于页面大小,并且由于A和B不是页面对齐,也不是页面大小的倍数,因此您将无法制作它们完全排队。这绝对是个问题。

重新映射N个字节需要为每M个字节修改至少一个页表条目,其中M是页面大小。这意味着重映射操作无论如何都具有O(n)的计算复杂度。其他操作(如为页表分配更多物理页面,刷新缓存和TLB等)会产生额外的性能影响。

另外,我想知道这个问题的目标是否涉及DMA(直接内存访问)。使用需要连续内存的古老设备执行DMA时,除非您拥有IOMMU,否则无需重新映射数量。一个可以进行分散 - 聚集DMA的现代设备首先不需要连续的缓冲区。

答案 2 :(得分:3)

对答案的简短表示抱歉,但不,你不能。

正如你自己所说,你需要

  • 分配一个大缓冲区,然后复制 单独缓冲到此或
  • 改变foo,采取多重指针。

答案 3 :(得分:1)

不,没有通用的解决办法。

你唯一的希望是你想要连接的两个内存区域是否在内存地址空间中直接相继。

答案 4 :(得分:1)

不,没有这样的解决方案,除非你了解内存分配器如何工作,并结合纯粹的运气。

为什么在知道之后需要一个缓冲区时会分配两个缓冲区?缓冲区有多大?你为什么要避免复制?你有没有测量过这会成为一个瓶颈?

答案 5 :(得分:1)

您可以尝试添加另一层间接。它需要你重写foo来获取char *的数组,并且能够处理字符串之间的边界条件。

void foo(char **, int nstrings)
{ 
}

然后连接字符串只是创建指针数组的问题:

char *strings[2] = { string1, string2 };
foo (strings, 2);

答案 6 :(得分:0)

是否可以修改foo()以获取描述要作用的内存位置列表的某种描述符(如指向具有指针/长度对的结构数组的指针)?

这样就不需要进行O(N)复制操作。

如果可能,这似乎是唯一合理的解决方案。

答案 7 :(得分:0)

看看使用realloc。获得第二个缓冲区后,可以调用realloc来增加第一个缓冲区的大小。我对Mac OS的体验是高度优化。

答案 8 :(得分:-1)

以下是一个非常脏的解决方案,但也许是您的唯一解决方案。它并不适用于所有情况(此外,它是不可预测的)。

您可以尝试使用mmap。调用mmap时,为其指定一个地址。 mmap会尝试在你给它的地址分配内存。

这个解决方案可能是您可以拥有的最佳解决方案。您只需要复制一个char[],而不是两者都复制。

您可能必须删除第一个结尾处的\0字符。

你可以使用MAP_FIXED标志:如果mmap不能使用地址,它将不会分配任何内存空间并返回错误。

例如

char a[20];
char b[20];

mmap(a + 20, 20, PROT_WRITE, MAP_FIXED, 0, 0);