求教,关于malloc给结构体分配内存大小的不解

拥抱Linux 2019-03-12 10:17:06
今天下午在看论坛里另一位坛友的提问贴 https://bbs.csdn.net/topics/392553412#post-403750895 的时候,他不改malloc()的参数的情况下,始终有运行错误,但是在我的电脑上却没问题。而且他那边的运行错误也不是一访问链表的节点(结构体成员)就出现,而是在删除链表节点之后才出现的。所以,按道理,在创建链表节点、输出节点信息的过程中都没问题,那表示节点是获得了合适的内存空间的啊。

然后,我在修改他的代码之前,运行出现的情况和他是一样了;但是我把代码里的逻辑上的错误改正了之后,我这里再运行就没有问题了。但是他说他那边改了代码逻辑之后,还是有同样的问题。这就百思不得其解了。具体代码如下:


#include <stdio.h>
#include <malloc.h>

typedef struct NUM
{
int a;
struct NUM *next;

} num, *pnum;

pnum c_list(); //创建链表
void s_list(pnum head); //输出链表
void d_list(pnum head, int a); //删除链表(删除指定的值)

int main(void)
{
int a;
while (1 == scanf("%d", &a))
{
pnum p;
p = c_list(); //创建链表,并将链表首节点赋给P
s_list(p); //输出链表
d_list(p, a); //删除链表(删除值为2的节点)
s_list(p); // 输出链表
// 我在main()函数里增加了循环输入欲删除节点成员值的功能
// 增加了输出下面的字节、内存地址信息
// 其它地方只改了删除链表节点函数的一行:增加了一句else
// 申请获取内存空间的代码没改
printf("sizeof(num) is %ld\n",sizeof(num));
printf("sizeof(pnum) is %ld\n",sizeof(pnum));
printf("sizeof(struct NUM) is %ld\n",sizeof(struct NUM));
printf("hex address of p is %x\n",&p);
printf("hex address of p->next is %x\n",p->next);
printf("hex address of p->next->next is %x\n",p->next->next);
}
return 0;
}

//创建链表
pnum c_list()
{
pnum p;
pnum pend; //尾节点
int i;
pnum head; //首节点,指向头节点
head = (pnum)malloc(sizeof(pnum)); // 所有的malloc里的sizeof都保持原样sizeof(pnum)
head->next = NULL;
p = head;
for (i = 0; i < 3; i++)
{
pend = (pnum)malloc(sizeof(pnum));
pend->a = i;
pend->next = NULL;
p->next = pend;
p = p->next;
}
return head;
}

//输出链表
void s_list(pnum head)
{
pnum p;
p = head; //接收传过来的首节点
while (NULL != p->next)
{
printf("%d ", p->next->a);
p = p->next;
}
printf("\n");
}

//删除链表
void d_list(pnum head, int a) //int a 接收删除指定的节点
{
pnum p;
pnum q; //用于指向需要删除的节点
p = head; //接收传过来的首节点
while (NULL != p->next)
{
if (p->next->a == a)
{
q = p->next;
p->next = q->next;
free(q);
q = NULL;
}
else //只增加了这一行else。下面的 p = p->next; 是原来就有的。
p = p->next;
}
}


当时我也看到了malloc里的sizeof(pnum)觉得不对,但是后来一想,既然能创建、输出链表,那应该没问题啊。也试过改成sizeof(num)的情况,还是会出现内存段错误。索性就先不管这里,尽量减少不相干的差异点,就还是按那个提问的代码,只是在最后的删除函数里加了一行else。就这样就好了。

刚刚在gdb里看了一下内存的情况,发现实际malloc()分配给每个结构体变量的内存大小并不是sizeof(pnum)==8,而是至少sizeof(struct NUM) == sizeof(num) == 16,因为相邻的两个结构体的首地址相差的是32个字节,是结构体本身大小的2倍,而不仅仅是16个字节。





