我试图了解如何使用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?
答案 0 :(得分:2)
如果ptr
是int*
,则*(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 *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()
函数,并将参数转换为调用方中适当的类型。