首页 文章

malloc()是否分配了一块连续的内存块?

提问于
浏览
33

我有一段由一位非常老的学校程序员编写的代码:-) . 它就是这样的

typedef struct ts_request
{ 
  ts_request_buffer_header_def header; 
  char                         package[1]; 
} ts_request_def; 

ts_request_def* request_buffer = 
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

程序员基本上正在研究缓冲区溢出概念 . 我知道代码看起来很狡猾 . 所以我的问题是:

  • malloc总是分配连续的内存块吗?因为在这段代码中,如果块不连续,代码将失败很长时间

  • 执行free(request_buffer),它将释放malloc分配的所有字节,即sizeof(ts_request_def)(2 * 1024 * 1024),或者只释放结构sizeof(ts_request_def)大小的字节

  • 你看到这种方法有任何明显的问题,我需要和老板讨论这个问题,并想指出这种方法有任何漏洞

14 回答

  • 3

    回答你的第三个问题 .

    free 始终释放一次性分配的所有内存 .

    int* i = (int*) malloc(1024*2);
    
    free(i+1024); // gives error because the pointer 'i' is offset
    
    free(i); // releases all the 2KB memory
    
  • 3

    问题1和2的答案是肯定的

    关于丑陋(即问题3)程序员试图用分配的内存做什么?

  • 2

    回答你的编号 .

    • 是的 .

    • 所有字节 . Malloc / free不知道或关心对象的类型,只是大小 .

    • 严格来说,这是一种未定义的行为,但是许多实现都支持这种技巧 . 请参阅下面的其他替代方案

    最新的C标准,ISO / IEC 9899:1999(非正式的C99)允许flexible array members .

    一个例子是:

    int main(void)
    {       
        struct { size_t x; char a[]; } *p;
        p = malloc(sizeof *p + 100);
        if (p)
        {
            /* You can now access up to p->a[99] safely */
        }
    }
    

    这个现在标准化的功能允许您避免使用您在问题中描述的常见但非标准的实现扩展 . 严格地说,使用非灵活的数组成员并超出其边界访问是未定义的行为,但许多实现记录并鼓励它 .

    此外,gcc允许zero-length arrays作为扩展名 . 零长度数组在标准C中是非法的,但是在C99为我们提供灵活的数组成员之前,gcc引入了这个功能 .

    在回复评论时,我将解释为什么下面的代码段在技术上是未定义的行为 . 我引用的章节编号参考C99(ISO / IEC 9899:1999)

    struct {
        char arr[1];
    } *x;
    x = malloc(sizeof *x + 1024);
    x->arr[23] = 42;
    

    首先,6.5.2.1#2显示a [i]与(((a)(i)))相同,因此x-> arr [23]等价于(((x-> arr)(23) )) . 现在,6.5.6#8(添加指针和整数)说:

    “如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义 . ”

    因此,由于x-> arr [23]不在数组中,因此行为未定义 . 您可能仍然认为它没关系,因为malloc()意味着数组现在已经扩展,但事实并非如此 . 信息性附件J.2(列出了未定义行为的例子)通过一个例子提供了进一步的说明:

    数组下标超出范围,即使某个对象显然可以使用给定的下标访问(如左边的表达式a [1] [7],给出声明int a [4] [5])(6.5.6) .

  • 7

    3 - 在结构的末尾分配动态数组是一个非常常见的C技巧 . 另一种方法是将指针放入结构中,然后单独分配数组,而不是忘记释放它 . 虽然大小固定为2mb似乎有点不寻常 .

  • 0

    1)是的,或者如果没有足够大的连续块可用,malloc将会失败 . (malloc失败将返回NULL指针)

    2)是的,它会 . 内部内存分配将跟踪使用该指针值分配的内存量并释放所有内存 .

    3)这是一种语言黑客攻击,并且对它的使用有点怀疑 . 它仍然会受到缓冲区溢出的影响,只是可能会让攻击者稍微长一点,以找到导致它的有效负载 . “保护”的成本也非常高(你真的需要>每个请求缓冲区2mb?) . 这也很难看,虽然你的老板可能不理解这个论点:)

  • 3

    这是一个标准的C技巧,并不比任何其他缓冲区更危险 .

    如果你试图向你的老板证明你比“非常老的学校程序员”聪明,那么这段代码就不适合你了 . 老派不一定不好 . 似乎“老派”的人对内存管理了解得足够多;)

  • 2

    我不认为现有的答案完全符合这个问题的本质 . 你说老派程序员正在做这样的事情;

    typedef struct ts_request
    { 
      ts_request_buffer_header_def header; 
      char                         package[1]; 
    } ts_request_def;
    
    ts_request_buffer_def* request_buffer = 
    malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
    

    我认为他不太可能做到这一点,因为如果这就是他想做的事情,他可以用简化的等效代码来完成它,不需要任何技巧;

    typedef struct ts_request
    { 
      ts_request_buffer_header_def header; 
      char                         package[2*1024*1024 + 1]; 
    } ts_request_def;
    
    ts_request_buffer_def* request_buffer = 
    malloc(sizeof(ts_request_def));
    

    我敢打赌,他真正做的就是这样的事情;

    typedef struct ts_request
    { 
      ts_request_buffer_header_def header; 
      char                         package[1]; // effectively package[x]
    } ts_request_def;
    
    ts_request_buffer_def* request_buffer = 
    malloc( sizeof(ts_request_def) + x );
    

    他想要实现的是分配具有可变包大小x的请求 . 用变量声明数组的大小当然是非法的,所以他通过一个技巧来解决这个问题 . 看起来他好像知道他对我做了什么,诀窍很好地朝着C技巧量表的可敬和实际结束 .

  • 7

    至于#3,没有更多的代码,很难回答 . 我没有看到它有什么问题,除非它发生了很多 . 我的意思是,你不想分配2mb的内存块时间 . 你也不想不必要地去做,例如如果你只使用2k .

    你出于某种原因不喜欢它的事实不足以反对它,或者证明完全重写它是正确的 . 我会仔细研究一下这个用法,尝试理解原始程序员的想法,仔细查看使用这个内存的代码中的缓冲区溢出(如workmad3指出的那样) .

    您可能会发现许多常见错误 . 例如,代码检查以确保malloc()成功吗?

  • 3

    漏洞利用(问题3)实际上取决于你的这种结构的界面 . 在上下文中,这种分配可能是有意义的,如果没有进一步的信息,就不可能说它是否安全 .
    但是,如果你的意思是分配内存大于结构的问题,这绝不是一个糟糕的C设计(我不会那么老的学校...;))
    这里只是最后一点 - 具有char [1]的点是终止NULL将始终位于声明的结构中,这意味着缓冲区中可以有2 * 1024 * 1024个字符,并且您不必具有帐户对于"+1"的NULL . 可能看起来像一个小壮举,但我只想指出 .

  • 5

    我经常看到并使用过这种模式 .

    它的好处是简化内存管理,从而避免内存泄漏的风险 . 所需要的只是释放malloc'ed块 . 使用辅助缓冲区,您将需要两个免费的 . 但是,应该定义并使用析构函数来封装此操作,以便始终可以更改其行为,例如切换到辅助缓冲区或添加在删除结构时要执行的其他操作 .

    对数组元素的访问也稍微有效,但现代计算机的使用越来越少 .

    如果内存对齐在具有不同编译器的结构中发生变化,代码也将正常工作,因为它非常频繁 .

    我看到的唯一潜在问题是编译器是否置换成员变量的存储顺序,因为这个技巧要求包字段在存储中保持最后 . 我不知道C标准是否禁止排列 .

    另请注意,分配的缓冲区的大小最有可能大于所需的大小,至少是一个字节,如果有的话还有额外的填充字节 .

  • 1

    是 . malloc只返回一个指针 - 它怎么可能告诉请求者它已经分配了多个不连续的块来满足请求?

  • 2

    想补充一点,这不是常见的,但我也可能称之为标准做法,因为Windows API充满了这种用途 .

    例如,检查非常常见的BITMAP头结构 .

    http://msdn.microsoft.com/en-us/library/aa921550.aspx

    最后一个RBG quad是一个1大小的数组,这完全取决于这种技术 .

  • 10
  • 47

    这里要实现的是 malloc 没有看到在此计算

    malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
    

    它一样

    int sz = sizeof(ts_request_def) + (2 * 1024 * 1024);
       malloc(sz);
    

    你可能会认为它分配了2块内存,并且在脑海中它们是“结构”,“一些缓冲区” . 但是malloc完全没有看到这一点 .

相关问题