说了这么多,现在不明白的是,究竟出现内存段访问错误的原因是什么?我认为当然是原来的删除函数的逻辑不对,访问了不该访问的内存。

但是,那个帖子的提问者的情况又体现的是malloc分配内存不足导致的——可这也矛盾啊,创建、输出都可以,偏偏删除不行???

难道是malloc()函数智能检测了struct NUM的大小,忽略了代码中的偏小的参数sizeof(pnum),以防内存访问错误 ?
又或者是编译器gcc作主,把交给malloc()的参数改成了struct NUM的大小,甚至还翻倍了?
有或者是因为平台不同、编译器不同导致的诧异?我这里是linux环境,用gcc编译的。他那边应该是windows和VC6。

苦于现在还不懂编译过程中的那些文件,不知道到底是怎么一回事,所以特此各位大佬求教,先谢谢了!
...全文
471 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
拥抱Linux 2019-03-15
  • 打赏
  • 举报
回复

补充一篇自己写的总结:
“C语言malloc(size)分配的可用空间,以及 free(*) 成功的根据”,
https://blog.csdn.net/weixin_44732817/article/details/88566962
拥抱Linux 2019-03-14
  • 打赏
  • 举报
回复
引用 9 楼 gs汗宝宝 的回复:
我觉得你是在说内存对齐的问题,这个网上有一些介绍还不错。


嗯,从某种角度来说,跟内存对齐也有关联。
这次无意当中因为错误使用 malloc() 而反过来查证,基本上理清了 malloc() 的实际操作了,也对 free(pointer) 为什么能恰好释放合适的内存块而不会多释放也不会少释放有了更清楚的认识,也算是大有收获。
也谢谢您的建议!
gs汗宝宝 2019-03-14
  • 打赏
  • 举报
回复
我觉得你是在说内存对齐的问题,这个网上有一些介绍还不错。
拥抱Linux 2019-03-14
  • 打赏
  • 举报
回复
引用 7 楼 火花20180731 的回复:
我是不太明白啊,你这个head = (pnum)malloc(sizeof(pnum));是几个意思?

你的节点类型是struct NUM类型,你给它取了个别名叫num,那么你创建节点应该是sizeof(num)的长度。
里面有一个int成员,一个pnum成员,所占内存大小是这两个成员的长度之和。
但是你创建节点却分配的是一个pnum的长度的内存,也就是struct NUM *类型。

你这还pend->a = i; pend->next = NULL;
赋值赋得倒是不亦乐乎......
程序没崩我也是见了鬼了!


帖子第一句就说了,原本的代码是之前另外一个帖子的提问者给的,他当时就是在创建链表的函数里写成这样的:

head = (pnum)malloc(sizeof(pnum));

而且他自己编译通过了,运行的时候可以创建链表、读取并输出链表信息(结构体成员的值),只是最后进行到 删除链表的函数的时候才报错了。而他的机器上,报错的原因 和 我的机器上 报错的原因 ,不一样。
而且,我把他原来的代码的逻辑错误改正之后,运行就没有出错了。甚至把申请内存空间的部分代码改成 malloc(0) 也没问题。
所以,才会有这篇延伸探究 malloc() 具体实现的帖子。
以前,我也只知道 malloc(size) 是申请 size 个字节的内存块,就想当然地以为获得的也仅仅是 size 个字节的内存块。
但是,上面的代码所运行表现出来的情况证明不是这样子的,所以才慢慢深入 malloc().c 这个源头,知道了 malloc(size) 分配出来的内存块的大小,并不简单地 等于 size 个字节。虽然,知道了这些实现的细节,在现在看来似乎没有什么用处,但是我觉得学编程,特别是C语言的编程,还是不能脱离底层的基础知识,不能太依赖于抽象的概念和接口,那样学到的东西比较像空中楼阁,基础不牢。
好吧,顺便多罗嗦了几句,觉得不合适的话就请忽略掉吧。
火花20180731 2019-03-14
  • 打赏
  • 举报
