【问题标题】:Hitting segmentation fault issue when trying to replicate malloc尝试复制 malloc 时遇到分段错误问题
【发布时间】:2017-08-10 19:51:17
【问题描述】:

背景: 我正在完成一个项目,其目标是构建一个复制 malloc() 和 free() 的内存管理系统。该程序首先使用 mmap 从操作系统请求指定数量的内存,然后使用链表跟踪哪些内存部分正在使用或空闲。请参阅下文,大致了解我在做什么。

Output after allocating one chunk of memory

问题 为什么我在尝试创建新的列表节点来表示可用内存时遇到分段错误?仅当尝试请求两个以上的内存块时才会出现此分段错误 - 第一个按预期工作。

详情 当试图为刚刚创建的节点(下面代码中的new)的属性分配值以表示剩余的可用内存空间时,会发生分段错误。此分配在我第一次将内存拆分为已用 v. 空闲段时有效,但第二次尝试将空闲内存拆分为已用 v. 空闲段时,我的程序遇到了分段错误 - 我假设发生这种情况是因为新的 node_t 结构未定义(编译器不知道它们在内存中的位置)。 我不知道为什么会发生这种情况,以及为什么我第一次使用此代码时没有发生这种情况。

注意: 答案不应该是在初始化 *new 节点时使用 malloc,因为项目的重点是在不使用 malloc 的情况下执行此操作。 Malloc 不会从我正在管理的内存中分配 - 相反,它会从我的程序实际维护(但不使用)的堆中分配。

结构定义:

typedef enum {false, true} bool;

typedef struct _node_t {
  int size;
  struct _node_t *next;
  struct _node_t *prev;
  bool used;
} node_t;

问题代码。 ret 是我要返回给调用者的内存分配,而 new 是我刚刚分配的空闲内存块的剩余部分。

// Code above this steps through the list elements to find the first free
// element that is large enough. I've debugged enough to know that's not the
// source of the problem.
node_t *new = ret + sizeof(node_t) + size;   // Pointer to new free slot
assert(new != NULL);
new->prev = ret;
new->next = ret->next;
new->size = ret->size - sizeof(node_t) - size;
new->used = false;

ret->used = true;
ret->next = new;
ret->size = size;

return (ret + sizeof(node_t));

调用代码:

int main(int argc, char* argv[]) {

  int success = Mem_Init(5000, P_FIRSTFIT);
  if (success == -1)
    return -1;

  int *slot1 = Mem_Alloc(sizeof(int));

  // This is where segmentation fault occurs
  int *slot2 = Mem_Alloc(sizeof(int));

Link 可以编译和运行的代码。

【问题讨论】:

标签: c pointers memory-management


【解决方案1】:

答案:一切都在指针数学中。问题是这样的声明:

node_t *new = ret + sizeof(node_t) + size;   // Pointer to new free slot

这个等式右边的任何整数都不会将内存地址增加一个字节......它会将它增加到 node_t 结构将开始的下一个位置。所以在上面的代码中,new 的地址在内存中设置为 (32 + size) * 32 字节(假设 sizeof(node_t) 为 32,这在 64 位系统上是正确的)。只要做到这一点,您就可以在实际从操作系统请求的 500 字节内存之外运行良好。

有效的代码是:

node_t *new = ret + 1 + (size / sizeof(node_t)) + 1;

如果 size 不完全等于 sizeof(node_t),则这段代码最终会过度分配内存,但这是一个更复杂的解决方案的问题。为了便于说明,请考虑以下示例:

  • ret = 地址 1000
  • sizeof(node_t) = 32(在 64 位系统中)
  • 大小 = 50

在本例中,ret 标头的结尾是地址 1032。然后程序分配 2x32 字节来处理 50 字节的请求(数学为 (50 / 32) + 1 = 2,或两个 node_t 结构)。所以new的内存地址其实是1096。

为了使这项工作全面完成,我还必须更新大小计算以反映实际分配而不是请求的分配。这对于防止超出操作系统请求的内存范围正常运行是必要的。

【讨论】:

  • 感谢@rici 让我走上了通往这一发现的正确道路。
  • 而你可以通过将其更改为:node_t *new = (node_t*) ((unsigned long)(ret + 1) + size) 来解决过度分配的问题
【解决方案2】:

虽然你没有包含ret的定义,而且Dropbox对于在智能手机上查看代码完全没用,我认为我们可以推断出声明

node_t* ret;

否则ret->next 将无法编译。

在这种情况下,ret + sizeof(node_t) 没有意义,因为 C 中指针运算的性质。请记住,p + k&p[k] 完全相同。换句话说,p + 1 是大小为sizeof*p 的下一个元素的地址。所以ret + sizeof(node_t)ret之后的第16个或第32个元素的地址,具体取决于sizeof (node_t)

您可能想要的是ret + 1,它的简单性说明了 C 指针算法背后的基本原理。

【讨论】:

  • 这绝对是我代码中的问题之一。这也意味着以下没有意义,因为指针算术。 node_t *new = ret + sizeof(node_t) + size; // Pointer to new free slot 我猜这应该是 node_t *new = ret + 1 + (size/sizeof(node_t))node_t *new = ret + 1 + (size / sizeof(node_t)) + 1 // Extra 1 to handle remainder of integer division
  • @Douglas:你肯定需要考虑对齐,因为 malloc 与其调用者之间的部分约定是返回的指针对于任何对象都充分对齐。
猜你喜欢
  • 2018-09-24
  • 2020-01-05
  • 2012-01-21
  • 2019-02-01
  • 2020-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-28
相关资源
最近更新 更多