1. 程式人生 > >王寶明C語言視訊教程學習筆記--DYA5

王寶明C語言視訊教程學習筆記--DYA5

malloc與free產生野指標

malloc的記憶體在使用後,程式設計師需要手動釋放記憶體,但是往往會存在下例中重複釋放的情況,從而導致宕機。

void main()

{

    char *p = NULL;

    p = (char *)malloc(100); //char p[100];

    strcpy(p, "abcdefg");

    //做業務

    //此處省略5000字。。。。。

    if (p != NULL)

    {

        free(p);

    }

    //做業務

    //此處省略5000字。。。。。

    if (p != NULL)

    {

        free(p);

    }

    system("pause");

}

上例中,p已經被釋放了一次,記憶體雖然釋放了,但是指標指向的地址依然沒有變,後面還是通過p != NULL來判斷是不合理的。

正確的方法如下:

1 定義指標時 把指標變數賦值成null

2 釋放記憶體時,先判斷指標變數是否為null

3 釋放記憶體完畢後,把指標變數重新賦值成null

再看看一個比較有代表性的例子。

這個例子中,已經按照上面的規則,free記憶體後,對指標進行了NULL賦值,但是當執行到68行,即第二次呼叫FreeMem2()時還是出現了宕機,那究竟是什麼原因呢?

答案是在54行中,對p=NULL值改變了形參p指向的地址,當時實參沒有發生任何變化,第二次呼叫FreeMem2(),依舊是把那塊已經釋放的記憶體地址傳給了形參,它的值並不是NULL,因此導致了重複free記憶體導致宕機。

可以使用二級指標來解決這個問題。

void getMem4(int count, char **p /*out*/)

{

       char *tmp = NULL;

       tmp = (char *)malloc(100 * sizeof(char)); //char tmp[100];    

       *p = tmp;

}

int FreeMem3(char **p)

{

       if (*p == NULL)

       {

              return -1;

       }

       if (*p != NULL)

       {

              free(*p);

              *p = NULL; //

       }

       return 0;

}

void main()

{

       char *myp = NULL;

       getMem4(100, &myp);

       //做業務操作

       FreeMem3(&myp);

       FreeMem3(&myp);

       system("pause");

}

上例中,FreeMem3()呼叫了兩次也不會導致宕機。

結構體定義及記憶體分配

// 這個定義了一個數據型別,沒有分配記憶體。

// 捆綁分配,捆綁釋放

// 記憶體自己對齊

struct Teacher

{

       char name[64];

       int age;

};

void main()

{

       // Teacher t1; //告訴c++編譯器分配記憶體 //在臨時區

       struct Teacher t1;

       printf("sizeof(t1) = %d \n", sizeof(t1));

       system("pause");

}

執行後,結構如下。

32位系統分配記憶體時,一個變數不會被拆成兩部分分別放在兩個不同的4Byte的。

一下有幾種不同的情況,紅色的數字為sizeof(t1)的結果。

從上面看,變數不僅要記憶體對齊,還是按照順序分配地址的。

定義結構體的3中方法

1)先宣告結構體型別,再定義該型別的變數

struct Teacher

{

       char name[62];

       char a;       

       int age;

       char b;

};

struct Teacher t1, t2;

2)在宣告型別的同時定義變數

struct Teacher

{

       char name[62];

       char a;       

       int age;

       char b;

} t1, t2;

3)不指定型別名而直接定義結構體型別標量

struct

{

       char name[62];

       char a;       

       int age;

       char b;

} t1, t2;

第三種方法與一種類似,但是這種方法制定了一個無名的結構體型別,她沒有名字(不出現結構體名),顯然不能再以此結構體型別去定義其它變數。

結構體賦值

t2 = t1;這句話是什麼意思?是把t1變數的地址賦給了t2,還是把t1記憶體的資料拷貝到t2的記憶體中呢?

下面通過除錯來揭曉答案,執行21行程式碼前後,t1、t2記憶體變化如下。

執行後

可以看出,t2的地址沒有變化,這是記憶體資料與t1一樣了。

下面在看一個通過函式來賦值結構體。

void copyStruct(Teacher *to, Teacher *from)

{

       *to = *from;

}

void copyStruct2(Teacher to, Teacher from)

{

       to = from;

}

void main()

{

       Teacher t1, t2, t3;

       strcpy(t1.name, "Tom");

       t1.age = 10;

       t2.age = 20;

       t3.age = 40;

       copyStruct(&t2, &t1);

       copyStruct2(t3, t1);

       printf("t2.age = %d\n", t2.age);

       printf("t3.age = %d\n", t3.age);  

       system("pause");

}

先想一想,copyStruct()、copyStruct2()那個函式可以達到對t2、t3成功賦值的功能。

執行結果如下。

這裡就不解釋了,仔細想一想吧。

結構體裡面的成員若為指標,使用前一定要先分配記憶體

看下面一個例子。

#include "stdlib.h"

#include "stdio.h"

#include "string.h"

//結構體的定義

typedef struct _Teacher

{

    char name[64];

    char *tile;

    int age;

}Teacher ;

int printTArray(Teacher *tArray, int num)

{

    int i = 0;

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

    {

        printf("%d %s %s \n", tArray[i].age, tArray[i].name, tArray[i].tile);

    }

    return 0;

}

//

int sortTArray(Teacher *tArray, int num)

{

    int i , j = 0;

    Teacher tmp;

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

    {

        for (j=i+1; j<num; j++)

        {

            if (tArray[i].age > tArray[j].age)

            {

                tmp = tArray[i]; //編譯器給我們提供的行為

                tArray[i] = tArray[j];

                tArray[j] = tmp;

            }

        }

    }

    return 0;

}

