C語言實現內存管理 (一)

我們都知道 c 語言申請內存釋放內存是 alloc / free 由蘑。直接調用函數就行甜无,但是某些特定的場景無法調用 c 的標準庫,比如單片機津辩,資源有限砚尽,并沒有帶 c 的標準庫施无,則需要自己實現 alloc / free,基本上就是先事先在單片機里申請一塊連續(xù)的內存必孤,然后基于這一塊內存實現內存管理猾骡。這里通過3種方式實現,以供參考:


因為在單片機上敷搪,內存非常有限兴想,大概不到100k,所以能用來額外申請的內存很小赡勘,另外不考慮多線程嫂便,所以實現內存管理比較簡單。多線程的可以參考微軟的 minialloc
我們先討論最簡單的一種闸与,定長的方式毙替,單片機很多情況自身的RAM也是這樣設計的岸售,也就是不管是 char 、int 都是4個字節(jié)厂画,這樣會帶來內存的一部分浪費凸丸。

1. 區(qū)塊設計

image.png

如上圖,需要先申請2塊區(qū)域:

  • 數據區(qū):以4個字節(jié)為一塊,申請的時候都是返回以塊為單位
  • 標識區(qū):用來標識數據區(qū)那一塊是可用的木羹,是可用被申請的甲雅,為了節(jié)省內存解孙,標識區(qū)用一個字節(jié)中的一位來標識數據區(qū)的一塊(4個字節(jié))的可用性坑填。這樣,假如申請64塊也就是256個字節(jié)的數據區(qū)弛姜,需要8個字節(jié)的標識區(qū)脐瑰。

2. 申請和釋放

這里實現的是 realloc 方法,而不是 alloc 廷臼,區(qū)別就是 alloc 每次執(zhí)行都是申請一塊新的內存區(qū)域苍在,而 realloc 執(zhí)行的時候需要傳遞舊的指針,如果舊的不為空荠商,則相當于給舊的指針擴大或縮小申請的內存寂恬。申請基本流程是:
從頭遍歷標記區(qū),一位一位的查找莱没,這里注意在常規(guī)的內存管理一般不會從頭遍歷初肉,會從上一次申請的地方往下遍歷,但是我們這里內存很小饰躲,從頭遍歷也不會影響速度牙咏。
找到第一個空閑位(為0),再查找后續(xù)的連續(xù)空的位數量是否滿足申請內存的塊數嘹裂,如果滿足妄壶,則返回指針,并把申請的所有標識位都改成 1 寄狼。比如要申請34個字節(jié)丁寄,則計算需要9個塊(4k一塊),則遍歷找到第一組連續(xù)超過9個塊都為空閑的區(qū)域返回回去泊愧。
如果要釋放伊磺,也很簡單,釋放的時候除了把指針傳遞過來拼卵,還需要把釋放的大小也傳遞過來奢浑,這樣釋放的時候只需要把標識區(qū)連續(xù)的區(qū)域標記為0即可∫溉看代碼很少雀彼,100來行:

////////////////////////////////////////////////////////////////////////
// 固定大小格式壤蚜,每塊占用4個字節(jié)(特定的單片機只支持這種方式
// 版本 20220913
//申請的內存區(qū)塊個數
#define dxheap_block_count 64
//總共占用的字節(jié)數需要*4
static unsigned char dxheap[dxheap_block_count * 4];
//需要用count/8個字節(jié)來標識每個區(qū)塊占用還是空閑,1個bit來表示一個區(qū)塊的情況徊哑,0表示空閑袜刷,1表示占用
static unsigned char dxheap_empty_flags[dxheap_block_count / 8];

/**
 * @brief 內存塊標識占用還是空閑,把一段連續(xù)的塊標記成占用還是空閑
 *
 * @param tag 0表示空閑,1表示占用
 * @param start_block_index 開始的塊序號莺丑,從0開始
 * @param count 連續(xù)塊的個數
 */
void dxheap_flag_write(int tag, int start_block_index, int count)
{
    int index;
    for (index = start_block_index; index < start_block_index + count; index++)
    {
        if (tag)
        {
            dxheap_empty_flags[index / 8] = dxheap_empty_flags[index / 8] | (1 << (index % 8));
        }
        else
        {
            dxheap_empty_flags[index / 8] = dxheap_empty_flags[index / 8] &= ~(1 << (index % 8));
        }
    }
}
//判斷第index個塊是否被占用 0表示空閑著蟹,1表示占用
int dxheap_flag_read(int index)
{
    char c = dxheap_empty_flags[index / 8];
    int pos = index % 8;
    return (c & (1 << pos)) >> pos;
}
//判斷從第index個塊開始,連續(xù)count個塊都為空則返回0梢莽,否則1
int dxheap_has_count_empty(int index, int count)
{
    int notfound = 0;
    int i = index;
    while (notfound == 0 && i < dxheap_block_count && i <= (index + count))
    {
        notfound = notfound + dxheap_flag_read(i);
        i++;
    }
    return notfound == 0;
}
void *dxheap_alloc(int size)
{
    //先計算需要的塊數量
    int count;
    if (size % 4 == 0)
    {
        count = size / 4;
    }
    else
    {
        //內存可能浪費
        count = (size / 4) + 1;
    }
    if (count > dxheap_block_count)
    {
        printf("not enough memory found\n");
        return -1;
    }
    void *result;
    int i = 0;
    while (i < dxheap_block_count)
    {
        int flag = dxheap_flag_read(i);
        if (flag)
        {
            //假如不為空,則跳過到下一塊
            i++;
            continue;
        }
        //判斷是否能找到count個空閑塊
        int isfound = dxheap_has_count_empty(i, count);
        if (isfound)
        {
            dxheap_flag_write(1, i, count);
            return dxheap + i * 4;
        }
        else
        {
            i++;
            continue;
        }
    }
    printf("not enough memory found\n");
    return -1;
}

