王寶明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的地址。