C語言動態內存的申請和釋放


什么是動態內存的申請和釋放?

 

     當程序運行到需要一個動態分配的變量時,必須向系統申請取得堆中的一塊所需大小的存儲空間,用於存儲該變量。當不再使用該變量時,也就是它的生命結束時,要顯式釋放它所占用的存儲空間,這樣系統就能對該堆空間進行再次分配,做到重復使用有限的資源。

下面將介紹動態內存申請和釋放的函數

1.malloc函數

 

在C語言中,使用malloc函數來申請內存。函數原型如下:

#include<stdlib.h>

void *malloc(size_t size);

參數size代表需要動態申請的內存的字節數,若內存申請成功,函數返回申請到的內存的起始地址,若申請失敗,返回NULL, 在使用該函數時應注意以下幾點

1.只關心申請內存的大小,該函數的參數很簡單,只有申請內存的大小,單位是字節

2.申請的是一塊連續的內存,該函數一定是申請一塊連續的區間,可能申請到內存比實際申請的大,但也有可能申請不到,若申請失敗,則返回NULL

3.返回值類型是void*,函數的返回值是void*,不是某種具體類型的指針,可以理解成該函數只是申請內存,對在內存中存儲什么類型的數據,沒有要求,因此,返回值是void*,實際編程中,根據實際情況將void*轉換成需要的指針類型

4.顯示初始化,注意:堆區是不會自動在分配時做初始化的(包括清),所以程序中需要顯示的初始化

 

 

2.free 函數

在堆區上分配的內存,需要用free函數顯示釋放。函數原型如下:

#include <stdlib.h>

void free(void *ptr);

函數的參數ptr,指的是需要釋放的內存的起始地址。該函數沒有返回值。使用該函數,也有下面幾點需要注意:

(1)必須提供內存的起始地址。調用該函數時,必須提供內存的起始地址,不能提供部分地址,釋放內存中的一部分是不允許的。因此,必須保存好malloc返回的指針值,若丟失,則所分配的堆空間無法回收,稱內存泄漏。

(2)malloc和free配對使用。編譯器不負責動態內存的釋放,需要程序員顯示釋放。因此,mallocfree是配對使用的,避免內存泄漏。

 

示例程序如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int *get_memory(int n){

    int *p, i;

    if ((p = (int *)malloc(n * sizeof(int))) == NULL)    {

        printf("malloc error\n");

        return p;

    }

    memset(p, 0, n * sizeof(int));

    for (i = 0; i < n; i++)

        p[i] = i+1;

       return p;

}

int main(){

    int n, *p, i;

    printf("input n:");

    scanf("%d", &n);

    if ((p = get_memory(n)) == NULL){

        return 0;

    }

    for (i = 0; i < n; i++){

           printf("%d ", p[i]);

    }

    printf("\n");

    free(p);

    p = NULL;

    return 0;

 

}

程序執行結果如下:

linux@ubuntu:~/book/ch10$ cc malloc.c -Wall

linux@ubuntu:~/book/ch10$./a.out

input n:10

1 2 3 4 5 6 7 8 9 10

該程序演示了動態內存的標准用法。動態內存的申請,通過一個指針函數來完成。內存申請時,判斷是否申請成功,成功后,對內存初始化。在主調函數中,動態內存依然可以訪問,不再訪問內存時,用free函數釋放。

(3)不允許重復釋放。同一空間的重復釋放也是危險的,因為該空間可能已另分配。在上面程序中,如果釋放堆空間兩次(連續調用兩次free(p)),會出現下面的結果。

linux@ubuntu:~/book/ch10$ cc malloc.c –Wall

linux@ubuntu:~/book/ch10$./a.out

input n:1

1

*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x08f1a008 ***

======= Backtrace: =========

/lib/libc.so.6(+0x6c501)[0x687501]

/lib/libc.so.6(+0x6dd70)[0x688d70]

/lib/libc.so.6(cfree+0x6d)[0x68be5d]

./a.out[0x804861e]

/lib/libc.so.6(__libc_start_main+0xe7)[0x631ce7]

./a.out[0x8048471]

======= Memory map: ========

0061b000-00772000 r-xp 00000000 08:01 1048623    /lib/libc-2.12.1.so

00772000-00773000 ---p 00157000 08:01 1048623   /lib/libc-2.12.1.so

00773000-00775000 r--p 00157000 08:01 1048623    /lib/libc-2.12.1.so

00775000-00776000 rw-p 00159000 08:01 1048623   /lib/libc-2.12.1.so

00776000-00779000 rw-p 00000000 00:00 0

008e1000-008fb000 r-xp 00000000 08:01 1048657   /lib/libgcc_s.so.1

008fb000-008fc000 r--p 00019000 08:01 1048657    /lib/libgcc_s.so.1

008fc000-008fd000 rw-p 0001a000 08:01 1048657   /lib/libgcc_s.so.1

00a8f000-00aab000 r-xp 00000000 08:01 1048599   /lib/ld-2.12.1.so

00aab000-00aac000 r--p 0001b000 08:01 1048599 /lib/ld-2.12.1.so

00aac000-00aad000 rw-p 0001c000 08:01 1048599   /lib/ld-2.12.1.so

