邏輯上的分區
- 棧區
- 堆區
- 靜態區
- 常量區
- 代碼區
代碼區,常量區,靜態區,堆區,棧區這個排列順序按照地址由小到大排列的
代碼區:
只讀的
程序員無需對其操作,里邊放著代碼編譯之后形成的二進制
常量區:
只讀的
例如"acfun",'Q',5,等等的常量,放在常量區
char *p ="bilibili";
任何試圖修改常量區內容的操作,都會造成內存崩潰
char arr[] ="bilibili";
arr[0] = 'I';
在使用指針操作字符串的時候一定注意內存分區的問題
strcpy(p, "123");
p[0] = 'I';
printf("%p\n",p);
靜態區:
使用static關鍵字修飾的變量放在靜態區,放在靜態區的變量只被初始化一次
static int a = 10;
- 如果不給初始值,默認相當于賦值0
static int b;//默認相當于給b = 0;
- 使用static修飾的靜態變量生命周期等于整個程序的生命周期,即程序退出后才被釋放(iOS編程寫單例類的時候,要使用static)
int all = 10;//全局變量,實際上也放在靜態區
printf("%p\n",&a);//代碼區,常量區,靜態區,棧區,的所占空間并不大
printf("%p\n",&all);//里邊局部變量0x7fff5fbff7b0
printf("%p\n",&all);//外邊全局變量0x100001020
(Stack)棧區:
一塊連續的內存,由系統自動分配和釋放,不需要程序員手動管理,大小也就1M-2M
他是有自己管理的規則的:棧區內存的分配按照先進后出的原則
int a = 10;//先初始化的a
int b = 20;//后初始化的b
printf("%p\n",&a);//地址高
printf("%p\n",&b);//地址低
之前所學的代碼其實都放在棧區,只不過沒有出現棧區溢出的問題
堆區:
內存比較大的空間都被堆區占用著,堆區是不連續的內存空間,需要咱們程序員手動的分配和釋放內存
定義一個指針
int *p =malloc(4);
malloc就是程序員手動分配內存空間的函數,函數的參數放著我們需要的內存空間這塊空間就是堆內存的一塊空間
printf("%p\n",p);//堆內存地址
printf("%d\n",*p);//堆內存里邊的值
釋放空間
free(p);
p = NULL;//讓p指向地址0x0.
課上練習
輸入三個單詞保存在堆內存并輸出
char *word[3] = {0};//指針數組
char temp1[100] = {0};//用來保存每一個單詞
printf("請輸入三個單詞:\n");
for(int i = 0; i < 3; i++) {
scanf("%s",temp1);
word[i] =malloc(sizeof(temp1));//指向堆內存地址
strcpy(word[i], temp1);
}
printf("**********************\n");
for(int i = 0; i < 3; i++) {
printf("%s\n",word[i]);
free(word[i]);//若多釋放一塊未開辟的空間,程序會崩潰掉,這種情況稱為過度釋放
word[i] =NULL;
}
內存分配函數
malloc和free…這些函數維護一個可用內存池,并向該程序返回一個指向這塊內存的指針。這塊內存此時此刻并沒有以任何方式進行初始化。如果這個內存很重要你可以手動進行初始化,要么使用calloc函數。
malloc函數
函數原型
void *malloc(size_t size);
- size就是需要分配的內存字節(字符)數。如果內存池中的內存可以滿足這個要求,malloc就返回一個指向被分配內存的起始位置的指針。
- malloc所分配的是一塊連續的內存。
- 如果內存池為空,malloc函數會向操作系統請求。如果操作系統無法向malloc提供更多的內存,malloc就會返回一個NULL指針。因此要對malloc返回的指針進行檢查,確保其非NULL
- malloc返回一個void *的指針。標準的void *指針可以轉換為其他任何類型的指針。但是,在某些(老式)編譯器可能需要你在轉換時使用強制類型轉換
- 對于要求邊界對齊的機器,malloc所返回的內存的起始位置將始終能夠滿足對邊界對齊要求最嚴格的類型的要求。
char *p1 =malloc(4);
for(int i = 0; i < 4; i++) {
*(p1+i) = 65 + i;
printf("%c\n",*(p1+i));
}
free(p1);
p1 =NULL;
free函數
函數原型
void free(void *pointer);
- free的參數要么是NULL,要么是先前從malloc、calloc或realloc返回的值。
- 向free傳遞一個NULL參數不會產生任何效果
calloc函數
函數原型
void *calloc(size_t num_elements,size_t element_size);
- calloc也用于分配內存。malloc和calloc之間的主要區別是后者返回指向內存的指針之前把它初始化為0。但顯得效率很低。
- malloc和calloc它倆請求內存數量的方式不同。calloc的參數包括所需元素的數量和每個元素的字節數。根據這些值,可以計算出總共需要分配的內存。
char *p2 =calloc(3, 1);第一個參數是分配多少個,第二個參數是每個有多少個字節
for(int i = 0; i < 3; i++) {
*(p2+i) = 'a';
printf("%c\n",*(p2+i));
}
free(p2);
p2 =NULL;
練習
使用calloc函數分配十塊四個字節的空間,存放十個隨機數取值范圍在10 - 30之間,然后打印
char *p3 =calloc(10, 4);
for(inti = 0; i < 10; i++) {
*(p3+i) =arc4random()%21 + 10;
printf("%d\n",*(p3+i));
}
free(p3);
realloc函數
函數原型
void realloc(void *ptr,size_t new_size);
- realloc函數用于修改一個原先已經分配的內存塊的大小。這個函數可以使一塊內存擴大或縮小。
- 如果擴大,那么這塊內存原先的內容依然保留,新增加的內存添加到原先內存塊的后面,新內存并未以任何方法進行初始化。
- 如果縮小,該內存塊尾部的部分內存便被拿掉,剩余部分內存的原先內容依然保留。
- 如果原先的內存塊無法改變大小,realloc將分配另一塊正確大小的內存,并把原先那塊內存的內容復制到新的塊上。因此,在使用realloc之后,你就不能再使用指向舊內存的指針,而應該改用realloc所返回的新指針。
- 如果realloc函數的第一個參數是NULL,那么它的行為就和malloc一模一樣。
int *p4 =malloc(4);
int *p5 =realloc(p4, 8);給p4重新分配了8個字節的空間
*p5 = 10;
*(p5+1) = 20;
*p4 = 11;
*(p4+1) = 21;
printf("%d\n%d\n",*p5,*(p5+1));
printf("%d\n%d\n",*p4,*(p4+1));
memset內存設置函數
int *p6 =malloc(4);
注意這里聲明的是整型
memset(p6, 65, 16);第一個參數放要操作的指針,第二個參數放內容(整型或者字符型),第三個參數放字節數
for(inti = 0; i < 4; i++) {
printf("%c\n",*(p6+i));
}
memcpy內存內容拷貝的函數
char*p7 =malloc(4);
memcpy(p7, p6, 5);
for(inti = 0; i < 4; i++) {
// *(p7 + i) = 66;
printf("%c\n",*(p7+i));
}
內存的比較函數
跟字符串比較函數一樣,比較里邊的內容,找到第一個不相同的字符,按照ascii碼表值進行作差
int count =memcmp(p7, p6, 5);
printf("%d\n",count);
動態內存分配
int *pi;
pi = malloc(100);
if(pi == NULL){
printf("Out of memory!\n");
exit(1);
}
如果分配內存成功,我們就擁有了一個指向100個字節的指針。在整型為4個字節的機器上,這塊內存將被當作25個整型數組元素的數組,因為pi是一個指向整型的指針。
技巧
pi = malloc(25*sizeof(int));
常見動態分配錯誤
定義一個不易發生錯誤的內存分配器
#define malloc //防止由于其他代碼塊直接塞入程序而導致偶爾直接調用maloc的行為。增加這個指令后,如果程序偶爾調用了malloc,程序將由于語法錯誤無編譯。
#define MALLOC(num,type) (type*)alloc((num)*sizeof(type))
extern void *alloc(size_t size);
- 忘記檢查內存是否分配成功
- 被訪問的內存可能保存了其他變量的值
- 傳遞給free的指針必須是從malloc、calloc或realloc函數返回的指針。