[源碼下載]
作者:webabcd
介紹
不可或缺 Windows Native 之 C 語言
示例
cMemory.h
#ifndef _MYHEAD_MEMORY_ #define _MYHEAD_MEMORY_ #ifdef __cplusplus extern "C" #endif char *demo_cMemory(); #endif
cMemory.c
/*
* 動態分配內存,鏈表,位域
*/
#include "pch.h"
#include "cMemory.h"
#include "cHelper.h"
void alloc_demo1();
void alloc_demo2();
void chain_demo();
void bitfield_demo();
char *demo_cMemory()
{
// 動態分配內存基礎 1
alloc_demo1();
// 動態分配內存基礎 2
alloc_demo2();
// 鏈表
chain_demo();
// 位域(所謂位域,就是可以按 bit 存放數據)
bitfield_demo();
return "看代碼及注釋吧";
}
// 動態分配內存基礎 1
void alloc_demo1()
{
// malloc - 分配指定大小的連續的內存區域,並返回該區域的首地址,返回值的類型是 void* (具體類型需要用戶自己指定)
// calloc - 分配 n 塊,每塊長度為指定大小的連續內存區域,並返回該區域的首地址,返回值的類型是 void* (具體類型需要用戶自己指定)
// free - 釋放由 malloc 或 calloc 函數所分配的內存區域(必須的)
char *s = "i am webabcd";
// 開一個 100 字節大小的連續內存區域,並將其強制轉換為字符數組類型,函數的返回值為數組首地址
char *p1 = (char *)malloc(100);
// 把 s 所指的內容復制到 p1 所指的內存區域
strncpy(p1, s, strlen(s) + 1);
// 釋放掉由 malloc 開辟的,p1 所指的內存空間
free(p1);
// 下面這段是不對的,因為開辟的內存空間不夠,賦值的時候雖然正常,但是 free 的時候會報錯
/*
char *p2 = (char *)malloc(1);
strncpy(p2, s, strlen(s) + 1);
free(p2); // 這裡會報錯
*/
// 下面這段是不對的,因為雖然為 p3 動態開辟了內存空間,但是卻將一個常量賦值給 p3,也就是說 p3 並沒有使用我們動態開辟的內存空間
/*
char *p3 = (char *)malloc(100);
p3 = "webabcd";
free(p3); // 這裡會報錯
*/
// 開一個 100 塊的,每塊長度為 1 字節大小的連續內存區域,並將其強制轉換為字符數組類型,函數的返回值為數組首地址
char *p4 = (char *)calloc(100, 1);
// 把 s 所指的內容復制到 p4 所指的內存區域
strncpy(p4, s, strlen(s) + 1);
// 釋放掉由 calloc 開辟的,p4 所指的內存空間
free(p4);
}
// 動態分配內存基礎 2
void alloc_demo2()
{
char *s = "i am webabcd";
char *p = (char *)malloc(100 * sizeof(char));
// 申請內存時有可能失敗,失敗時會返回 NULL('\0'),這個判斷是必須的
if (p == NULL)
{
printf("內存分配出錯!");
exit(1); // exit(0) - 告訴系統我是正常退出;非 0 值則是告訴系統我是非正常退出
}
strncpy(p, s, strlen(s) + 1);
// 一定要釋放(釋放指針所指的動態分配的內存空間)
free(p);
// 釋放後,指針置空(將指針本身置空,即空指針)是好習慣,防止野指針
p = NULL;
}
/*
* 關於鏈表的概念
*
* 什麼是數據域:在結點結構中用於存放實際數據的區域稱為“數據域”
* 什麼是指針域:在結點結構中定義一個成員項用來存放下一結點的首地址,這個用於存放地址的成員就稱為“指針域”
*
* 什麼是鏈表:在第一個結點的指針域內存入第二個結點的首地址,在第二個結點的指針域內存入第三個結點的首地址,如此串連下去直到最後一個結點。
* 最後一個結點因無後續結點連接,其指針域一般賦值為 '\0'。這樣一種連接方式,在數據結構中稱為“鏈表”。
* 鏈表中的每個結點都分為兩個域,一個是數據域,另一個域為指針域。
* 指向整個鏈表的指針一般稱之為“頭結點”,其存放的其實就是第一個結點的首地址。
*/
// 定義一個名為 employee 的結構體(用於演示“單向鏈表”)
struct employee
{
int num;
char *name;
struct employee *next; // 指針域,用於存放下一個節點的首地址
// struct employee *prev; // 如果需要“雙向鏈表”的話,可以在這個指針域中存放上一個節點的首地址
};
void chain_demo()
{
// 創建一個有 100 個 employee 結點的鏈表
struct employee *creat(int n);
struct employee *employee_chain = creat(100);
// 如果不用了,別忘了 free 掉鏈表
struct employee *temp;
while (employee_chain)
{
temp = employee_chain;
employee_chain = employee_chain->next;
free(temp);
}
}
// 創建一個指定結點數的鏈表(n 是指定的節點數)
struct employee *creat(int n)
{
struct employee *head = NULL, *p1 = NULL, *p2 = NULL;
for (int i = 0; i<n; i++)
{
p2 = (struct employee *)malloc(sizeof(struct employee));
p2->num = i;
p2->name = "webabcd";
if (i == 0)
head = p1 = p2;
else
p1->next = p2;
p2->next = NULL;
p1 = p2;
}
return head;
}
// 位域(所謂位域,就是可以按 bit 存放數據)
void bitfield_demo()
{
// 注:以下如果有列出結果的,均為我的環境的結果。比如我這裡 int 是占用 4 個字節的
// 1、如果相鄰位域成員的類型相同,且每個成員的位長之和不大於類型的 sizeof 大小,則後面的成員將緊鄰前一個成員存儲
struct bf // 此位域類型占用 4 個字節
{
int a : 1; // 位長為 1,它存儲在 4 字節的第 1 位
int b : 3; // 位長為 3,它存儲在 4 字節的第 2 位到第 4 位
int c : 7; // 位長為 7,它存儲在 4 字節的第 5 位到第 11 位
} bf1;
// 2、如果相鄰位域成員的類型相同,且每個成員的位長之和大於類型的 sizeof 大小,則後面的成員如果緊鄰前一個成員存儲時超過類型 sizeof 大小了,則此成員會從新的存儲單元開始存儲(而不是緊鄰前一個成員存儲)
struct // 此位域類型占用 12 個字節
{
int a : 31; // 位長為 31,它存儲在第 1 個 4 字節的第 1 位到第 31 位
int b : 2; // 位長為 2,它存儲在第 2 個 4 字節的第 1 位到第 2 位(不會緊鄰前一個成員存儲)
int c : 31; // 位長為 31,它存儲在第 3 個 4 字節的第 1 位到第 31 位(不會緊鄰前一個成員存儲)
} bf2;
// 3、如果相鄰位域成員的類型不相同,則各編譯器的具體實現有差異,VC 采取不壓縮方式(不同的位域成員存放在不同的位域類型空間中);GCC 和其它都采取壓縮方式(參考第 1 知識點和第 2 知識點)
struct // 此位域類型占用 8 個字節
{
char c : 2;
int i : 4; // 和前一個成員的類型不同,會在新的空間中存儲(如果是 GCC 的話則會緊鄰存儲)
} bf3;
// 4、如果位域成員之間穿插著非位域成員,則不進行壓縮,即此非位域成員後面的位域成員不會與此非位域成員前面的位域成員緊鄰存儲
struct // 此位域類型占用 12 個字節
{
int a : 1; // 位長為 1,它存儲在第 1 個 4 字節的第 1 位
char x; // 非位域成員,它存儲在第 2 個 4 字節
int b : 3; // 位長為 3,它存儲在第 3 個 4 字節的第 1 位到第 3 位
int c : 7; // 位長為 7,它存儲在第 3 個 4 字節的第 4 位到第 10 位
} bf4;
// 5、空域 - 用於強制後面的位域從新的空間存儲
struct // 此位域類型占用 8 個字節
{
int a : 1; // 位長為 1,它存儲在第 1 個 4 字節的第 1 位
int : 0; // 空域,強制後面的位域從新的空間存儲
int b : 3; // 位長為 3,它存儲在第 2 個 4 字節的第 1 位到第 3 位
int c : 7; // 位長為 7,它存儲在第 2 個 4 字節的第 4 位到第 10 位
} bf5;
// 6、占位域 - 僅占位用,不可使用
struct // 此位域類型占用 4 個字節
{
int a : 1; // 位長為 1,它存儲在 4 字節的第 1 位
int : 2; // 占位域,僅占位用,不可使用,位長為 2,它占用了 4 字節的第 2 位到第 3 位
int b : 3; // 位長為 3,它存儲在 4 字節的第 4 位到第 6 位
int c : 7; // 位長為 7,它存儲在 4 字節的第 7 位到第 13 位
} bf6;
}
/*
* 關於內存洩漏注意事項
* 1、內存分配未成功,卻使用了它
* 2、內存分配雖然成功,但是尚未初始化就引用它
* 3、內存分配成功並且已經初始化,但操作越過了內存的邊界
* 4、忘記了釋放內存,造成內存洩露
* 5、釋放了內存卻繼續使用它
*
*
* 關於內存的幾點注意
* 1、如果在函數內使用了動態分配內存的話,那麼函數結束後,其是不會自動消亡的,必須要手動 free 掉。也就是說凡是動態 alloc 的內存,必須要手動 free 掉
* 2、動態分配內存後,如果只是對相應的指針賦值 NULL,那樣內存是不會釋放的(現代語言中,有引用計數器的概念,如果一塊區域無人引用的話就會被釋放或者被 GC 釋放)
*
*
* 關於內存區域的類型
* 1、棧:在棧裡面存儲的是局部變量以及形參
* 2、字符常量區:用於存儲字符常量,比如:char *p = "webabcd"; 其中 "webabcd" 就存儲在字符常量區裡面
* 3、全局區:用於存儲全局變量和靜態變量
* 4、堆:在堆裡面存儲的主要是通過動態分配的內存空間
*
*
* 本例講的是內存的動態分配
* 1、什麼是靜態分配:是指在程序運行期間分配固定的存儲空間的方式(可以這麼理解:編譯器在編譯時會對類似 int i; 這樣的代碼生成一種內存分配方案並將其寫入編譯後的文件,等到運行時就按指定的方案分配)
* 2、什麼是動態分配:是在程序運行期間根據需要進行動態的分配存儲空間的方式
*/
OK
[源碼下載]