Teacher *creatTArray2(int num)

{

    int i = 0;

    Teacher *tArray = NULL;

    tArray = (Teacher *)malloc(num * sizeof(Teacher));

    if (tArray == NULL)

    {

        return NULL;

    }

    return tArray;

}

void main()

{

    //定義一個結構體陣列,給結構體陣列元素賦值,給結構題排序。。。。列印

    int i = 0;

    int ret = 0;

    Teacher *pArray = NULL;

    pArray = creatTArray2(3);

    if (pArray == NULL)

    {

        return ;

    }

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

    {

        printf("請鍵入第%d個老師的年齡:", i+1);

        scanf("%d", &pArray[i].age);

        printf("請鍵入第%d個老師的姓名:", i+1);

        scanf("%s", pArray[i].name);

        printf("請鍵入第%d個老師的職稱:", i+1);

        scanf("%s", pArray[i].tile); // 注意這裡

    }

    printf("排序之前。。。。\n");

    printTArray(pArray, 3);

    sortTArray(pArray, 3);

    printf("排序之後。。。。\n");

    printTArray(pArray, 3);

    system("pause");

}

上例中,當執行到scanf("%s", pArray[i].tile)時直接宕機,原因是pArray[i].tile分配了記憶體,當時pArray[i].tile指向的記憶體並沒有分配記憶體,因此寫入資料直接就崩潰了。

因此,在為結構體分配記憶體是,如果成員有指標變數,不管是幾級指標均需要提前分配好記憶體,以免使用的時候出現意外情況。

creatTArray2( )的正確寫法如下:

Teacher *creatTArray2(int num)

{

    int i = 0;

    Teacher *tArray = NULL;

    tArray = (Teacher *)malloc(num * sizeof(Teacher));

    if (tArray == NULL)

    {

        return NULL;

    }

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

    {

        tArray[i].tile = (char *)malloc(100);

    }

    return tArray;

}

結構體的淺copy與深copy

看下面一個例子:

#include "stdlib.h"

#include "stdio.h"

#include "string.h"

//結構體的定義

typedef struct _AdvTeacher

{

       char *name;

       char buf[100];

       int age;

}Teacher ;

void FreeT(Teacher *t)

{

       if (t == NULL)

       {

              return ;

       }

       if (t->name != NULL)

       {

              free(t->name);

       }

}

//解決方案

int copyObj(Teacher *to, Teacher *from)

{

       //*to = *from;//copy;

       memcpy(to, from, sizeof(Teacher));

       to->name = (char *)malloc(100);

       strcpy(to->name, from->name);

}

void main()

{

       Teacher t1;

       Teacher t2;

       t1.name = (char *)malloc(100);

       t1.age = 10;

       //t2 = t1;//淺copy;

       copyObj(&t2, &t1);// 深copy

       if (t1.name != NULL)

       {

              free(t1.name );

       }

       if (t2.name != NULL)

       {

              free(t2.name );

       }

       system("pause");

}

當t2 = t1;進行copy時,執行程式會宕機;

當執行copyObj(&t2, &t1);設計,程式執行正常。

產生的原因:

編譯器給我們提供的copy行為是一個淺copy。

  • 當結構體成員域中含有buf的時候,沒有問題,因為buf的記憶體分配在結構體的記憶體空間內;

  • 當結構體成員域中還有指標的函式,編譯器只會進行指標變數的copy,指標變數所指的記憶體空間,編譯器不會進行任何操作。

總的來說,編譯器只對指標標量存放的地址負責,對指標變數指向的記憶體不負責。

結構體的進一步思考

執行下面這段程式碼,會不會報錯?不會報錯。

//結構體的定義

typedef struct _AdvTeacher

{

       char *name; //4

       int age2 ;

       char buf[32];  //32

       int age; //4

}Teacher ;

void main()

{

       int i = 0;

       Teacher * p = NULL;

       p = p - 1;

       p = p - 2;

       p = p +2;

       p = p -p;

       i = (int) (&(p->age)); //1邏輯計算在cpu中,運算

    //&屬於cpu的計算,沒有讀寫記憶體,所以說沒有coredown

       printf("i:%d \n", i);

         i = (int )&(((Teacher *)0)->age );

        printf("i:%d \n", i);

       system("pause");

}

&屬於cpu的計算,沒有讀寫記憶體,所以說沒有coredown。

那上面的操作究竟是什麼意思呢?往下看。

執行後結果如下:

原因分析:

  • 結構體的記憶體長度為44Byte。p初始地址為0x00,經過+1、+2後,p指向的地址為44*(1+2)= 132 = 0x84。

  • “->”的本質是定址,尋每一個成員相對於結構體變數的記憶體偏移。

  • age這個變數在“name”、“age2”、“buf[32]”之後,這幾個變數佔用的記憶體空間的長度為4+4+32 = 40(0x28),所以p->age的地址為0x84+0x28 = 0xAC(172)。

  • 最後通過(int)強制型別轉換將地址轉化為整形資料。

在這個過程中,只進行了定址操作,沒有對記憶體內的資料進行讀寫,因此,即使結構體沒有分配記憶體空間,執行程式也沒有報錯。

在看下面這段程式碼,本質上與上面是一樣的。

i = (int )&(((Teacher *)0)->age );

printf("i:%d \n", i);

執行後 i = 40 。

(Teacher *)0) 的意思是將地址為0x00記憶體強制轉為 Teacher *型指標,然後通過->操作符找到age的地址。