1. 程式人生 > >C之動態內存分配(三十四)

C之動態內存分配(三十四)

C語言 malloc free calloc realloc

在一般的程序中,我們難免會遇到動態的申請內存,那麽動態內存分配的意義到底是什麽呢?在 C 語言中的一切操作都是基於內存的,變量和數組都是內存的別名。內存分配由編譯器在編譯期間決定,定義數組的時候必須指定數組長度,數組長度當然也是在編譯期就必須確定的。

那麽為什麽會有動態分配內存的需求呢?在程序運行的過程中,可能需要使用一些額外的內存空間。我們都是在 C 語言中使用 malloc 來動態申請內存的,當時釋放的時候是用 free,下來我們看看 malloc 和 free 用於執行動態內存分配和釋放時是怎樣進行的,用下圖進行說明

技術分享圖片

我們可以看到程序通過 malloc 在內存池中進行申請,那麽歸還則是通過 free 進行釋放的。如果我們只是 malloc 進行申請而不 free,那麽我們的內存池將會被用完,那麽程序也就崩潰了。這就是我們平時所說的內存泄漏。

malloc 所分配的是一塊連續的內存,它是以字節為單位並且不帶任何的類型信息free 則是用於將動態內存歸還系統。這兩個函數的原型是 void* malloc(size_t size); void free(void* pointer);我們得註意這麽幾點:a> malloc 和 free 是庫函數,而不是系統調用;b> malloc 實際分配的內存可能會比請求的多;c> 不能依賴於不同平臺下的 malloc 行為;d> 當請求的動態內存無法滿足時,malloc 返回 NULL;e> 當 free 的參數為 NULL時,函數直接返回

那麽我們接下來思考下,malloc(0) 將返回什麽?是會報錯?還是啥也不做?還是會出現不確定的結果?我們做下實驗看看

#include <stdio.h>

int main()
{
    int* p = (int*)malloc(0);
    
    printf("p = %p\n", p);
    
    return 0;
}

我們看看編譯結果

技術分享圖片

我們看到編譯器給出警告了,但是還是成功執行了。其實我們平時所說的內存有兩個概念,一個是它的起始地址,一個是大小。在這塊我們就好解釋了,malloc(0) 只是申請的內存大小為0而已,但是它還會有起始地址。所以如果當我們在程序中無限次的 malloc(0) 時,程序最終會崩潰,因為它的地址信息也會占用空間。

下來我們再看一個代碼,是唐長老從實際工程中抽象出來的內存檢測模塊


test.c 源碼

#include <stdio.h>
#include "mleak.h"

void f()
{
    MALLOC(100);
}

int main()
{
    int* p = (int*)MALLOC(3 * sizeof(int));
    
    f();
    
    p[0] = 1;
    p[1] = 2;
    p[2] = 3;
    
    FREE(p);
    
    PRINT_LEAK_INFO();
    
    return 0;
}


mleak.h 源碼

#ifndef _MLEAK_H_
#define _MLEAK_H_

#include <malloc.h>

#define MALLOC(n) mallocEx(n, __FILE__, __LINE__)
#define FREE(p) freeEx(p)

void* mallocEx(size_t n, const char* file, const line);
void freeEx(void* p);
void PRINT_LEAK_INFO();

#endif


mleak.c 源碼

#include "mleak.h"

#define SIZE 256

/* 動態內存申請參數結構體 */
typedef struct
{
    void* pointer;
    int size;
    const char* file;
    int line;
} MItem;

static MItem g_record[SIZE]; /* 記錄動態內存申請的操作 */

void* mallocEx(size_t n, const char* file, const line)
{
    void* ret = malloc(n); /* 動態內存申請 */
    
    if( ret != NULL )
    {
        int i = 0;
        
        /* 遍歷全局數組,記錄此次操作 */
        for(i=0; i<SIZE; i++)
        {
            /* 查找位置 */
            if( g_record[i].pointer == NULL )
            {
                g_record[i].pointer = ret;
                g_record[i].size = n;
                g_record[i].file = file;
                g_record[i].line = line;
                break;
            }
        }
    }
    
    return ret;
}

