目錄
一、memcpy庫函數介紹
1.?memcpy的使用
2. memcpy的模擬
二、memmove庫函數介紹
1. memmove的使用
2. memmove的模擬
三、memset庫函數介紹
四、memcmp庫函數介紹
五、動態內存中malloc和free
1. malloc
2. free
六、動態內存中calloc和realloc
1. calloc
2. realloc
七、柔性數組
1.1 柔性數組特點
1.2 柔性數組的使用
1.3 柔性數組與結構體內部成員指針區別
八、動態內存中使用常見錯誤
1.?對NULL指針的解引?操作
2.?對動態開辟空間的越界訪問
3.?對?動態開辟內存使?free釋放
4.?使?free釋放?塊動態開辟內存的?部分
5.?對同?塊動態內存多次釋放
6.?動態開辟內存忘記釋放(內存泄漏)
九、關于動態內存易錯例題
1. 題目一
2. 題目二
3. 題目三
4. 題目四
十、總結c/c++中程序內存區域劃分圖
一、memcpy庫函數介紹
1.?memcpy的使用
? ? ? ? memcpy這個庫函數用于是任何類型,將一個地址的內容復制到到另一個地址上,它是針對于內存的修改,使用使用過程中是memcpy(arr1(拷貝地址),arr2(從這里抄內容到arr1中),size_t(字節個數)),需要頭文件string.h,返回的是arr1這個已經拷貝的地址。
它遇到\0不會停止,它只受字節個數的影響,它針對的是內存的修改,也就是一個一個字節的修改。注意這里是適用于不重疊的內存。
2. memcpy的模擬
? ? ? ? 根據以上的信息,可以知道memcpy的修改是通過一個一個字節的修改而以小改大。
#include<stdio.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{char* ret = dest;//記入起始地址//拷貝while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;//void*類型不能改變值,需要強制類型轉換src = (char*)src + 1;}return ret;
}
int main()
{int arr1[20];int arr2[] = { 0,1,2,3,4,5,6,7 };void* ret = my_memcpy(arr1, arr2, sizeof(int) * 5);for (int i = 0; i < 5; i++){printf("%d ", ((int*)ret)[i]);}return 0;
}
?
二、memmove庫函數介紹
1. memmove的使用
? ? ? ? memmove和memcpy的使用是一樣的,不過它專門用于重疊內存的拷貝。
2. memmove的模擬
? ? ? ? menmove的情況就比較多了,因為對于重疊的內存,那就得考慮第一種是與需要拷貝字節內有重疊部分,這時候就是類型指針用于放入拷貝的內容地址比另一個指針類型用于輸s出內容到拷貝指針小(也就是改變后,對后面拷貝沒有影響)第二種就是需要拷貝的字節內容有重疊部分,此時類型指針用于放入拷貝的內容地址比另一個指針類型用于輸出內容到拷貝指針大(也就是改變后,對后面拷貝有影響),第三種就是拷貝字節不重疊(完全錯開),這個是較為簡單。直接上圖來解釋
#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, void* src, size_t num)
{assert(dest && src);void* ret = dest;//記入起始//判斷重疊條件//第一種和第三種的一部分,從前往后直接替換即可if (dest < src){while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;//一個一個字節改變src = (char*)src + 1;}}//第二種和第三種的一部分,從后到前else{while (num--){*((char*)dest + num) = *((char*)src + num);//一步到位,用num來控制循環,還能使得從后到前改變每個字節//這里要注意的是一開始加的是19,而不是20,所以這里是num--}}return ret;
}
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,0 };void* ret = my_memmove(arr + 2, arr, sizeof(int) * 5);return 0;
}
?
對比memmove,模擬出來的恰好的正確的。這里需要注意的就是num--,為什么要這樣設計。?
三、memset庫函數介紹
????????memset雖然是內存的修改,但是對于整型修改時,可能會不符合預期,memset的使用格式為(void*)memset(void*(地址),int(修改值字符或者數字),size_t(需要修改的字節個數)),返回的是修改后的起始地址。上面也說了,memset是一個一個字節的修改對于要修改的值,所以對于數字整型4個字節的修改時,就不會符合預期。
每個字節都會修改成1,而讀取整型時是通過4個字節4個字節的讀取的,所以這里數字就會0x01010101 這16進制數字將會是很大的數,一般我們修改整型時是將其改為0。不過這里對于是一個字節類型,就沒有影響,修改為什么值就是什么值。
四、memcmp庫函數介紹
? ? ? ? memcmp是用來對比內存中對應的數字大小(這里字符也是數字,因為字符在內存中是以ascll值存儲的),用法就是(int)memcmp(void* prt1,void* prt2,size_t(需要對比的字節個數))。
#include<stdio.h>
#include<string.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 1,2,3,4,0x80000001 };//這里以小端存儲,所以這里在內存存儲是0x01 00 00 80//實際打印時還是打印出0x80 00 00 01int r = memcmp(arr1, arr2, 17);if (r > 0){printf("arr1 > arr2\n");}else if (r < 0){printf("arr1 < arr2\n");}else{printf("arr1 == arr2\n");}return 0;
}
這里是最好反映出memcmp的內部比較的形式,這里是以小端存儲。對整型在內存的存儲有興趣的可以看這里?
??整數和浮點數在內存中的存儲-CSDN博客
五、動態內存中malloc和free
1. malloc
? ? ? ? malloc這個庫函數是用與分配空間內存,需要頭文件stdlib.h,用法為(void*)malloc (size_t(申請字節)),這里申請成功返回該空間的起始地址,如果申請失敗則返回空指針,這里分配的是連續可用的空間。
#include<stdio.h>
#include<stdlib.h>
int main()
{//開辟空間int* p = (int*)malloc(sizeof(int) * 10);if (p == NULL){perror("malloc");return 1;}//使用空間for (int i = 0; i < 10; i++){p[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", p[i]);}//釋放空間free(p);p = NULL;return 0;
}
2. free
? ? ? ? 這里free這個用法很簡單,就是用來釋放空間的函數,就好比圖書館借書,借了書肯定是要換的,這里開辟空間就類似借書這個過程,還書就是釋放空間。(void) free(void* prt),內部是指向需要釋放內存的這個空間,而prt指的是這個需要釋放內存的這個空間的起始地址。這里必須是開辟的空間,否則會報錯,如果釋放的是空指針,那什么free什么都不做。
這里需要注意的是free釋放完后,要養成一個好習慣,就是將這個指針變為空指針,因為后續如果要使用時,直接使用,那就是野指針的泛濫了。
六、動態內存中calloc和realloc
1. calloc
? ? ? ? calloc這個庫函數和malloc這個類似,也是開辟空間的一個庫函數,格式有所不同,就是(void*) calloc((元素個數),size_t(一個元素的字節)),都是開辟空間,只不過calloc開辟一個空間后是有自己的初始化內容的,這個初始化內容全為0;要說還有一個不一樣的就是語法格式吧,malloc直接就是申請字節個數,而calloc是需要知道什么類型,該類型需要多少個元素。
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p1 = (int*)calloc(10, sizeof(int));if (p1 == NULL){perror("calloc");return 1;}//使用空間//釋放空間free(p1);p1 = NULL;return 0;
}
左圖為calloc這個庫函數開辟的空間,右邊為malloc這個庫函數開辟的空間。?
2. realloc
? ? ? ? realloc這個用于擴容或縮減內存的大小,可以看需求給他增減空間大小,用法(void*)realloc((void*prt)表示需要擴容或縮減的地址,(size_t)表示擴容或者縮減后的大小)。如果擴容成功則會返回擴容成功后的起始地址并拷貝了擴容之前地址的內容,如果擴容縮減失敗則會返回NULL空指針。這里擴容有2種情況
#include<stdio.h>
#include<stdlib.h>
int main()
{//開辟空間int* p = malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");return 1;}//使用空間for (int i = 0; i < 5; i++){p[i] = i + 1;}//不夠擴容int* prt = (int*)realloc(p, sizeof(int) * 10);if (prt != NULL){p = prt;//將擴容的地址賦給p(這里主要是怕第二種情況)prt = NULL;}else{perror("realloc");return 1;}//使用//釋放空間free(p);p = NULL;return 0;
}
realloc也可以實現malloc功能,realloc(NULL,40)等價于開辟40個字節空間,返回開辟后的空間。 這里如果是縮減空間,縮減過多可能會導致某個溢出字節數據丟失。
七、柔性數組
1.1 柔性數組特點
? ? ? ? 柔性數組是指結構體中成員最后一項為一個數組并且還要是未知數組(沒有[]大小),特點為:不參與整個結構體的大小(前提是還沒給它開辟空間。就是整個結構體大小不算這個數組大小)。
#include<stdio.h>
struct Stu1
{int age;char name;
};
struct Stu2
{int age;char name;int arr[];
};
int main()
{printf("%zd\n", sizeof(struct Stu1));printf("%zd\n", sizeof(struct Stu2));return 0;
}
?
1.2 柔性數組的使用
? ? ? ? 柔性數組的使用一般配合動態內存函數一起使用,可以隨時動態改變所需的大小,一般開辟空間大于結構體總大小,
#include<stdio.h>
#include<stdlib.h>
struct Stu
{int age;char name;int num[];
};
int main()
{//開辟空間struct Stu* s1 = (struct Stu*)malloc(sizeof(struct Stu) + sizeof(int) * 5);if (s1 == NULL){perror("malloc");return 1;}//使用s1->age = 18;s1->name = 'A';for (int i = 0; i < 5; i++){s1->num[i] = i + 1;}//空間不夠struct Stu* ps = (struct Stu*)realloc(s1, sizeof(struct Stu) + sizeof(int) * 10);if (ps == NULL){perror("realloc");return 1;}//繼續使用for (int i = 5; i < 10; i++){ps->num[i] = i + 1;}//釋放free(ps);free(s1);s1 = NULL;ps = NULL;return 0;
}
1.3 柔性數組與結構體內部成員指針區別
? ? ? ? 直接代碼展示結構體內部成員為指針是如何和柔性數組一樣操作的。
#include<stdio.h>
#include<stdlib.h>
struct Stu
{int age;char name;int* arr;
}*s1;
int main()
{//開辟s1 = (struct Stu*)malloc(sizeof(struct Stu));if (s1 == NULL){perror("malloc1");return 1;}//使用s1->age = 18;s1->name = 'A';s1->arr = (int*)malloc(sizeof(int) * 5);//申請if (s1->arr == NULL){perror("malloc2");return 1;}//使用for (int i = 0; i < 5; i++){s1->arr[i] = i + 1;}//擴容int* prt = (int*)realloc(s1->arr, sizeof(int) * 10);if (prt == NULL){perror("realloc");return 1;}else{s1->arr = prt;prt = NULL;}//使用//釋放free(s1->arr);//先釋放內部再釋放外部s1->arr = NULL;free(s1);s1 = NULL;return 0;
}
一般我們都是使用柔性數組,這種用指針的代碼量太大了,而且需要開辟兩個連續的空間,合起來就不連續,會造成很多內存碎片(表示開辟空間過多,介于兩個連續空間內部剩余的空間為內存碎片)。柔性數組的優點就是1.內存釋放過程中方便,不像指針需要釋放兩次,而且還有順序可言。2.有利于內存訪問(連續內存便于訪問)。?
八、動態內存中使用常見錯誤
1.?對NULL指針的解引?操作
? ? ? ? 開辟空間時,如果不判斷是否為空指針,并且阻斷后面進程,那就會報警告。
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){return 1;//必要性,否則會報錯}//使用//釋放free(p);p = NULL;return 0;
}
2.?對動態開辟空間的越界訪問
? ? ? ? 對于已經開辟的空間,使用了未開辟的空間,會導致越界訪問。
#include<stdio.h>
#include<stdlib.h>
int main()
{//開辟int* p = (int*)malloc(sizeof(int) * 10);if (p == NULL){perror("malloc");return 1;}//使用for (int i = 0; i <= 10; i++){*(p + i) = i + 1;//i=10時越界了}for (int i = 0; i <= 10; i++){printf("%d ", p[i]);}//釋放free(p);p = NULL;return 0;
}
3.?對?動態開辟內存使?free釋放
? ? ? ? 對于不是動態函數開辟的內存,使用free釋放,會導致程序錯誤。
#include<stdio.h>
#include<stdlib.h>
int main()
{int arr[10] = { 0 };int* p = (int*) & arr;free(p);p = NULL;return 0;
}
?
4.?使?free釋放?塊動態開辟內存的?部分
? ? ? ? 我們知道free(*p)括號里面指向的是開辟空間,而p是開辟空間的起始地址,而釋放一部分地址,也會導致程序錯誤,不能正常工作。
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");return 1;}p++;free(p);p = NULL;return 0;
}
5.?對同?塊動態內存多次釋放
? ? ? ? 對一個已經開辟的動態內存多次釋放。
#include<stdio.h>
#include<stdlib.h>
int main()
{//開辟int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");return 1;}//使用//釋放free(p);free(p);p = NULL;return 0;
}
6.?動態開辟內存忘記釋放(內存泄漏)
? ? ? ? 在函數中開辟空間,如果不釋放空間并且不返回該空間起始地址,會導致這一塊空間浪費,既不能使用,也不能再次開辟這一塊空間。
#include<stdio.h>
#include<stdlib.h>
void test()
{int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");p = NULL;}//忘記釋放
}
int main()
{test();return 0;
}
九、關于動態內存易錯例題
1. 題目一
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void GetMemory(char* p)
{p = (char*)malloc(100);//1.沒有對NULL解引判斷用//2.內存泄漏(沒有釋放空間)
}
void Test(void)
{char* str = NULL;GetMemory(str);//這里是傳值調用strcpy(str, "hello world");//str為空指針printf(str);
}
int main()
{Test();return 0;
}
根據錯誤信息修改:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void GetMemory(char** p)
{*p = (char*)malloc(100);if (*p == NULL)//判斷{perror("malloc");return;}
}
void Test(void)
{char* str = NULL;GetMemory(&str);//傳址strcpy(str, "hello world");printf(str);free(str);//釋放str = NULL;
}
int main()
{Test();return 0;
}
2. 題目二
#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{char p[] = "hello world";//函數中的局部變量在棧區離開后,會釋放內存return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();//str已經改變//但是使用過程中會變為野指針printf(str);
}
int main()
{Test();return 0;
}
3. 題目三
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);//未釋放
}
int main()
{Test();return 0;
}
4. 題目四
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);//野指針使用(未置空指針)if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}
十、總結c/c++中程序內存區域劃分圖
