动态分配内存

内存泄露、踩内存很糟糕。

1、realloc、calloc、malloc区别

realloc:调用形式为(类型)realloc(ptr,size):将ptr内存大小增大到size。(也可以缩小,缩小的内容消失)。 calloc :调用形式为(类型)calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址。 malloc :调用形式为(类型)malloc(size):在内存的动态存储区中分配一块长度为“size”字节的连续区域,返回该区域的首地址。

malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。calloc在动态分配完内存后,自动初始化该内存空间为零。 1.如果 当前连续内存块足够realloc的话,只是将p所指向的空间扩大,并返回p的指针地址。这个时候q和p指向的地址是一样的。 2.如果 当前连续内存块不够长度,再找一个足够长的地方,分配一块新的内存q,并将p指向的内容copy到q,返回q。并将p所指向的内存空间删除。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char s1[100];
    memset(s1, 0, sizeof(s1));
    char *s2 = (char *)malloc(sizeof(char) * 100);
    printf("uninit malloc s2[55]: %d\n", s2[55]);
    memset(s2, 0, sizeof(char) * 100);
    printf("init: %lu %lu %lu %lu %lu\n", sizeof(s1), sizeof(s2),  sizeof(char *), strlen(s1), strlen(s2));

    const char *str = "12345";
    memcpy(s1, str, sizeof(s1));
    strncpy(s2, str, sizeof(char)*100-1);
    printf("assign: %lu %lu %lu %lu %lu\n", sizeof(s1), sizeof(s2),  sizeof(char *), strlen(s1), strlen(s2));

    char *s3 = (char *)calloc(100, sizeof(char));
    printf("uninit calloc s3[55]: %d\n", s3[55]);

    s3 = (char *)realloc(s3, sizeof(char) * 200);
    printf("uninit realloc s3[155]: %d\n", s3[155]);
    return 0;
}

2、内存是否可以分段释放

https://bbs.csdn.net/topics/200019746 堆分配其实就是链表管理,每次申请内存产生一个节点,挂在busylist上,如果释放,那么就是把这个块从busylist上脱离,然后挂到freelist上,并且每个节点有头部记录上一节点位置,下一节点位置,以及本节点的大小

如按照你的做法,回收节点的一部分,那么好,下面有2个问题需要确定

1)每次被回收的内存也要被当作块,块就有头,记录了块大小等等消息,那么你这个块的块头是什么数据?不清楚,那么很可能就会破坏freelist

2)假如我再次使用malloc将你回收的内存分配出去了,但是你原先的块并不知道他的大小已经改变了啊...当整个快都被使用的时候,就有可能出现数据覆盖

#include <iostream>
#include <string>
#include <memory>
#include <stdlib.h>
#include <stdint.h>
using namespace std;

int main()
{
        uint8_t *data = (uint8_t *) malloc(1000);
        free(data + 100);
        //free(data);
        return 0;
}

3、tcmalloc

3-1、记一次 TCMalloc Debug 经历

https://zhuanlan.zhihu.com/p/37696341 在版本2.9.1没有复现该任何例子,仅仅能看看。

最后决定使用对应版本试试看:

[ubuntu@hj_arm_debain10:~/hj/gperftools-2.6.1/.libs] $ cp libtcmalloc_debug.so.4.4.5 /usr/lib/libtcmalloc_debug.so.4
cp: cannot create regular file '/usr/lib/libtcmalloc_debug.so.4': Permission denied
[ubuntu@hj_arm_debain10:~/hj/gperftools-2.6.1/.libs] $ sudo cp libtcmalloc_debug.so.4.4.5 /usr/lib/libtcmalloc_debug.so.4
[ubuntu@hj_arm_debain10:~/hj/gperftools-2.6.1/.libs] $ sudo cp libtcmalloc.so.4.4.5 /usr/lib/libtcmalloc.so.4
[ubuntu@hj_arm_debain10:~/hj/gperftools-2.6.1/.libs] $ cd ../..
[ubuntu@hj_arm_debain10:~/hj] $ ./a.out
src/tcmalloc.cc:284] Attempt to free invalid pointer 0x5555e0508040
Aborted (core dumped)

最后的demo能复现,其余两个无法实现。

https://zhuanlan.zhihu.com/p/51432385

3-2、起因

修改代码测试的过程中偶现core在tcmalloc free,core出现的地方五花八门,有core在智能指针生命周期结束的时候,有的直接core在右值临时变量析构的时候。 排查过程 查看gperftools的issue,发现有很多人core在相同的地方,谷歌官方回复说这种错误的根因是因为程序的内存管理出了问题,比如访问已经被free的地址,double free等等。 顺着这个思路,一开始先入为主的认为是多线程竞态问题,改了好多代码,也用valgrind等方式查看内存错误。但是问题一直得不到解决。 最后把之前的改动bisect,找了好久,终于找到是某个字符串被double free了。

4、gperftools检查内存泄露/越界等问题的简易说明

https://www.shangmayuan.com/a/5e114526e02a4e7e9ee215e3.html

大名鼎鼎的Google的内存检查工具

在实际工程的Makefile中添加LIB库依赖. 通常来讲 -ltcmalloc就能够了 若是须要使用Profiler的功能,那么用 -ltcmalloc_and_profiler 若是须要检查数组越界等,那么须要用 -ltcmalloc_debug(会大大下降处理速度)

5、内存池

(Memory Pool)是一种内存分配方式,又被称为固定大小区块规划(fixed-size-blocks allocation)。通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

内存池的实现有很多,性能和适用性也不相同,以下是一种较简单的C++实现 -- GenericMP模板类。(本例取材于《Online game server programming》一书)
在这个例子中,使用了模板以适应不同对象的内存需求,内存池中的内存块则是以基于链表的结构进行组织。
GenericMP模板类定义

template <class T, int BLOCK_NUM= 50>
class GenericMP
{
public:
    static VOID *operator new(size_t allocLen)
    {
        assert(sizeof(T) == allocLen);
        if(!m_NewPointer)
            MyAlloc();
        UCHAR *rp = m_NewPointer;
        m_NewPointer = *reinterpret_cast<UCHAR**>(rp); //由于头4个字节被“强行”解释为指向下一内存块的指针,这里m_NewPointer就指向了下一个内存块,以备下次分配使用。
        return rp;
    }

    static VOID operator delete(VOID *dp)
    {
        *reinterpret_cast<UCHAR**>(dp) = m_NewPointer;
        m_NewPointer = static_cast<UCHAR*>(dp);
    }

private:
    static VOID MyAlloc()
    {
        m_NewPointer = new UCHAR[sizeof(T) * BLOCK_NUM];
        UCHAR **cur = reinterpret_cast<UCHAR**>(m_NewPointer); //强制转型为双指针,这将改变每个内存块头4个字节的含义。
        UCHAR *next = m_NewPointer;
        for(INT i = 0; i < BLOCK_NUM-1; i++)
        {
            next += sizeof(T);
            *cur = next;
            cur = reinterpret_cast<UCHAR**>(next); //这样,所分配的每个内存块的头4个字节就被“强行“解释为指向下一个内存块的指针, 即形成了内存块的链表结构。
        }
        *cur = 0;
    }

    static UCHAR *m_NewPointer;

protected:
    ~GenericMP()
    {
    }
};

template<class T, int BLOCK_NUM >
UCHAR *GenericMP<T, BLOCK_NUM >::m_NewPointer;
GenericMP模板类应用
class ExpMP : public GenericMP<ExpMP>
{
    BYTE a[1024];
};

int _tmain(int argc, _TCHAR* argv[])
{
    ExpMP *aMP = new ExpMP();
    delete aMP;
}

6、pk

这样的一个测试程序,如果用jemalloc/tcmalloc链接运行,cpu的消耗在36%左右,而用glibc的malloc实现链接运行,cpu的消耗只有5.7%左右。
这个程序每10ms分配8M数据,写入数据后,再释放。

#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

uint8_t src_buf[1920*1080*4];

int main()
{
    int i = 0;
    uint8_t * dst_buf;
    int count;
    time_t record = 0;
    time_t new = 0;


    for(i = 0;i<10000;i++){
        dst_buf = malloc(sizeof(src_buf));
        memcpy(dst_buf,src_buf,sizeof(src_buf));
        free(dst_buf);
        usleep(10000);
        count++;
        new = time(NULL);
        if(new != record){
            printf("count %d per second\n",count);
            count = 0;
            record = new;
        }
    }

    return 0;
}

results matching ""

    No results matching ""