【问题标题】:Understanding memory allotment in heap了解堆中的内存分配
【发布时间】:2018-09-12 02:17:40
【问题描述】:

我试图了解如何使用 malloc 在堆上分配内存,并遇到以下观察结果,但无法理解其背后的原因。如果有人能解释一下就太好了。

首先看一下我写的代码:

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

void print_int_heap(unsigned int *ptr, int len)
{
    printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for INT malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
}
void print_char_heap(char *ptr, int len)
{
    printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for CHAR malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
}

int main() {

    unsigned int *ptr1 = malloc(20);
    print_int_heap(ptr1, 20);
    char *ptr2 = malloc(20)
    print_char_heap(ptr2, 20);
    return 0;
}

我为上述程序得到的输出是:

PREV_SIZE: [0x00000000] SIZE: [0x00000019] MEM: [0x0804b008] for INT malloc(20)
PREV_SIZE: [0x00000000] SIZE: [0x00000000] MEM: [0x0804b020] for INT malloc(20)

我可以理解 int malloc 的输出,但我不明白为什么 char malloc 的块大小的值为 0?

【问题讨论】:

    标签: c memory-management heap-memory


    【解决方案1】:

    如果ptrint**(ptr - 1) 指的是sizeof(int) 之前的ptr 引用的字节。这通常是一个 32 位的数量,从 ptr 之前的四个字节开始。

    同样,如果是char**(ptr - 1) 指的是sizeof(char) 之前的ptr 引用的字节。 sizeof(char) 始终为 1;通常这将是 ptr 值之前的单个字节中的 8 位数量。

    这些显然是完全不同的东西。

    顺便说一句,你可以写ptr[-1]。但正如上面的分析所示,这真的不是你想要的。您想将ptr 转换为指向您认为在ptr 之前的对象的数据类型的指针,可能是uint32_t

    从技术上讲,这都是未定义的行为,但是如果您的 malloc 实现在分配之前存储数据并且您知道该数据的类型,我会说可以阅读它。 (虽然盯着系统函数的内部数据看总是有点粗鲁。)

    请注意,并非所有malloc 实现都做同样的事情。您很可能会在其他地方找到一个长度超过或根本没有的。

    【讨论】:

      【解决方案2】:

      当您对指针执行算术运算时,算术运算是以指针指向的对象的大小为单位进行的。所以对于char *ptrptr-1ptr 的地址中减去 1 个字节。但是对于unsigned int *ptrptr-1 会从ptr 中的地址中减去sizeof(int)

      因此,在您的两个函数中,您不会减去相同数量的字节来获取块的堆簿记数据。

      此外,当您取消引用指针时,它只访问指针数据类型中的字节数。所以在print_int_heap() 中,*(ptr-1) 返回一个unsigned int,而在print_char_heap() 中返回一个char

      您可能应该只编写一个 print_heap() 函数,并将参数转换为调用者中的适当类型。

      【讨论】:

        【解决方案3】:

        来自DENNIS M. RITCHIE 的C 编程语言一书

        而不是从已编译的 固定大小 数组中分配, ma​​lloc 将根据需要向操作系统请求空间。由于程序中的其他活动也可能需要空间 调用这个分配器,空间 ma​​lloc 管理可能不连续。因此,它的空闲存储空间被保存为空闲块的列表。每个块包含一个size,一个 指向下一个区块的指针,以及 空间本身。这些块按存储地址递增的顺序保存,最后一个块(最高地址)指向第一个。这 malloc() 返回的块看起来像

            points to          
            next free
             block       
              |                
           ---------------------------------------
           |       |   size   |                  |
           ---------------------------------------
           |       |          |..address returned to the user
           (ptr-2) (ptr-1)    ptr -->     
                              LSB              MSB
        

        这里

        void print_int_heap(unsigned int *ptr, int len) {
            printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for INT malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
        }
        

        *(ptr-2) 打印"next free block" 内的值,如上图所示,*(ptr-1) 打印"size" 块内的值,即分配了多少内存,&ptr 打印用户返回的地址。请注意,这里的ptr 类型是unsigned int*,所以*(ptr-2) 表示从2*sizeof(int) 之前的ptr 指向的字节访问数据。

        这里

        void print_char_heap(char *ptr, int len){
            printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for CHAR malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
        }
        

        访问*(ptr-1)

            next free
             block        (ptr-1)--> *(ptr-1) prints data from ? marked location.
              |            |  
           ---------------------------------------
           |       | size |? |                  |
           ---------------------------------------
           |       |         |..address returned to the user
                             ptr -->     
                             LSB              MSB
        

        ptr 类型为char* 意味着当您执行*(ptr-1) 时,它将访问来自sizeof(char) 字节之前的ptr 指向的数据。

        在动态分配内存时最好使用valgrind 并确保没有发生内存泄漏的地方,只需运行

        valgrind --leak-check=full -v ./your_exe
        

        并分析valgrind的消息。例如,它可能会显示类似

        ==3193== Invalid read of size 4
        ==3193==    at 0x8048459: print_int_heap
        ==3193== Invalid read of size 4
        ==3193==    at 0x8048461: print_int_heap
        

        【讨论】:

        • 他显然是在尝试访问 malloc 的实现相关的簿记数据。它是 UB 的事实可能无关紧要,就像关于 shellcode 漏洞利用的问题一样。
        • 是的。我怎么忘记了?谢谢@Barmar 让我修改。
        猜你喜欢
        • 2019-07-08
        • 2017-01-20
        • 2011-05-30
        • 1970-01-01
        • 1970-01-01
        • 2021-06-18
        • 2011-10-09
        • 2020-10-03
        • 2018-03-22
        相关资源
        最近更新 更多