00b6c000-00b6d000 r-xp 00000000 00:00 0          [vdso]

08048000-08049000 r-xp 00000000 08:01 1079938    /home/linux/book/ch10/a.out

08049000-0804a000 r--p 00000000 08:01 1079938    /home/linux/book/ch10/a.out

0804a000-0804b000 rw-p 00001000 08:01 1079938   /home/linux/book/ch10/a.out

08f1a000-08f3b000 rw-p 00000000 00:00 0          [heap]

b7700000-b7721000 rw-p 00000000 00:00 0

b7721000-b7800000 ---p 00000000 00:00 0

b7815000-b7816000 rw-p 00000000 00:00 0

b7823000-b7827000 rw-p 00000000 00:00 0

bf9a5000-bf9c6000 rw-p 00000000 00:00 0           [stack]

Aborted

(4)free只能釋放堆空間。像代碼區、全局變量與靜態變量區、棧區上的變量,都不需要程序員顯示釋放,這些區域上的空間,不能通過free函數來釋放,否則執行時,會出錯。

示例程序如下:

#include <stdlib.h>

int main(){

    int a[10] = {0};

    free(a);

    return 0;

}

程序執行結果如下:

linux@ubuntu:~/book/ch10$ cc free.c –o free -Wall

free.c: In function 'main':

free.c:7: warning: attempt to free a non-heap object 'a'

可以看到有一個警告,即釋放一個非堆上的空間。如果強行執行程序,會出現下面的結果:

linux@ubuntu:~/book/ch10$./a.out

Segmentation fault

 

3.野指針

 

野指針指的是指向“垃圾”內存的指針,不是NULL指針。出現“野指針主要有以下原因:

1)指針變量沒有被初始化。指針變量和其它的變量一樣,若沒有初始化,值是不確定的。也就是說,沒有初始化的指針,指向的是垃圾內存,非常危險。

示例程序如下:

#include <stdio.h>

int main(){

    int *p;

    printf("%d\n", *p);

    *p = 10;

    printf("%d\n", *p);

    return 0;

}

 

程序執行結果如下:

linux@ubuntu:~/book/ch10$ cc p.c –o p -Wall

linux@ubuntu:~/book/ch10$./p 

1416572

Segmentation fault

(2)指針pfree之后,沒有置為NULLfree函數是把指針所指向的內存釋放掉,使內存成為了自由內存。但是,該函數並沒有把指針本身的內容清楚。指針仍指向已經釋放的動態內存,這是很危險。程序員稍有疏忽,會誤以為是個合法的指針。就有可能再通過指針去訪問動態內存。實際上,這時的內存已經是垃圾內存了,關於野指針會造成什么樣的后果,這是很難估計的。若內存仍然是空閑的,可能程序暫時正常運行;若內存被再次分配,又通過野指針對內存進行了寫操作,則原有的合法數據,會被覆蓋,這時,野指針造成的影響將是無法估計的。

 

示例程序如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(){

    int n = 5, *p, i;

    if ((p = (int *)malloc(n * sizeof(int))) == NULL) {

        printf("malloc error\n");

        return 0;

    }

    memset(p, 0, n * sizeof(int));

    for (i = 0; i < n; i++) {

        p[i] = i+1;

        printf("%d ", p[i]);

    }

    printf("\n");

    printf("p=%p *p=%d\n", p, *p);

    free(p);

    printf("after free:p=%p *p=%d\n", p, *p);

    *p = 100;

    printf("p=%p *p=%d\n", p, *p);

    return 0;

}

 

程序執行結果如下:

linux@ubuntu:~/book/ch10$cc test.c –o test -Wall 

linux@ubuntu:~/book/ch10$./test 

1 2 3 4 5

p=0x92cf008 *p=1

after free:p=0x92cf008 *p=0

p=0x92cf008 *p=100

該程序中,故意在執行了“free(p)”之后,通過野指針p對動態內存進行了讀寫,程序正常執行,也在預料之中。前面已經分析過,內存釋放后,若繼續訪問甚至修改,后果是不可預料的。

3)指針操作超越了變量的作用范圍。指針操作時,由於邏輯上的錯誤,導致指針訪問了非法內存,這種情況讓人防不勝防,只能依靠程序員好的編碼風格,已及扎實的基本功。下面演示一個指針操作越界的情況:

示例程序如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(){

    int a[5] = {1, 9, 6, 2, 10}, *p, i, n;

    n = sizeof(a) / sizeof(n);

    p = a;

    for (i = 0; i <= n; i++) {

       printf("%d ", *p);

        p++;

    }

    printf("\n");

    *p = 100;

    printf("*p=%d\n", *p);

    return 0;

}

 

程序執行結果如下:

linux@ubuntu:~/book/ch10$ cc test.c –o test -Wall

linux@ubuntu:~/book/ch10$./test

1 9 6 2 10 5

*p=100

該程序故意出了兩個錯誤,一是for循環的條件“i <= n”,p指針指向了數組以外的空間。二是“*p = 100”,對非法內存進行了寫操作。

4不要返回指向棧內存的指針。指針函數會返回一個指針。在主調函數中,往往會通過返回的指針,繼續訪問指向的內存。因此,指針函數不能返回棧內存的起始地址,因為棧內存在函數結束時會被釋放。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM