【问题标题】:How does this memory allocator work?这个内存分配器是如何工作的?
【发布时间】:2016-10-31 18:35:56
【问题描述】:

所以,我就坐下来决定写一个内存分配器。我累了,所以我只是扔了一些东西。我最终得到的是这样的:

#include <stdio.h>
#include <stdlib.h>

#define BUFSIZE 1024

char buffer[BUFSIZE];
char *next = buffer;

void *alloc(int n){
    if(next + n <= buffer + BUFSIZE){
        next += n;
        return (void *)(next - n);
    }else{
        return NULL;
    }
}

void afree(void *c){
    next = (char *)c;
}

int main(){
    int *num = alloc(sizeof(int));
    *num = 5643;
    printf("%d: %d", *num, sizeof(int));
    afree(num);
}

由于某种原因,这行得通。但我无法解释它为什么起作用。这可能与我累的事实有关,但我真的不明白它为什么会起作用。所以,从逻辑上讲,这就是它应该做的事情,据我所知:

  1. 它创建一个 char 数组,其指针指向数组的第一个元素。

  2. 当我调用值为 4 的 alloc(这是一个 int 的大小,正如我在下面测试过的)时,它应该设置为指向数组的第四个元素。然后它应该返回一个 char 指针,指向转换为 void 指针的数组的前 4 个字节。

  3. 然后我将该值设置为大于 char 的最大值。 C 应该意识到这是不可能的,然后应该将其截断为 *num % sizeof(char)。

我有一个关于为什么会这样的猜测:当 char 指针被强制转换为 void 指针然后变成整数时,它会以某种方式改变指针的大小,以便它能够指向整数。 (我不仅尝试过使用整数的内存分配器,还尝试使用结构,而且它似乎也适用于它们)。

这个猜测是正确的,还是我太累了,无法思考?

编辑:

编辑2:我想我已经理解了。我意识到我昨天的措辞很糟糕。让我失望的是返回的指针实际上指向一个字符,但我仍然能够以某种方式存储一个整数值。

【问题讨论】:

  • 睡一觉,然后再看一遍。我猜你会明白的。
  • 我认为您得到了指向的内容和错过的内容。 void * 可以远大于字符的最大大小。这就是让你困惑的地方吗?
  • “然后它应该返回一个 char 指针,指向转换为 void 指针的数组的前 4 个字节。”为什么?它返回一个void *,它被转换为int *。
  • 一般来说,它并不总是有效。分配两个浮点数,将它们设置为 1.0,然后释放分配的第一个浮点数。接下来,分配一个双精度并将其设置为 1.0,然后检查第二个浮点值。它改变了。
  • @hidefromkgb:很好的观察。事实上,对于任何不是以 LIFO 顺序成对匹配的alloc()/afree() 序列,它都完全被破坏了。仁慈的是,它是一个堆栈。

标签: c


【解决方案1】:

发布的分配器实现了标记和释放分配方案:

  • alloc(size) 如果在竞技场中至少有 size 未分配字节可用,则返回一个有效指针。可用大小相应减小。请注意,此指针只能用于存储字节,因为它没有正确对齐其他任何内容。此外,从 C 标准的严格解释来看,即使指针正确对齐,将其用作指向任何其他类型的指针也会违反严格的别名规则。

  • afree(ptr) 将竞技场重置为alloc() 返回ptr 之前的状态。让afree(NULL) 将竞技场重置为其初始状态将是一个有用的扩展。

注意main() 函数尝试使用alloc(sizeof(int)) 返回的指针作为指向int 的指针。这会调用未定义的行为,因为无法保证 buffer 会为此正确对齐,并且违反了严格的别名规则。

另请注意,第二个参数的 printf 格式 printf("%d: %d", *num, sizeof(int)); 不正确。如果 C 运行时库太旧而无法支持 %zd,则应该是 printf("%d: %zd", *num, sizeof(int)); 或 printf("%d: %d", *num, (int)sizeof(int));。

【讨论】:

  • 是的,我只需要一些睡眠,我现在明白了!
【解决方案2】:

其实!我想出了这种行为的原因!这就是我想知道的,但是,我昨天不太擅长将我的想法变成文字(对不起)。我将我的代码修改为这样的:

#include <stdio.h>
#include <stdlib.h>

#define BUFSIZE 1024

char buffer[BUFSIZE];
char *next = buffer;

void *alloc(int n){
    if(next + n <= buffer + BUFSIZE){
        next += n;
        return (void *)(next - n);
    }else{
        return NULL;
    }
}

void afree(void *c){
    next = (char *)c;
}

int main(){
    int *num = alloc(sizeof(int));
    *num = 346455;
    printf("%d: %d\n", *num, (int)sizeof(int));
    printf("%d, %d, %d, %d", *(next - 4), *(next - 3), *(next - 2), *(next - 1));
    afree(num);
}

现在,最后一个 printf 产生“87, 73, 5, 0”。 如果将所有值转换为一个大的二进制值,您将得到:00000000 00000101 01001001 01010111。如果您将该二进制值转换为十进制,您将得到 *num 的原始值,即 346455。因此,基本上它将整数分成 4 个字节,并将它们放入数组的不同元素中。我认为这是实现定义的,并且与小端和大端有关。它是否正确?我的第一个预测是它会截断整数并基本上将值设置为 (整数值) % sizeof(char)。

【讨论】:

    【解决方案3】:
    int *num = alloc(sizeof(int));
    

    说 - '这是一个指向某个空间的指针 (alloc),让我们说它指向一个整数 (int*)。'

    你说的

     *num = 5643;
    

    也就是说 - 将该整数设置为 5643。

    为什么它不起作用 - 因为 alloc 实际上确实返回了一个指向可以保存整数的好内存块的指针

    【讨论】:

    • 技术上它“行不通”,因为它违反了严格的别名并且是未定义的行为。
    • @cmaster 否。您可以通过字符类型的左值表达式访问任何类型的对象,但反过来不是的情况。
    • @EOF - 我想我不明白这意味着什么。 OP 的 alloc 返回 void *,就像 malloc 一样。什么问题?我们是说返回 void * 的函数是 UB 吗?或者只是如果你写这些指针。在这种情况下,任何 malloc 如何工作?
    • @pm100: malloc() 的特殊之处在于它返回一个指向未声明类型的对象的指针。 char 的数组确实 具有声明的类型。请参阅 C11 标准草案 n1570,6.5 表达式,第 6 段(有效类型)和第 7 段(按类型允许的访问)。
    • @EOF 你是对的。我现在在标准中查找了它:指针是否可用于修改对象确实取决于该对象的声明类型,即使该类型从未用于操作该对象。所以,是的,使用 char 的静态数组来支持内存分配确实会导致 UB。有趣的是,如果char 数组本身是动态分配的(通过malloc() 或mmap()),那就没问题了,因为这样对象就没有声明的类型。对我来说,标准措辞听起来是错误的,因为声明的目的是允许重新排序内存访问...
    猜你喜欢
    • 2018-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-07
    • 1970-01-01
    • 2015-12-05
    • 1970-01-01
    • 2014-06-08
    相关资源
    最近更新 更多