回复
我是不太明白啊,你这个head = (pnum)malloc(sizeof(pnum));是几个意思?

你的节点类型是struct NUM类型,你给它取了个别名叫num,那么你创建节点应该是sizeof(num)的长度。
里面有一个int成员,一个pnum成员,所占内存大小是这两个成员的长度之和。
但是你创建节点却分配的是一个pnum的长度的内存,也就是struct NUM *类型。

你这还pend->a = i; pend->next = NULL;
赋值赋得倒是不亦乐乎......
程序没崩我也是见了鬼了!
拥抱Linux 2019-03-13
  • 打赏
  • 举报
回复
谢谢指导。上午的结论还是比较粗浅的,因为之前对malloc关心得不多。博客里当时有个结论也写错了,在64-bit、size_t为8个bytes的系统里,malloc()成功得到的内存块的大小不是32bytes的正整数倍,而应该是 32 + 2 * sizeof(size_t) * n 个bytes,其中n是大于等于 0 的正整数。我刚刚把文章里的相关内容修改过了。

关于您上面回复的2点,我之前是这样想的:
(1)之所以在上面的帖子里下第1点结论,是因为当时想理清到底malloc(0)分配了多大的内存空间。而且除了结构体本身使用到的之外,两个连续地址之间多出来的16个字节到底是不是malloc()分配的呢?后来,了解到malloc()还有对齐、overhead这些东西,才推断应该是连续的,中间多出来的16个字节也是malloc()分配的。当然了,这只是为了解决头脑里的困惑,就算知道是分配到的内存块的一部分,也是不能去动的。
(2)访问或者查看额外的内存地址上的东西,主要是因为调试的时候看到了一些不是代码里能提现出来的数据,所以就想搞清楚那些是什么,主要还是为了调试的时候明白一点吧,不是真的想在代码里去访问越界的内存。

再次感谢!
636f6c696e 2019-03-13
  • 打赏
  • 举报
回复
你博客里的结论基本都对,但帖子里的疏漏太多了: 1. 连续使用malloc()给指针分配内存块的时候,分配的内存块确实是连续的 ——不应该有这种假定,这种假定既危险也无实际的意义。 2. 能访问内存不代表是合法的,比如你申请一个32字节的内存,你实际用了64字节。这就是典型的内存越界,内存越界不见得会立即出现异常,但是依然是非法访问内存。
拥抱Linux 2019-03-13
  • 打赏
  • 举报
回复
引用 3 楼 niiiloc 的回复:
混淆了很多概念,相邻申请内存块的偏移值不等于申请内存块的大小。


从malloc.c的描述来看,如果分配到的内存块确实是相邻的话,确实是地址的差。

因为,分配的内存块里,除了有用户或代码需要存放数据的空间之外,还有 “对齐操作”的空间 和 “overhead” 的空间。
所以,并不因为分配的相邻内存块的地址差大于申请的size字节数,就不相邻。
只不过是用户代码“应该”访问的内存地址是不相邻的。

下午把这篇帖子重新整理了一下,请指导:
https://blog.csdn.net/weixin_44732817/article/details/88530993
636f6c696e 2019-03-13
  • 打赏
  • 举报
回复
混淆了很多概念,相邻申请内存块的偏移值不等于申请内存块的大小。
拥抱Linux 2019-03-13
  • 打赏
  • 举报
回复

既然没有人回复,那还是我自己来回答吧。

刚刚查阅了glibc里的malloc.c文件,里面介绍了malloc()在64位系统、size_t为8字节的情况下,最小分配块的大小是32字节。

在线查看C/C++库文件的网页:https://code.woboq.org/userspace/glibc/malloc/malloc.c.html


