了解堆中的内存分配

问题描述 投票:-3回答:3

我试图了解如何使用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
3个回答
2
投票

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

类似地,如果它是char**(ptr - 1)sizeof(char)引用之前引用ptr字节。 sizeof(char)总是1;通常,这将是ptr值之前的单字节中的8位数量。

这些显然是完全不同的事情。

顺便说一下,你被允许写ptr[-1]。但正如上面的分析所示,那真的不是你想要的。你想把ptr强制转换为指向你认为在ptr之前的对象的数据类型的指针,可能是uint32_t

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

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


1
投票

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

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

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

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


1
投票

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

malloc将根据需要从操作系统请求空间,而不是从已编译的固定大小的数组中分配。由于程序中的其他活动也可以在不调用此分配器的情况下请求空间,因此malloc管理的空间可能不是连续的。因此,它的免费存储空间被保存为一个空闲块列表。每个块包含一个大小,指向下一个块的指针以及空间本身。块按存储地址增加的顺序保存,最后一个块(最高地址)指向第一个块。 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
© www.soinside.com 2019 - 2024. All rights reserved.