void dxheap_free(void *ptr, int osize)
{
    //先判斷ptr是否是整個數組區(qū)域內的指針
    int min = &dxheap[0];
    int max = &dxheap[dxheap_block_count * 4 - 1];
    if (ptr < min || ptr > max)
    {
        return;
    }
    //先算出ptr對應的指針指向第幾塊,這里index必然是可以整除4的萧豆,是塊的首地址。除非亂傳一個地址過來
    int index = ptr - min;
    index = index / 4;
    //后面osize個字節(jié)對應的塊也需要置空
    int count = osize / 4;
    if (osize % 4 != 0)
    {
        count++;
    }
    dxheap_flag_write(0, index, count);
    memset(ptr, 0x00, count * 4);
}

void dxheap_init()
{
    //初始化昏名,把整個數組標記為可用涮雷,
    memset(dxheap, 0x00, dxheap_block_count * 4);
}

void *dxheap_realloc(void *ptr, int osize, int size)
{
    if (!ptr)
    {
        // ptr為空表示直接alloc一塊內存
        void *block = dxheap_alloc(size);
        return block;
    }
    //先判斷ptr是否是整個數組區(qū)域內的指針,不是的話直接alloc
    int min = &dxheap[0];
    int max = &dxheap[dxheap_block_count * 4 - 1];
    if (ptr < min || ptr > max)
    {
        void *block = dxheap_alloc(size);
        return block;
    }
    //申請一塊足夠大的新快,把內容復制過去
    int index = ptr - min;
    //有可能osize比size大,這里不用math.min
    int minLength = osize;
    if (osize > size)
    {
        minLength = size;
    }
    void *newBlock = dxheap_alloc(size);
    if (newBlock == -1)
    {
        return newBlock;
    }
    memcpy(newBlock, ptr, minLength);
    //最后釋放舊的區(qū)域
    dxheap_free(ptr, osize);
    return newBlock;
}

// //////////////////////////////////////////////////////////////////////////////////////

對外暴露的函數主要是 dxheap_realloc,dxheap_free轻局。其中 dxheap_alloc是申請一塊新區(qū)域洪鸭,而 dxheap_realloc也是簡化了過程,也就是想要擴大或縮小現有指針指向的區(qū)塊仑扑,是先申請一塊額外的新塊區(qū)览爵,然后釋放舊塊區(qū),而不是在原有區(qū)域上操作镇饮。
完整的源碼包括測試用例蜓竹,參考 git

參考下一部分

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盒让,隨后出現的幾起案子梅肤,更是在濱河造成了極大的恐慌,老刑警劉巖邑茄,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姨蝴,死亡現場離奇詭異,居然都是意外死亡肺缕,警方通過查閱死者的電腦和手機左医,發(fā)現死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來同木,“玉大人浮梢,你說我怎么就攤上這事⊥罚” “怎么了秕硝?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洲尊。 經常有香客問我远豺,道長奈偏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任躯护,我火速辦了婚禮惊来,結果婚禮上,老公的妹妹穿的比我還像新娘棺滞。我一直安慰自己裁蚁,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布继准。 她就那樣靜靜地躺著枉证,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锰瘸。 梳的紋絲不亂的頭發(fā)上刽严,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天昂灵,我揣著相機與錄音避凝,去河邊找鬼。 笑死眨补,一個胖子當著我的面吹牛管削,可吹牛的內容都是我干的。 我是一名探鬼主播撑螺,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼含思,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甘晤?” 一聲冷哼從身側響起含潘,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎线婚,沒想到半個月后遏弱,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡塞弊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年漱逸,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片游沿。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡饰抒,死狀恐怖,靈堂內的尸體忽然破棺而出诀黍,到底是詐尸還是另有隱情袋坑,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布眯勾,位于F島的核電站枣宫,受9級特大地震影響疆柔,放射性物質發(fā)生泄漏。R本人自食惡果不足惜镶柱,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一旷档、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧歇拆,春花似錦鞋屈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至输吏,卻和暖如春权旷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贯溅。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工拄氯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人它浅。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓译柏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姐霍。 傳聞我的和親對象是個殘疾皇子鄙麦,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容

  • 一、內存模型 對于一個C語言程序而言镊折,內存空間主要由五個部分組成 代碼段(text)胯府、數據段(data)、未初始化...
    Leung_ManWah閱讀 2,353評論 1 28
  • 前言 C語言作為一門應用途廣泛恨胚、功能強大骂因、使用靈活的面向過程式編程語言。既可用于編寫應用軟件与纽,又能用于編寫系統(tǒng)軟件...
    老板娘來盤一血閱讀 12,985評論 32 83
  • 內存空間 進程空間圖示 棧內存(Stack) 棧中可以存放任意類型的變量, 即自動類型的局部變量, 隨用隨開,用完...
    低頭看云閱讀 473評論 0 0
  • 1.源碼文件如何變成可執(zhí)行文件(*) 需要以下4個步驟:(1)預處理階段:預處理器根據以#開頭的指令侣签,修改主要包括...
    __bba3閱讀 363評論 0 1
  • 動態(tài)內存分配 我們需要動態(tài)內存分配的原因 C語言中的一切操作都是基于內存的 變量和數組都是內存的別名,如何分配這些...
    PcDack閱讀 797評論 2 1