我试图了解如何使用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?
如果ptr
是int*
,*(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
实现都做同样的事情。你可能会找到一个在其他地方或根本没有长度的地方。
对指针执行算术运算时,算术以指针指向的对象大小为单位进行。因此,使用char *ptr
,ptr-1
从ptr
中的地址中减去1个字节。但是对于unsigned int *ptr
,ptr-1
从sizeof(int)
的地址中减去了ptr
。
因此,在您的两个函数中,您不会减去相同数量的字节来获取块的堆寄存数据。
此外,当您取消引用指针时,它只访问指针数据类型中的字节数。所以在print_int_heap()
,*(ptr-1)
返回unsigned int
,而在print_char_heap()
它返回一个char
。
您应该只编写一个print_heap()
函数,并将参数转换为调用者中的相应类型。
来自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