// glibc/malloc/malloc.c
/*
* Vital statistics:
Supported pointer representation: 4 or 8 bytes
Supported size_t representation: 4 or 8 bytes
Note that size_t is allowed to be 4 bytes even if pointers are 8.
You can adjust this by defining INTERNAL_SIZE_T
Alignment: 2 * sizeof(size_t) (default)
(i.e., 8 byte alignment with 4byte size_t). This suffices for
nearly all current machines and C compilers. However, you can
define MALLOC_ALIGNMENT to be wider than this if necessary.

Minimum overhead per allocated chunk: 4 or 8 bytes
Each malloced chunk has a hidden word of overhead holding size
and status information.

Minimum allocated size: 4-byte ptrs: 16 bytes (including 4 overhead)
8-byte ptrs: 24/32 bytes (including, 4/8 overhead)
When a chunk is freed, 12 (for 4byte ptrs) or 20 (for 8 byte
ptrs but 4 byte size) or 24 (for 8/8) additional bytes are
needed; 4 (8) for a trailing size field and 8 (16) bytes for
free list pointers. Thus, the minimum allocatable size is
16/24/32 bytes.

Even a request for zero bytes (i.e., malloc(0)) returns a
pointer to something of the minimum allocatable size.

*/


简而言之,malloc()分配到的最小内存块的大小是8bytes的倍数,具体在64-bit系统、size_t的大小定义为8bytes(64-bit OS默认;此值可修改)时,分配到的最小内存块的大小是16bytes的倍数,实际再加上还有overhead占用的空间和Alignment(对齐)的要求,其实是32bytes的倍数。

下面用一段代码来验证:


// 测试最小分配到的内存空间大小,是否为32bytes
// 测试环境OS为64bit,size_t为8bytes

#include <stdio.h>
#include <malloc.h>

typedef struct mall_test
{
int a;
struct mall_test *next;
} MT, *PMT;

int main(int argc, char const *argv[])
{
char *test; // 指针变量
printf("size of type : size_t is %ld\n", sizeof(size_t));
printf("size of type : int is %ld\n", sizeof(int));
printf("size of type : unsigned int is %ld\n", sizeof(unsigned int));
printf("size of type : * is %ld\n", sizeof(test));
printf("size of type : struct mall_test is %ld\n", sizeof(MT));

// 申请内存空间
PMT p[5] = {NULL};
for (int i = 0; i < 5; ++i)
{
p[i] = (PMT)malloc(0);
if (NULL == p[i])
printf("p[%d] malloc failed.\n", i);
else
{
p[i]->a = i + 1;
p[i]->next = NULL;
printf("p[%d] = (char*)malloc(0) returns hex address of p[%d] : %x\n", i, i, p[i]);
}
}

// 访问成员
for (int i = 0; i < 5; ++i)
{
printf("p[%d]->a is %d\n",i,p[i]->a);
}

// 释放内存空间
for (int i = 0; i < 5; ++i)
{
free(p[i]);
p[i] = NULL;
}

return 0;
}


运行结果截图:



总结:

(1)64-bit操作系统、按 size_t 默认为8bytes的情况,malloc()分配到的最小内存块的大小是 32bytes。
(2)即使是 malloc(0) 也是。
(3)分配到的内存空间,可以按正常方式访问:比如 p[i]->a 访问结构体成员。
(4)也许不同的编译器对使用malloc(size)时,参数size 有不同的检查要求吧,看起来gcc没有要求太多,好像是非负整数就行。

暂时就想到这么多,应该算是解决了心里的一个小疑惑吧!
拥抱Linux 2019-03-12
  • 打赏
  • 举报
回复
关于内存段访问错误的原因,已经通过gdb调试找到了,是删除函数的逻辑错误造成的。



但是,这又和那位提问者所反馈的修改了代码之后,仍然出现和之前一样的错误的情况想矛盾了。现在想来,可能是操作系统和编译器的原因。

但是malloc到底分配多大的内存这个问题,还是存在。再次恳请有了解这方面情况的专家不吝赐教!谢谢!

69,382

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