溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解C語言中的動態(tài)內(nèi)存分配

發(fā)布時間:2021-11-25 14:15:17 來源:億速云 閱讀:184 作者:柒染 欄目:編程語言

如何理解C語言中的動態(tài)內(nèi)存分配,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

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

        那么為什么會有動態(tài)分配內(nèi)存的需求呢?在程序運(yùn)行的過程中,可能需要使用一些額外的內(nèi)存空間。我們都是在 C 語言中使用 malloc 來動態(tài)申請內(nèi)存的,當(dāng)時釋放的時候是用 free,下來我們看看 malloc 和 free 用于執(zhí)行動態(tài)內(nèi)存分配和釋放時是怎樣進(jìn)行的,用下圖進(jìn)行說明

如何理解C語言中的動態(tài)內(nèi)存分配

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

        malloc 所分配的是一塊連續(xù)的內(nèi)存,它是以字節(jié)為單位并且不帶任何的類型信息。free 則是用于將動態(tài)內(nèi)存歸還系統(tǒng)。這兩個函數(shù)的原型是 void* malloc(size_t size); void free(void* pointer);我們得注意這么幾點(diǎn):a> malloc 和 free 是庫函數(shù),而不是系統(tǒng)調(diào)用;b> malloc 實(shí)際分配的內(nèi)存可能會比請求的多;c> 不能依賴于不同平臺下的 malloc 行為;d> 當(dāng)請求的動態(tài)內(nèi)存無法滿足時,malloc 返回 NULL;e> 當(dāng) free 的參數(shù)為 NULL時,函數(shù)直接返回。

        那么我們接下來思考下,malloc(0) 將返回什么?是會報錯?還是啥也不做?還是會出現(xiàn)不確定的結(jié)果?我們做下實(shí)驗(yàn)看看

#include <stdio.h>

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

        我們看看編譯結(jié)果

如何理解C語言中的動態(tài)內(nèi)存分配

        我們看到編譯器給出警告了,但是還是成功執(zhí)行了。其實(shí)我們平時所說的內(nèi)存有兩個概念,一個是它的起始地址,一個是大小。在這塊我們就好解釋了,malloc(0) 只是申請的內(nèi)存大小為0而已,但是它還會有起始地址。所以如果當(dāng)我們在程序中無限次的 malloc(0) 時,程序最終會崩潰,因?yàn)樗牡刂沸畔⒁矔加每臻g。

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

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

/* 動態(tài)內(nèi)存申請參數(shù)結(jié)構(gòu)體 */
typedef struct
{
    void* pointer;
    int size;
    const char* file;
    int line;
} MItem;

static MItem g_record[SIZE]; /* 記錄動態(tài)內(nèi)存申請的操作 */

void* mallocEx(size_t n, const char* file, const line)
{
    void* ret = malloc(n); /* 動態(tài)內(nèi)存申請 */
    
    if( ret != NULL )
    {
        int i = 0;
        
        /* 遍歷全局?jǐn)?shù)組,記錄此次操作 */
        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;
        
        /* 遍歷全局?jǐn)?shù)組,釋放內(nèi)存空間,并清除操作記錄 */
        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");
    
    /* 遍歷全局?jǐn)?shù)組,打印未釋放的空間記錄 */
    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() 函數(shù)中動態(tài)申請了內(nèi)存,但是沒有進(jìn)行釋放。由于是局部的,當(dāng)這個函數(shù)調(diào)用完后,將產(chǎn)生內(nèi)存泄漏。那么我們在第 21 行將會打印出信息。我們這個對應(yīng)的函數(shù)是怎么實(shí)現(xiàn)的呢,在 mleak.c 中將申請得到的內(nèi)存地址放入一個數(shù)組中,在后面會進(jìn)行檢查,如果進(jìn)行 FREE 操作,便會在數(shù)組中對應(yīng)的刪除標(biāo)記,否則標(biāo)記存在。如果標(biāo)記存在,我們則會打印出對應(yīng)的信息來。我們來看看編譯結(jié)果

如何理解C語言中的動態(tài)內(nèi)存分配

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

如何理解C語言中的動態(tài)內(nèi)存分配

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

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

        下來我們以代碼為例進(jìn)行分析

#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;
}

        我們看看編譯結(jié)果

如何理解C語言中的動態(tài)內(nèi)存分配

        按照我們前面講的,數(shù)組 pI 中的數(shù)應(yīng)該是隨機(jī)數(shù)的,數(shù)組 pS 的數(shù)是被初始化為 0 的。可是現(xiàn)在全是0,別著急,這只是 gcc 中做的優(yōu)化。我們看到數(shù)組 pI 在調(diào)用 realloc 后的大小確實(shí)改變了,并且地址也變了。下來我們看看 BCC 編譯器中是怎樣的

如何理解C語言中的動態(tài)內(nèi)存分配

我們看到數(shù)組 pI 中的數(shù)確實(shí)是隨機(jī)數(shù)了,而數(shù)組 pS 中的數(shù)依舊全是 0。

總結(jié)如下:

1、動態(tài)內(nèi)存分配是 C 語言中的強(qiáng)大功能,程序能夠在需要的時候有機(jī)會使用更多的內(nèi)存;

2、malloc 單純的從系統(tǒng)中申請固定字節(jié)大小的內(nèi)存,calloc 能以類型大小為單位申請內(nèi)存并初始化為0,realloc 用于重置內(nèi)存大小。

看完上述內(nèi)容,你們掌握如何理解C語言中的動態(tài)內(nèi)存分配的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI