黑马程序员C语言基础(第七天)内存管理
黑馬程序員C語言基礎(第一天)
黑馬程序員C語言基礎(第二天)
黑馬程序員C語言基礎(第三天)
黑馬程序員C語言基礎(第四天)數據類型
黑馬程序員C語言基礎(第五天)運算符與表達式、程序流程結構、數組和字符串、函數
黑馬程序員C語言基礎(第六天)指針
黑馬程序員C語言基礎(第七天)內存管理
視頻地址
文章目錄
- 內存管理
- 作用域
- 8.1.1 局部變量(auto自動變量)
- 8.1.2 靜態(static)局部變量
- 普通局部變量和靜態(static)局部變量的區別
- 8.1.3 全局變量
- 跨文件使用全局變量示例(沒用頭文件)
- 跨文件使用全局變量示例(用頭文件)
- 8.1.4 靜態(static)全局變量
- 8.1.5 extern全局變量聲明
- 8.1.6 全局函數(普通函數)和靜態函數(static函數)
- 8.1.7 總結
- 8.2 內存布局
- 8.2.1 內存分區(text代碼區、data區、bss區、棧區、堆區)
- 8.2.2 存儲類型總結(有用!)
- 棧越界:指分配的內存空間超出了棧的范圍(ulimit -a)
- 8.2.3 存儲類型總結內存操作函數
- 1) memset()(將s的內存區域的前n個字節以字符c填入)(注意是以字節為單位填入,一個字節填入一個字符的ascii碼)
- 2) memcpy()(拷貝src所指的內存內容的前n個字節到dest所值的內存地址上)(即使遇到 \0 也能拷貝)
- 3) memmove()(內存重疊時代替memcpy()使用)(內存重疊:使用memcpy拷貝的過程中原數組值發生變化,使得結果不正確)(踩內存)
- 4) memcmp()(主要用來比較兩個地址前n個字節是否相等)
- 8.2.4 堆區內存分配和釋放
- 1)malloc()
- 2)free()
- 8.3 內存分區代碼分析(在Linux下測試)
- 1) 返回棧區地址
- 2) 返回data區地址
- 3) 值傳遞1
- 4) 值傳遞2
- 5) 返回堆區地址(較上面兩種完美)
內存管理
作用域
C語言變量的作用域分為:
- 代碼塊作用域(代碼塊是{}之間的一段代碼)
- 函數作用域
- 文件作用域
8.1.1 局部變量(auto自動變量)
局部變量也叫auto自動變量(auto可寫可不寫),一般情況下代碼塊{}內部定義的變量都是自動變量,它有如下特點:
- 在一個函數內定義,只在函數范圍內有效
- 在復合語句中定義,只在復合語句中有效
- 隨著函數調用的結束或復合語句的結束局部變量的聲明聲明周期也結束
- 如果沒有賦初值,內容為隨機
注意:auto關鍵字在c++ .cpp文件下不能識別
#include <stdio.h>void test() {//auto寫不寫是一樣的//auto只能出現在{}內部auto int b = 10; }int main(void) {//b = 100; //err, 在main作用域中沒有bif (1){//在復合語句中定義,只在復合語句中有效int a = 10;printf("a = %d\n", a);}//a = 10; //err離開if()的復合語句,a已經不存在return 0; }8.1.2 靜態(static)局部變量
- static局部變量的作用域也是在定義的函數內有效
- static局部變量的生命周期和程序運行周期一樣,同時staitc局部變量的值只初始化一次(如:static int i =10;只能運行一次),但可以賦值多次
- static局部變量若未賦以初值,則由系統自動賦值:數值型變量自動賦初值0,字符型變量賦空字符
結果:
i = 1 i = 1 a = 1 a = 2普通局部變量和靜態(static)局部變量的區別
8.1.3 全局變量
聲明用extern關鍵字聲明
跨文件使用全局變量也同樣要先聲明
- 在函數外定義,可被本文件及其它文件中的函數所共用,若其它文件中的函數調用此變量,須用extern聲明
- 全局變量的生命周期和程序運行周期一樣
- 不同文件的全局變量不可重名
跨文件使用全局變量示例(沒用頭文件)
main.cpp
#include <stdio.h>int main(void) {extern void test();test();extern int a;extern int b;a = 111;b = 222;printf("a = %d, b= %d\n", a, b);return 0; }fun.cpp
#include <stdio.h> #include <stdlib.h> #include <String.h>int a = 10; int b = 5; void test() {a = 1;b = 1; }運行結果:
a = 111, b= 222跨文件使用全局變量示例(用頭文件)
main.cpp
#include <stdio.h> #include "test.h"int main(void) {test();a = 111;b = 222;printf("a = %d, b= %d\n", a, b);return 0; }fun.cpp
#include <stdio.h> #include <stdlib.h> #include <String.h>int a = 10; int b = 5; void test() {a = 1;b = 1; }test.h
#pragma once extern void test(); extern int a; extern int b;運行結果:
a = 111, b= 2228.1.4 靜態(static)全局變量
- 在函數外定義,作用范圍被限制在所定義的文件中
- 不同文件靜態全局變量可以重名,但作用域不沖突
- static全局變量的生命周期和程序運行周期一樣,同時staitc全局變量的值只初始化一次
8.1.5 extern全局變量聲明
extern int a;聲明一個變量,這個變量在別的文件中已經定義了,這里只是聲明,而不是定義。
8.1.6 全局函數(普通函數)和靜態函數(static函數)
在C語言中函數默認都是全局的,使用關鍵字static可以將函數聲明為靜態,函數定義為static就意味著這個函數只能在定義這個函數的文件中使用,在其他文件中不能調用,即使在其他文件中聲明這個函數都沒用。
對于不同文件中的staitc函數名字可以相同。
注意:
- 允許在不同的函數中使用相同的變量名,它們代表不同的對象,分配不同的單元,互不干擾。
- 同一源文件中,允許全局變量和局部變量同名,在局部變量的作用域內,全局變量不起作用。
- 所有的函數默認都是全局的,意味著所有的函數都不能重名,但如果是staitc函數,那么作用域是文件級的,所以不同的文件static函數名是可以相同的。
8.1.7 總結
auto變量就是局部變量(普通變量),extern變量就是全局變量,extern函數就是普通函數
8.2 內存布局
size ./out
可以看區?
還真可以!
dontla@dontla-virtual-machine:~/桌面/test$ size testtext data bss dec hex filename1820 608 8 2436 984 test8.2.1 內存分區(text代碼區、data區、bss區、棧區、堆區)
C代碼經過預處理、編譯、匯編、鏈接4步后生成一個可執行程序。
在 Linux 下,程序是一個普通的可執行文件,以下列出一個二進制可執行文件的基本情況:
通過上圖可以得知,在沒有運行程序前,也就是說程序沒有加載到內存前,可執行程序內部已經分好3段信息,分別為代碼區(text)、數據區(data)和未初始化數據區(bss)3 個部分(有些人直接把data和bss合起來叫做靜態區或全局區)。
-
代碼區
存放 CPU 執行的機器指令。通常代碼區是可共享的(即另外的執行程序可以調用它),使其可共享的目的是對于頻繁被執行的程序,只需要在內存中有一份代碼即可。代碼區通常是只讀的,使其只讀的原因是防止程序意外地修改了它的指令。另外,代碼區還規劃了局部變量的相關信息。 -
全局初始化數據區/靜態數據區(data段)
該區包含了在程序中明確被初始化的全局變量、已經初始化的靜態變量(包括全局靜態變量和局部靜態變量)和常量數據(如字符串常量)。 -
未初始化數據區(又叫 bss 區)
存入的是全局未初始化變量和未初始化靜態變量。未初始化數據區的數據在程序開始執行之前被內核初始化為 0 或者空(NULL)。
程序在加載到內存前,代碼區和全局區(data和bss)的大小就是固定的,程序運行期間不能改變。然后,運行可執行程序,系統把程序加載到內存,除了根據可執行程序的信息分出代碼區(text)、數據區(data)和未初始化數據區(bss)之外,還額外增加了棧區、堆區。
-
代碼區(text segment)
加載的是可執行文件代碼段,所有的可執行代碼都加載到代碼區,這塊內存是不可以在運行期間修改的。 -
未初始化數據區(BSS)(Block Started by Symbol)
加載的是可執行文件BSS段,位置可以分開亦可以緊靠數據段,存儲于數據段的數據(全局未初始化,靜態未初始化數據)的生存周期為整個程序運行過程。 -
全局初始化數據區/靜態數據區(data segment)
加載的是可執行文件數據段,存儲于數據段(全局初始化,靜態初始化數據,文字常量(只讀))的數據的生存周期為整個程序運行過程。 -
棧區(stack)
棧是一種先進后出的內存結構,由編譯器自動分配釋放,存放函數的參數值、返回值、局部變量等。在程序運行過程中實時加載和釋放,因此,局部變量的生存周期為申請到釋放該段棧空間。 -
堆區(heap)
堆是一個大容器,它的容量要遠遠大于棧,但沒有棧那樣先進后出的順序。用于動態內存分配。堆在內存中位于BSS區和棧區之間。一般由程序員分配和釋放,若程序員不釋放,程序結束時由操作系統回收。
8.2.2 存儲類型總結(有用!)
#include <stdio.h> #include <stdlib.h>int e; static int f; int g = 10; static int h = 10; int main() {int a;int b = 10;static int c;static int d = 10;char *i = "test";char *k = NULL;printf("&a\t %p\t //局部未初始化變量\n", &a);printf("&b\t %p\t //局部初始化變量\n", &b);printf("&c\t %p\t //靜態局部未初始化變量\n", &c);printf("&d\t %p\t //靜態局部初始化變量\n", &d);printf("&e\t %p\t //全局未初始化變量\n", &e);printf("&f\t %p\t //全局靜態未初始化變量\n", &f);printf("&g\t %p\t //全局初始化變量\n", &g);printf("&h\t %p\t //全局靜態初始化變量\n", &h);printf("i\t %p\t //只讀數據(文字常量區)\n", i);k = (char *)malloc(10);printf("k\t %p\t //動態分配的內存\n", k);return 0; } &a 00D3FA00 //局部未初始化變量 &b 00D3F9F4 //局部初始化變量 &c 0118A18C //靜態局部未初始化變量 &d 0118A048 //靜態局部初始化變量 &e 0118A184 //全局未初始化變量 &f 0118A188 //全局靜態未初始化變量 &g 0118A040 //全局初始化變量 &h 0118A044 //全局靜態初始化變量 i 01187B30 //只讀數據(文字常量區) k 00FF04D0 //動態分配的內存棧越界:指分配的內存空間超出了棧的范圍(ulimit -a)
在linux下用ulimit -a指令查看各個空間大小:
可以看到棧的空間為8192kbytes,也就是8Mb
dontla@dontla-virtual-machine:~/桌面/test$ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 111644 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 111644 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited放visual studio上也會報錯:(但大小好像不太一樣?)
8.2.3 存儲類型總結內存操作函數
1) memset()(將s的內存區域的前n個字節以字符c填入)(注意是以字節為單位填入,一個字節填入一個字符的ascii碼)
#include <string.h> void *memset(void *s, int c, size_t n); 功能:將s的內存區域的前n個字節以參數c填入 參數:s:需要操作內存s的首地址c:填充的字符,c雖然參數為int,但必須是unsigned char , 范圍為0~255n:指定需要設置的大小 返回值:s的首地址 #include <stdio.h> #include <String.h>int main() {int a[10];memset(a, 0, sizeof(a));memset(a, 97, sizeof(a));//memset(a, 'a', sizeof(a));//都可以int i = 0;for (i = 0; i < 10; i++){printf("%c\n", a[i]);}return 0; }結果:
a a a a a a a a a a示例:以字節為單位初始化
2) memcpy()(拷貝src所指的內存內容的前n個字節到dest所值的內存地址上)(即使遇到 \0 也能拷貝)
#include <string.h> void *memcpy(void *dest, const void *src, size_t n); 功能:拷貝src所指的內存內容的前n個字節到dest所值的內存地址上。 參數:dest:目的內存首地址src:源內存首地址,注意:dest和src所指的內存空間不可重疊n:需要拷貝的字節數 返回值:dest的首地址示例1:
#include "cal.h" #include<stdio.h> int main() { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int b[10] = {0};memcpy(b, a, sizeof(a));for (int i = 0; i < 10; i++){printf("%d, ",*(b+i));}printf("\n");//memcpy(&a[3], a, 5 * sizeof(int)); //err, 內存重疊 }結果:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,示例2:
#include <stdio.h> #include <String.h>int main() {char p[] = "adfsfdsf\0assfs";//printf("%s\n", p);//adfsfdsf//printf("%d\n",sizeof(p));//15char buf[100];strncpy_s(buf, p, sizeof(p));printf("%s\n", buf);//adfsfdsfprintf("%s\n", buf + strlen("adfsfdsf") + 1);//無(說明strncpy沒法將\0后面的字符拷貝過去)memset(buf, 0, sizeof(buf));memcpy(buf, p, sizeof(p));printf("%s\n", buf);//adfsfdsfprintf("%s\n", buf + strlen("adfsfdsf") + 1);//assfsreturn 0; }3) memmove()(內存重疊時代替memcpy()使用)(內存重疊:使用memcpy拷貝的過程中原數組值發生變化,使得結果不正確)(踩內存)
memmove()功能用法和memcpy()一樣,區別在于:dest和src所指的內存空間重疊時,memmove()仍然能處理,不過執行效率比memcpy()低些。
4) memcmp()(主要用來比較兩個地址前n個字節是否相等)
#include <string.h> int memcmp(const void *s1, const void *s2, size_t n); 功能:比較s1和s2所指向內存區域的前n個字節 參數:s1:內存首地址1s2:內存首地址2n:需比較的前n個字節 返回值:相等:=0大于:>0小于:<0 #include <stdio.h> #include <String.h>int main() {int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int b[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int flag = memcmp(a, b, sizeof(a));printf("flag = %d\n", flag);//flag = 0 }8.2.4 堆區內存分配和釋放
1)malloc()
#include <stdlib.h> void *malloc(size_t size); 功能:在內存的動態存儲區(堆區)中分配一塊長度為size字節的連續區域,用來存放類型說明符指定的類型。分配的內存空間內容不確定,一般使用memset初始化。 參數:size:需要分配內存大小(單位:字節) 返回值: 成功:分配空間的起始地址 失敗:NULL #include <stdlib.h> #include <stdio.h> #include <string.h>int main() {int count, * array, n;printf("請輸入要申請數組的個數:\n");scanf_s("%d", &n);array = (int*)malloc(n * sizeof(int));if (array == NULL){printf("申請空間失敗!\n");return -1;}//將申請到空間清0memset(array, 0, sizeof(int) * n);for (count = 0; count < n; count++) /*給數組賦值*///array[count] = count;//提示波浪線?*(array + count) = count;//不提示for (count = 0; count < n; count++) /*打印數組元素*/printf("%2d", *(array+count));free(array);return 0; }結果:
請輸入要申請數組的個數: 60 1 2 3 4 5注意,。melloc(0)運行時不會報錯,但調試最后free空間時會報這樣的錯誤:
2)free()
#include <stdlib.h> void free(void *ptr); 功能:釋放ptr所指向的一塊內存空間,ptr是一個任意類型的指針變量,指向被釋放區域的首地址。對同一內存空間多次釋放會出錯。 參數: ptr:需要釋放空間的首地址,被釋放區應是由malloc函數所分配的區域。 返回值:無示例:
#include <stdio.h> #include <String.h> #include<stdlib.h> int main() {int* address = (int*)malloc(sizeof(int));if (address==NULL) {printf("分配失敗!\n");return -1;}else if (address!=NULL) {*address = 11;}printf("%d\n", *address);//11if (NULL!=address) {free(address);address = NULL;}//printf("%d\n", *address);//運行時不報錯,調試的時候報錯,address是空指針//*address = 12;//運行時不報錯,調試的時候報錯 }8.3 內存分區代碼分析(在Linux下測試)
1) 返回棧區地址
#include <stdio.h>int* fun() {int a = 10;return &a;//函數調用完畢,a釋放//通過visual studio調試發現,雖然a沒了,但是原先a指向的內存里的值還在,所以不會報錯,//但在linux,報錯顯示不能返回局部變量的地址return &a }int main(int argc, char* argv[]) {int* p = NULL;p = fun();*p = 100;printf("%d\n",*p);//100return 0; }2) 返回data區地址
#include <stdio.h>int* fun() {static int a = 10;return &a; //函數調用完畢,a不釋放 }int main(int argc, char* argv[]) {int* p = NULL;p = fun();*p = 100; //okprintf("*p = %d\n", *p);//*p = 100return 0; }3) 值傳遞1
示例1:值傳遞,形參修改不會影響實參
#include <stdio.h> #include <stdlib.h>void fun(int* tmp) {tmp = (int*)malloc(sizeof(int));if (NULL!=tmp) {*tmp = 100;} }int main(int argc, char* argv[]) {int a = 1;int* p = &a;fun(p); //值傳遞,形參修改不會影響實參printf("*p = %d\n", *p);//err,操作空指針指向的內存return 0; }示例2:
4) 值傳遞2
#include <stdio.h> #include <stdlib.h>void fun(int* tmp) {*tmp = 100; }int main(int argc, char* argv[]) {int* p = NULL;p = (int*)malloc(sizeof(int));fun(p); //值傳遞if (NULL!=p) {printf("*p = %d\n", *p); //ok,*p為100}return 0; }5) 返回堆區地址(較上面兩種完美)
#include <stdio.h> #include <stdlib.h>int* fun() {int* tmp = NULL;tmp = (int*)malloc(sizeof(int));if (NULL!=tmp) {*tmp = 100;}return tmp;//返回堆區地址,函數調用完畢,不釋放 }int main(int argc, char* argv[]) {int* p = NULL;p = fun();printf("*p = %d\n", *p);//ok//堆區空間,使用完畢,手動釋放if (p != NULL){free(p);p = NULL;}return 0; }https://www.bilibili.com/video/BV1jW411K7z8/?p=42&spm_id_from=pageDriver
總結
以上是生活随笔為你收集整理的黑马程序员C语言基础(第七天)内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 注意:C语言结构体里不能赋初始值!
- 下一篇: 黑马程序员C语言基础(第八天)复合类型(