当前位置:C++技术网 > 资讯 > c内存申请malloc的诡异

c内存申请malloc的诡异

更新时间:2017-08-30 22:31:54浏览次数:1+次

你好,麻烦解答一下我以下这几个问题,我有些困惑。

1:malloc申请的空间不足,不会报错

  int* xxx = (int*) malloc(1);
  *xxx = 4504;
  cout << "xxx[0]="<< *xxx << endl;
  我只申请了1个字节,1个int有4个字节,结果打印出来的是4504,不是乱码

2:二级指针,也应该是指针数组可以随意越界,还有就是地址相差大

    int** xxx = (int**) malloc(2);
    int x2 = 2015;
    int x3 = 2018;
    xxx[0] = &x2;
    xxx[10] = &x3;


    xxx[0][1] = 44;
    xxx[2][0] = 77;

xxx[0][1] 的地址0x0012FFA4,xxx[1][15] 的地址0xABABFE39xxx[0][1]与xxx[1][15]的地址为什么会差这么大呢?

还有就是我写成 xxx[10] 与 xxx[2][0],也可以,为什么呢,感谢。


C++技术网会员解答:

    您好,感谢您一直对C++技术网的支持与信任。

    整理一下,您提问的问题有:

1.为什么只分配一个字节的内存,在使用时却可以超过1个字节?

2.为什么超过内存的赋值还可以正常输出?

3.为什么二级指针可以越界?

4.为什么一维数组和写成二维数组的形式?

5.然后还有一个问题就是为什么二维数组中地址会相差很大?


    看上去是一大堆问题。我们来一起看看这些问题吧。

    提问中的两个malloc的内存分配,分别分配了1个字节和2个字节。而在使用的时候,1个字节的内存被当做了4个字节的int类型使用。而分配的2个字节的内存,当做数组使用也可以随意使用。

    我们知道,malloc是从堆中申请内存的。堆是系统的一块内存区,可以供系统的所有程序申请使用的内存资源池。一般为了提高内存使用效率,降低内存碎片,在分配内存的时候,都不会真的按照你分配的数量随意分割的。否则会因为参差不齐的内存切割引来一大堆的内存碎片。举个例子来理解,这个就跟切蛋糕一样。如果每个人都按照自己的心意随便切割,有的多有的少,最后会导致剩余的一块不多不少,给需要多的人少了,给需要少的人又多了。这样的内存就无法分配出去,除非刚好合适。那么这样一来,堆内存池中就会出现很多这样不大不小的内存块,后续的内存分配要一个个的去找合适大小的内存块。这样就会大大降低内存分配效率。而且一些内存块切割剩下的小内存块真的是不好分配了,大家都看不上,就无法利用了。这样一来,这个内存碎片就只能一直待在那了,不能利用起来,就浪费了。因为切割到处都存在,这样的碎片就会到处都有。

    所以在真正分配内存的时候,系统会做额外的分配。比如按照一个字(2个字节)来分配或者一个双字(4个字节)或者一块内存(可能是比较多的字节数)。因为不分配给你,余下的这点内存也不能使用,所以就所幸全部给你程序。只不过,在上层看来,多余分配的内存是不存在的。我们也不能依赖这样的内存。万一刚好就没有剩余的内存或是被整分(没有零头)了。还有,你根本就不知道内存堆里的分配情况。

    所以,提问中出现的分配了1或2个字节,在使用的时候却可以超出分配的大小。理论上就是这个道理了。类似的问题还有内存栈里。系统分配给程序一块栈内存,程序内部分配的变量在使用时也可以超界。因为超界发生在自己的地盘里,外界无法检测。而且一些超界也不会一定引发崩溃。所以这样是很难提前检测的。所以在使用的时候,发现超界了也很正常,没有报错,没有崩溃。但是,当你内存使用的频繁,比如变量很多,就会刚好使你使用的内存附近都是有效的变量占有了。那么此时你再越界了,虽然不会报错,但是却会无形中将相邻的变量的数据破坏了。而这样的破坏,一般很难检测出来。当然,不排除有些调试器或者编译器可能会检测到。但是我们不要出现这样的情况,这样只会让自己变得难堪。

    所以1-3的问题就已经解答了。第4个问题的数组的使用问题,一维数组和二维数组的使用,并不是绝对的。不管是一维还是二维还是三维数组,在内存中的表现形式都是线性的,也就是内存排序都是从0开始排的。尽管叫做二维数组,但是实际内存中还是一条线,并没有两条上下并排的线。二维数组的平面表格形式是我们对二维格式的抽象理解,并不是真的是一个表格。三个数组更是理解为立体的存储格式。但是存储的都是线性的。所以一维和二维是可以直接转换的,只是一个表示的问题。我们完全可以使用一个公式将二维数组转为一维数组。使用二维数组的原因就是因为和我们思维的抽象表格结构更符合,让人去理解更加直接。这就好像10个人站成一排和站成两排的区别。站成一排是内存的表示方式,站成两排是人为表示方式,但是从头到尾报数,最后一个人不管是哪种站队,报数都是10。就是这个道理。我们只有最终的地址是一个,也就是同一个元素,与表示没有直接关系,哪种表示都行。C/C++特别就是有这样的好处,抛开表象看本质,所以强制转换类型,就是这个效果。

    malloc分配的内存都是连续的。如果是你自己分配的内存的范围内,不会出现数组内的元素内存地址相差很大的情况。如果本来就建立在不正常的基础之上,比如越界了。这种情况就不好说了。你问的问题中的地址,相差悬殊,不好确定是为什么。这个可以表明,这个数组越界了,越界后的使用是不合理的,从这个地址也就发现了这个问题。我们不知道越界的索引指到哪里去了。这就是越界后的问题之一,刚好可以提醒你去避免越界。使用内存时要小心。

    在不正常的基础上引发的诡异问题,没有多大必要去深究。如果真想去研究,那么就要深入到内存管理机制了。这就不是一下能够说的清楚的。我们要做到的是在正常的基础上把一些基本的错误规避掉。目的是去防止这类诡异问题的发生,而不是去研究这些诡异问题的逻辑。这类问题,出错之后,很难说的清楚的。不过偶尔思考一下,也是不错的想法。