void freeEx(void* p)
{
    if( p != NULL )
    {
        int i = 0;
        
        /* 遍歷全局數組,釋放內存空間,並清除操作記錄 */
        for(i=0; i<SIZE; i++)
        {
            if( g_record[i].pointer == p )
            {
                g_record[i].pointer = NULL;
                g_record[i].size = 0;
                g_record[i].file = NULL;
                g_record[i].line = 0;
                
                free(p);
                
                break;
            }
        }
    }
}

void PRINT_LEAK_INFO()
{
    int i = 0;
    
    printf("Potential Memory Leak Info:\n");
    
    /* 遍歷全局數組,打印未釋放的空間記錄 */
    for(i=0; i<SIZE; i++)
    {
        if( g_record[i].pointer != NULL )
        {
            printf("Address: %p, size:%d, Location: %s:%d\n", g_record[i].pointer, g_record[i].size, g_record[i].file, g_record[i].line);
        }
    }
}

我們看到在 test.c 中第6行 f() 函數中動態申請了內存,但是沒有進行釋放。由於是局部的,當這個函數調用完後,將產生內存泄漏。那麽我們在第 21 行將會打印出信息。我們這個對應的函數是怎麽實現的呢,在 mleak.c 中將申請得到的內存地址放入一個數組中,在後面會進行檢查,如果進行 FREE 操作,便會在數組中對應的刪除標記,否則標記存在。如果標記存在,我們則會打印出對應的信息來。我們來看看編譯結果

技術分享圖片

我們看到在地址為 0x9d13018 處存在100大小的內存沒進行釋放,它位於 test.c 的第6行。下來我們註釋掉 teat.c 中的第19行,看看這個內存沒進行釋放是否會打印出來

技術分享圖片

我們看到一樣的打印出來了。證明我們這個內存泄漏的檢測模塊還是很準的。

下來我們在來看看 calloc 和 realloc,它們是 malloc 的同胞兄弟,原型分別為:void* calloc(size_t num, size_t size); void* realloc(void* pointer, size_t new_size);那麽 calloc 的參數代表所返回內存的類型信息,其中 calloc 會將返回的內存初始化為 0;realloc 用於修改一個原先已經分配 的內存塊大小,在使用 realloc 之後應該使用其返回值,當 pointer 的第一個參數為 NULL 時,等價於 malloc。

下來我們以代碼為例進行分析

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

#define SIZE 5

int main()
{
    int i = 0;
    int* pI = (int*)malloc(SIZE * sizeof(int));
    short* pS = (short*)calloc(SIZE, sizeof(short));
    
    for(i=0; i<SIZE; i++)
    {
        printf("pI[%d] = %d, pS[%d] = %d\n", i, pI[i], i, pS[i]);
    }
    
    printf("Before: pI = %p\n", pI);
    
    pI = (int*)realloc(pI, 2 * SIZE * sizeof(int));
    
    printf("After: pI = %p\n", pI);
    
    for(i=0; i<10; i++)
    {
        printf("pI[%d] = %d\n", i, pI[i]);
    }
    
    free(pI);
    free(pS);
    
    return 0;
}

我們看看編譯結果

技術分享圖片

按照我們前面講的,數組 pI 中的數應該是隨機數的,數組 pS 的數是被初始化為 0 的。可是現在全是0,別著急,這只是 gcc 中做的優化。我們看到數組 pI 在調用 realloc 後的大小確實改變了,並且地址也變了。下來我們看看 BCC 編譯器中是怎樣的

技術分享圖片

我們看到數組 pI 中的數確實是隨機數了,而數組 pS 中的數依舊全是 0。通過本節對動態內存分配的學習,總結如下:1、動態內存分配是 C 語言中的強大功能,程序能夠在需要的時候有機會使用更多的內存;2、malloc 單純的從系統中申請固定字節大小的內存,calloc 能以類型大小為單位申請內存並初始化為0,realloc 用於重置內存大小。


歡迎大家一起來學習 C 語言,可以加我QQ:243343083

C之動態內存分配(三十四)