了解堆中的内存分配

时间:2018-09-12 02:17:41

标签: c memory-management heap-memory

我试图了解如何使用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?

3 个答案:

答案 0 :(得分:2)

如果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实现都做同样的事情。您很可能会找到一个可以将长度存储在其他位置或根本不存储的。

答案 1 :(得分:1)

来自 DENNIS M. RITCHIE

C编程语言
  

与其从编译后的固定大小数组中分配,    malloc 将根据需要从操作系统请求空间。由于程序中的其他活动也可能要求空间   调用此分配器,    malloc 管理可能不是连续的。因此,其免费存储空间被保存为免费块的列表。每个块包含一个 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

答案 2 :(得分:1)

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

因此,在您的两个函数中,您并没有减去相同数量的字节来获取该块的堆记账数据。

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

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