日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

从内存分配角度分析c和java里的static 关键字.

發(fā)布時間:2025/3/20 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从内存分配角度分析c和java里的static 关键字. 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

??? 即使作為Java的初學(xué)者, 對this 和 static 這兩個關(guān)鍵字都不會陌生. 其實也不難理解:

??? this 關(guān)鍵字:? 指的是對象的本身(注意不是類本身)? 跟.net 語言的Me 關(guān)鍵字類似.

??? static 關(guān)鍵字: 靜態(tài)分配的對象或?qū)ο蟪蓡T.? 也就是指被static 修飾的成員只屬于類本身, 而不會想其他成員一樣會對每個對象單獨分配.

??? 但是c語言也有static關(guān)鍵字, 但是c語言中的static并不只是靜態(tài)分配的意思,如果用在靜態(tài)局部變量(函數(shù)內(nèi)部), 則是說明這個變量是靜態(tài)的,?? 如果用在全局變量或函數(shù), 則是防止函數(shù)或全程變量被其他c文件中的函數(shù)訪問(通過include 頭文件).? 為什么Java里的static 會跟c 語言里的有這種區(qū)別呢.

??? 下面會從內(nèi)存分配的角度淺析一下這個問題.




一. C語言程序所占內(nèi)存的大概結(jié)構(gòu)

?? ? ? 我們知道, static 的意思是靜態(tài)分配, 那么到底什么是靜態(tài)分配和動態(tài)分配呢.? 其實內(nèi)存的靜態(tài)分配和動態(tài)分配是對于C/C++ 來講的.? 而Java 作為由C/C++ 發(fā)展而來的類C語言, 雖然把內(nèi)存管理這一塊砍掉了(對程序員屏蔽, 在Java底層處理), 但是還是繼承了C語言的一些特性.

??

?????? 所以Java里有些特性和概念, 通過C語言分析能更好的理解.

?????? 首先, 1個由C語言編譯的程序所使用的內(nèi)存大概分成如下幾個部分

?????? 1、棧區(qū)(stack)— 由編譯器自動分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧
?????? 2、堆區(qū)(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時可能由OS回收 。
?????? 3、全局區(qū)(靜態(tài)區(qū))(static) --- 用來存放全局變量和靜態(tài)局部變量.
?????? 4、文字常量區(qū) —常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放 ?
?????? 5、程序代碼區(qū)—存放函數(shù)體的二進制代碼。


?????? 下面是大致的圖解.

??????

二. C語言內(nèi)存的靜態(tài)分配和動態(tài)分配.



2.1 靜態(tài)分配內(nèi)存

????? 對于C語言來講, 靜態(tài)分配內(nèi)存就是只用 類型 + 變量名 定義的變量. 不管這個變量是局部變量(函數(shù)中) 或全局變量(函數(shù)外).

2.1.1 局部變量 ?

???? 第一種情況,? 函數(shù)中靜態(tài)分配內(nèi)存的局部變量.

???

int f(){ int j=20; return j; }

?????

???? 如上面那個簡單的例子,? 在f()函數(shù)里定義的局部變量j 就是1個 靜態(tài)分配內(nèi)存的變量(注意不是靜態(tài)變量).

???? 這種靜態(tài)分配的變量的生存周期就是函數(shù)執(zhí)行一次的周期.

???? 什么意思呢,? 就是當(dāng)f()執(zhí)行時, 操作系統(tǒng)會為變量j 分配1個字節(jié)(32位系統(tǒng))的內(nèi)存,? 但是當(dāng)f() 執(zhí)行完時.? 變量j所占的內(nèi)存就會被釋放. 可以操作系統(tǒng)用作它用.

????? 也就是說, 當(dāng)f() 被循環(huán)執(zhí)行1萬次, 程序并不會額外占用9MB多的內(nèi)存,? 因為j所占的內(nèi)存會不斷地釋放分配.

????? 上面提過了,? 局部變量所占的內(nèi)存是分配在內(nèi)存里的Stuck(棧)區(qū)的. 因為j是int 類型, 所以它會在stuck區(qū)占用1字節(jié)

?????

注: 局部變量還有另1種形式就是函數(shù)的參數(shù):

如下面的變量i也是局部變量:

int f(int i){i++;return i; }

1.1.2 全局變量

???? 第二種情況就是定義在函數(shù)外(c文件中)靜態(tài)分配的變量.

??? 例子:

int j ;int f(){ j+=20; return j; }
???? 上面的j就是全局變量了.

???? 全局變量可以被各個函數(shù)調(diào)用, 所以全局變量j的生存周期跟函數(shù)f()無關(guān).

???? 也就是說, 全局變量的生存周期是就是程序的生存周期.

????? 即是,如果程序一直在運行,? 變量j所占的內(nèi)存就不會被釋放.? 理論上講, 定義的全局變量越多, 程序所占的內(nèi)存就越大.


???? 可見全局變量在內(nèi)存中是靜態(tài)的, 一旦被分配內(nèi)存.它不會被釋放和重新分配. 所以它占用的內(nèi)存被分配在內(nèi)存里的靜態(tài)區(qū):



2.2 動態(tài)分配內(nèi)存

????? c語言所謂動態(tài)分配內(nèi)存就是使用malloc 函數(shù)(必須引用stdlib.h) 在內(nèi)存中劃分一個連續(xù)區(qū)間(heap區(qū)), 然后用1個 指針變量接受把這區(qū)間的 頭部地址.? 這個指針變量所指向的內(nèi)存就是動態(tài)分配的內(nèi)存.


????? 舉個例子:

#include <stdio.h> #include <stdlib.h>int * f(){//error, variable with static memory allocation cannot be past to another function.//int j = 20; int * p = &j; int * p = (int *)malloc(sizeof(int)); //goodreturn p; }int main(){int * q = f();*q = 20;printf ("*q is %d\n", *q);free(q);q=NULL; }

參考上面那個小程序.

在函數(shù)f()中.

int * p = (int *)malloc(sizeof(int));

這條語句首先定義了1個int類型的指針 p. 這個指針本身是靜態(tài)分配的.

在heap去劃分了1個size為1個int(1字節(jié))的動態(tài)內(nèi)存,? 并且將該動態(tài)內(nèi)存的頭部地址賦給指針p.


最終這個函數(shù)f()返回了指針p的值, 也就是動態(tài)內(nèi)存的頭部地址


在main()函數(shù)中,

再定義1個靜態(tài)分配的指針q, 用來接受函數(shù)f()返回的動態(tài)內(nèi)存的頭部地址.

一旦f()函數(shù)執(zhí)行完,? f()里的指針p本身會被釋放,? 但是p指向的動態(tài)內(nèi)存仍然存在, 而且它的頭部地址被賦給了main()函數(shù)的指針q.


然后, q使用了這個動態(tài)內(nèi)存.(賦值20)


動態(tài)內(nèi)存的生命周期也是整個程序, 但是動態(tài)內(nèi)存可以被手動釋放. 在main()函數(shù)的最后使用了free()函數(shù)釋放了指針q,也就是q指向的動態(tài)內(nèi)存.


如果不手動釋放, 那么當(dāng)f() 和 main()函數(shù)被循環(huán)多次執(zhí)行時, 就會多次在heap區(qū)劃分內(nèi)存, 造成可用內(nèi)存越來越少,就是所謂的內(nèi)存泄露了.


圖解:



1. main()函數(shù)定義指針q, 這個指針q本身是1個局部變量, 在棧區(qū)分配內(nèi)存.

2. main()函數(shù)調(diào)用f()函數(shù), 局部變量指針q在f()里定義, 在棧區(qū)分配內(nèi)存

3. 在heap區(qū)劃分一塊動態(tài)內(nèi)存(malloc函數(shù))

4? 把動態(tài)內(nèi)存的頭部地址賦給f()函數(shù)里的p

5. f()函數(shù)執(zhí)行完成, p的值(就是動態(tài)內(nèi)頭部地址)賦給了main()函數(shù)的指針q, 這時q指向了動態(tài)內(nèi)存. p本身被釋放.

6. 當(dāng)動態(tài)內(nèi)存被使用完后, 在mian()函數(shù)的最后利用free()函數(shù)把動態(tài)內(nèi)存釋放, 這個動作相當(dāng)重要.

7. 當(dāng)mian()執(zhí)行完時, 指針p本身也會被釋放.




2.3 動態(tài)分配內(nèi)存和靜態(tài)分配內(nèi)存的區(qū)別.

由上面的例子可以看出, 動態(tài)內(nèi)存與靜態(tài)內(nèi)存有如下的區(qū)別.


1.靜態(tài)分配內(nèi)存的變量用 類型名(結(jié)構(gòu)體名) + 變量名 定義,? 動態(tài)分配的內(nèi)存變量用malloc劃分, 然后必須把地址傳給另1個指針.

2.靜態(tài)變量在內(nèi)存里的棧區(qū)(局部變量)或全局區(qū)(全局變量 or 靜態(tài)局部變量(后面會提到))里分配.? 動態(tài)分配內(nèi)存的變量在heap區(qū)分配.

3.靜態(tài)分配內(nèi)存變量生命周期有兩種, 其中局部變量,在函數(shù)結(jié)束后就會被釋放, 而全局變量在程序結(jié)束后才釋放.? 而動態(tài)變量需要程序員手動釋放, 否則會在程序結(jié)束后才釋放.


2.4 動態(tài)分配內(nèi)存的優(yōu)缺點

2.4.1 動態(tài)分配內(nèi)存的三個優(yōu)點

看起來動態(tài)分配內(nèi)存的變量的使用貌似比靜態(tài)分配內(nèi)存的變量使用麻煩啊.? 單單1個malloc函數(shù)都令新人覺得頭痛.

但是動態(tài)分配的內(nèi)存有三個優(yōu)點.


1.可以跨函數(shù)使用. 參見上面的例子, main()函數(shù)使用了f()函數(shù)定義的動態(tài)內(nèi)存.

?? 有人說全局變量也可跨函數(shù)使用啊, 的確. 但是全局變量必須預(yù)先定義(預(yù)先占用內(nèi)存), 而動態(tài)分配內(nèi)存可以再需要時分配內(nèi)存. 更加靈活.

??? 關(guān)于跨函數(shù)使用內(nèi)存可以參考我另1篇博文:

http://blog.csdn.net/nvd11/article/details/8749395


2.? 可以靈活地指定或分內(nèi)存的大小.

??? 例如 (int *)malloc(sizeof(int) * 4) 就劃分了4個字節(jié)的內(nèi)存(動態(tài)數(shù)組).?? 這個特性在定義動態(tài)數(shù)組時特別明顯.

??? 而且可以用realloc 函數(shù)隨時擴充或減少動態(tài)內(nèi)存的長度.?

???? 這個特性是靜態(tài)分配內(nèi)存的變量不具備的.


3. 動態(tài)變量可以按需求別手動釋放.

??? 雖然局部變量隨函數(shù)結(jié)束會自動釋放,? 而動態(tài)分配的內(nèi)存甚至能在函數(shù)結(jié)束前手動釋放.

??? 而全局變量是不能釋放的. 所以使用動態(tài)內(nèi)存比使用全局變量更加節(jié)省內(nèi)存.



2.4.2 動態(tài)分配內(nèi)存的兩個硬傷


但是動態(tài)分配內(nèi)存也有2個硬傷:

1.? 就是必須手動釋放....? 否則會造成內(nèi)存泄露.

其實上面都講過了, 這里舉個具體例子:


程序1:

#include <stdio.h> #include <stdlib.h>int f(int i){int * p = (int *)malloc(sizeof(int));*p = i*2;printf ("*p is %d\n", *p);free(p);p=NULL;return 0; }int main(){int i;for (i=0; i<100; i++){f(i); } }
程序1的f() 函數(shù)被main()函數(shù)循環(huán)執(zhí)行了100次,?? 所以f()在內(nèi)存heap區(qū)劃分了100次動態(tài)內(nèi)存,? 但是每一次f()結(jié)束前都會用free函數(shù)將其手動釋放.

所以并不會造成內(nèi)存浪費.? 這里再提一提, free(p) 這個函數(shù)作用是釋放p所指向的動態(tài)內(nèi)存,? 但是指針p的值不變, 仍然是那個動態(tài)內(nèi)存的地址.? 如果下次再次使用p就肯定出錯了.所以保險起見加上p=NULL, 以后使用p之前,也可以用NULL值判斷它是否被釋放過.


程序2:

#include <stdio.h> #include <stdlib.h>int f(int i){int * p = (int *)malloc(sizeof(int));*p = i*2;printf ("*p is %d\n", *p);return 0; }int main(){int i;for (i=0; i<100; i++){f(i); } }
上面就是反面教材. 如果沒有手動釋放, 當(dāng)f()函數(shù)被循環(huán)執(zhí)行時就會多次劃分動態(tài)內(nèi)存, 導(dǎo)致可用內(nèi)存越來越少. 也就是程序所占的內(nèi)存越來越多, 這就是傳說中的內(nèi)存泄露.


可能有人認(rèn)為, 不就是加多1個free()函數(shù)嗎?? 算不上缺點.

但是有時候程序猿很難判斷一個指針該不該被free() 函數(shù)釋放..

程序3:

#include <stdio.h> #include <stdlib.h>int main(){int * p = (int *)malloc(sizeof(int));int * q = p;*p = 10;printf ("*p is %d\n", *p);*q = 20;printf ("*q is %d\n", *q);free(p);free(q); //error, the dynamic memory is released already.return 0; }
看看上面的例子,

指針p和q指向同1個動態(tài)內(nèi)存.

當(dāng)執(zhí)行free(p)時,? 釋放的是動態(tài)內(nèi)存, 而不是釋放p本身,? 所以再此執(zhí)行free(q)就出錯了,?? 因為那個動態(tài)內(nèi)存已經(jīng)被釋放過了嘛..

有人覺得, 這個錯誤也不難發(fā)現(xiàn)嘛.., 小心點就ok了.


首先, 上面的代碼編譯時并不會報錯, 至少gcc會編譯通過, 執(zhí)行時才會出錯...

而且, 當(dāng)項目越來越復(fù)雜時, 可能有多個指針指向同1個動態(tài)內(nèi)存,? 而且某個指針指向的動態(tài)內(nèi)存是其他程序員在其他函數(shù)內(nèi)分配的..


這時你就很難判斷了,? 如果不釋放怕造成內(nèi)存泄露,? 如果釋放了, 別的程序猿不知道的話再次使用...就會出錯.

所以有時候在項目中程序猿很難判斷1個指針該不該釋放啊... 特別是多個程序猿合作的大型c項目.


這就是為什么說c語言功能強大, 但是不適合編寫大型項目的原因之一,? 需要程序猿有相當(dāng)扎實的內(nèi)存管理能力.


2.? 另個硬傷就是內(nèi)存溢出.

什么是內(nèi)存溢出呢,? 就是使用了動態(tài)分配內(nèi)存長度之外的內(nèi)存...


舉個例子:

#include <stdio.h> #include <stdlib.h>int main(){int * p = (int *)malloc(sizeof(int) * 4);*p = 1;*(p + 1) = 2;*(p + 2) = 3; *(p + 3) = 4;*(p + 4) = 5; // error,memory overflowint i;for (i=0;i < 5;i++){ // error, should be i < 4printf("no.%d is %d\n",i,*(p+i));}free(p);p=NULL;return 0; }
上面定義了長度為4的連續(xù)內(nèi)存空間. (動態(tài)整形數(shù)組)

但是這個程序卻使用了長度為5的內(nèi)存空間, 也就是這個動態(tài)內(nèi)存后面額外的的那一個字節(jié)的內(nèi)存被使用了.

其實就是p+4 這個地址的內(nèi)存并沒有定義, 但是卻被使用, 這就是傳說中的內(nèi)存溢出.


這個代碼可以被正常編譯, 可怕的是, 很多情況下它會正常執(zhí)行...

但是如果p+4剛好被這個程序的其他變量或其他程序正在使用, 而你卻往它寫入數(shù)據(jù), 則可能會發(fā)生導(dǎo)致程序漰潰的錯誤...

這就是有些c \ c++ 程序不夠健壯的原因, 有時候會發(fā)生崩潰..


所以說c語言很難就難在這里, 內(nèi)存管理啊.



三. C語言的static關(guān)鍵字

終于講到正題了, 下面就說說c語言static關(guān)鍵字對內(nèi)存分配的影響.

首先, c的static 關(guān)鍵字是不能修飾動態(tài)分配內(nèi)存的.

例如

int * p = static (int *)malloc(sizeof(int))
是錯誤的.??

?但是 下面寫法是合法的.

static int * p = (int *)malloc(sizeof(int))上面的static 不是修飾動態(tài)分配的內(nèi)存,? 而是修飾靜態(tài)分配的指針變量p


上面也提到過了, c語言的static 可以修飾如下三種對象:

1. 全局變量和函數(shù)

2. 函數(shù)內(nèi)的局部變量.


注意, c語言結(jié)構(gòu)體的成員不能用static 修飾


3.1 static 修飾全局變量或函數(shù).

在全局變量和函數(shù)名前面的 static函數(shù)并不影響 對象的內(nèi)存分配方式,

static 修飾的全局變量還是被分配與全局區(qū)中.???

而被static修飾的函數(shù)的2進制代碼還是被分配于程序代碼區(qū)中.


這種情況下 static 的作用只是簡單地對其他c文件的函數(shù)屏蔽.


也就是1個c文件a.c,? 引用了另一個c文件b.c

那么a.c 文件就不能訪問b.c 文件里用static修飾的 全局變量和函數(shù).



3.2 static 修飾局部變量.

如果用static 來修飾c語言函數(shù)中的局部變量, 那么這個局部變量是靜態(tài)局部變量了.

如下面的例子:

#include <stdio.h> #include <stdlib.h>int f(){int i = 1; // local variablestatic int j = 1; // static local variablei++; j++;printf("i is %d, j is %d\n",i,j);return 0; }int main(){int i;for (i=0;i < 10 ;i++){f();}return 0; }
上面的f() 函數(shù)i就是 一般的局部變量了, 而 j 前面有static修飾, 所以j是1個靜態(tài)局部變量.


如果上面的f()函數(shù)被連續(xù)執(zhí)行10次, 那么 i 和 j的值是不同的.

gateman@TFPC tmp $ ./a.out i is 2, j is 2 i is 2, j is 3 i is 2, j is 4 i is 2, j is 5 i is 2, j is 6 i is 2, j is 7 i is 2, j is 8 i is 2, j is 9 i is 2, j is 10 i is 2, j is 11

可以見到, 每次f()執(zhí)行,? i 的值都是2,? 而 j 的 值 會不斷加1.

原因就是static 用在局部變量前面就會改變該局部變量的內(nèi)存分配方式.

上面說過, 一般局部變量是放在內(nèi)存Stuck區(qū)的,? 而靜態(tài)局部變量是放在全局(靜態(tài))區(qū)的.


圖解:



當(dāng)程序執(zhí)行時,? f()作為1個函數(shù),? 它的2進制代碼是存放在內(nèi)存里的程序代碼區(qū)的.

對于變量i:

??????? f()每次執(zhí)行時都會在Stuck區(qū)為變量i初始化一塊內(nèi)存.? 而結(jié)束時會自動地把該內(nèi)存釋放,

??????? 也就是說int i = 1; 這個語句每次執(zhí)行時都會執(zhí)行.? 所以i每次輸出的值都是一樣的.


對于靜態(tài)局部變量j:

??????? f() 會在內(nèi)存Static區(qū)檢測有無屬于變量j的內(nèi)存.

??????? 如果無, 則執(zhí)行初始化語句 static int j = 1;? 并記錄下該內(nèi)存的地址.

??????? 如果有, 則直接使用該內(nèi)存.

??????? 當(dāng)f() 執(zhí)行完成時, 該內(nèi)存不會被釋放.

??????? 也就是講, 當(dāng)f()下一次執(zhí)行時, 就不會執(zhí)行 static int j =1; 這條語句.

??????? 所以當(dāng)f()循環(huán)執(zhí)行時, j的值就會遞增了.


也就是講, 當(dāng)static 修飾1個局部變量時, 會更改局部變量的內(nèi)存分配方式.而且這個局部變量的生命周期就會變成全局變量一樣.


全局變量與靜態(tài)局部變量的區(qū)別:

由此可見, 靜態(tài)局部變量與全局變量的內(nèi)存分配方式是類似的, 它們的內(nèi)存都是被分配在static 區(qū), 那么它們的生命周期也是一樣的,都是整個程序的生命周期. 而它們的區(qū)別如下: 1. 全局變量在程序開始時就分配內(nèi)存, ?而靜態(tài)局部變量在對應(yīng)函數(shù)第一次執(zhí)行時分配內(nèi)存.
2. 全局變量能被各個函數(shù)訪問, 所以一般用于傳遞數(shù)據(jù). ?靜態(tài)局部變量只能被定義它的函數(shù)訪問, ?一般用于保存特定數(shù)據(jù). ? ?


四. Java語言程序所占內(nèi)存的大概結(jié)構(gòu)

Java 作為C/C++ 發(fā)展出來的語言, 最大的區(qū)別就是對程序員管理屏蔽了內(nèi)存管理的部分. ? 也就是說Java沒有了指針這個概念. 所有動態(tài)內(nèi)存的分配和釋放都在Java底層里自動完成.

所以說Java 的功能和性能都遠比不上C/C++ .

但是正因為從根本上避免了內(nèi)存泄漏等內(nèi)存操作容易產(chǎn)生的錯誤, 所以Java編寫的程序的健壯性會很好, 也就是Java比C語言更適合大型項目的原因.


Java畢竟也是類C語言的一種, 所以Java的內(nèi)存結(jié)構(gòu)跟C語言類似:

如圖:



可見java的程序會把其占用的內(nèi)存大概分成4個部分.


Stuck 區(qū): 跟c一樣, 存放局部變量, 也就是函數(shù)內(nèi)定義的變量.

Heap 區(qū): 跟c一樣, 存放動態(tài)分配內(nèi)存的變量, 只不過動態(tài)分配內(nèi)存的方式跟c不通, 下面會重點提到.

數(shù)據(jù)區(qū): ? ?相當(dāng)于c的static區(qū), 存放靜態(tài)(static)變量和字符串常量

代碼區(qū): ? ?跟c一樣, 存放2進制代碼.



五. Java內(nèi)存的靜態(tài)分配和動態(tài)分配.

跟C一樣, Java的變量的內(nèi)存分配也可以分成靜態(tài)分配內(nèi)存和動態(tài)分配兩種, 只不過形式上跟c語言可以講存在很大的差別.



5.1 Java靜態(tài)分配內(nèi)存


上面提到了, c語言靜態(tài)分配的內(nèi)存(非static 修飾)可以分成兩種: ?全局變量和局部變量. ?

其中全局變量在函數(shù)外定義, 內(nèi)存分配在static區(qū). ? ?而局部變量在函數(shù)內(nèi)定義, 內(nèi)存分配在stuck區(qū).


而Java 里是不存在全局變量這玩意的. ?因為Java是1個完全面向?qū)ο蟮恼Z言, ?一旦1個變量不是在函數(shù)里定義, 那么他就是在類里面定義, 就是1個類的成員了.

如下面這個例子:

public class A{int j;int f(){int i = 0;return i;} }

其中, 變量j是A的1個成員, 而不是全局變量.

而變量i 跟c一樣, 是屬于函數(shù)f的1個局部變量.

java里局部變量的內(nèi)存方式跟c語言是一樣的, 都是屬于靜態(tài)分配, ?內(nèi)存被分配在stuck區(qū).

那么其生命周期就也會隨函數(shù)執(zhí)行完成而結(jié)束, 這里就不細講了.



5.2 Java動態(tài)分配內(nèi)存

我們看回上面那個例子.
Class A里面的變量j 其實就是A的一個成員, ?而Java里1個類里面的所有非Static修飾的成員都是動態(tài)分配內(nèi)存的. 也就是講, 其實那個變量j是動態(tài)分配內(nèi)存的, 內(nèi)存被分配在heap區(qū)里.
怎么講呢, ?還是要借助c語言:..



2.2.1 c語言靜態(tài)分配內(nèi)存的結(jié)構(gòu)體

面向?qū)ο蟾旧鲜?種編程的思想, 其實作為面向過程的c語言, 也可以用面向?qū)ο蟮乃枷雭砭幊?.
大家都知道c語言里具有類的雛形---> 結(jié)構(gòu)體. 其本質(zhì)就是讓不同的數(shù)據(jù)類型集合存儲.
首先看看結(jié)構(gòu)體的靜態(tài)分配內(nèi)存用法:
#include <stdio.h> #include <stdlib.h> #include <string.h>struct A{int id;char name[16]; };int main(){struct A a;a.id = 1;strcpy(a.name, "Jack");printf("%d, %s\n", a.id, a.name);return 0; }
上面的簡單例子, 定義并使用了1個結(jié)構(gòu)體A.
下面1句1句從內(nèi)存分配的角度詳細講解...
首先:
struct A{int id;char name[16]; }
這幾句代碼定義了結(jié)構(gòu)體A的結(jié)構(gòu), 包括1個整形和1個字符數(shù)組的兩個成員.
在程序執(zhí)行過程中, ?定義代碼會作為2進制代碼存放于內(nèi)存里的代碼區(qū)中.
struct A a;
這代碼靜態(tài)定義了1個結(jié)構(gòu)體a. ?它的長度是4 + 16 =20 byte 而內(nèi)存是被分配在 stuck區(qū)的.
注意, 這個時候, A里面的兩個成員: ?id 和 name里面是垃圾值, 并沒有初始賦值的.
a.id = 1; strcpy(a.name, "Jack");
這兩個就是為結(jié)構(gòu)體a的兩個成員賦值了. 不多說..
圖解:




2.2.2 c語言靜態(tài)分配內(nèi)存的結(jié)構(gòu)體的缺點


這種靜態(tài)定義使用的結(jié)構(gòu)體優(yōu)點很簡答: 方便使用, 安全性好.
缺點是什么呢?
當(dāng)然了, 上面都提過:
1. 不能夸函數(shù)使用, 生命周期隨函數(shù)結(jié)束.
2. 不能靈活釋放.
其實這兩個缺點都系虛的.
真正的問題是, 在生產(chǎn)中, ?1個結(jié)構(gòu)體往往定義得十分復(fù)雜. ?也就是包含幾十個成員, 幾十個函數(shù)指針(方法).
那么這個結(jié)構(gòu)體所占的內(nèi)存就很客觀了,
棧的內(nèi)存大小有限, ?而在heap區(qū)能申請更大的內(nèi)存, ?這個才是動態(tài)分配內(nèi)存的結(jié)構(gòu)體的必要性.

2.2.3 c語言動態(tài)分配內(nèi)存的結(jié)構(gòu)體

看下面的例子: #include <stdio.h> #include <stdlib.h> #include <string.h>struct A{int id;char name[16];void (* A_prinf)(struct A *); };void printf_A(struct A * b){printf("%d, %s\n", b->id, b->name); }struct A * A_new(int id, char * name){struct A * b = (struct A *)malloc(sizeof(struct A));b->A_prinf = printf_A;b->id = id;strcpy(b->name,name);return b; }int main(){struct A * a;a = A_new(1,"Jack");a->A_prinf(a);free(a);a = NULL;return 0; }
上面就是動態(tài)分配內(nèi)存的結(jié)構(gòu)體最常用的用法..





再一句一句來: struct A{int id;char name[16];void (* A_prinf)(struct A *); };
這段定義了1個結(jié)構(gòu)體A, 跟之前例子不同的是只不過, 多了1個函數(shù)指針, 用于打印這個結(jié)構(gòu)體的成員. 這段2進制代碼一樣存放子在代碼區(qū)中.


void printf_A(struct A * b){printf("%d, %s\n", b->id, b->name); }
上面是打印函數(shù)的定義了, ?我們會將結(jié)構(gòu)體A的指針指向這個函數(shù). 也會放在代碼區(qū)中.


struct A * A_new(int id, char * name){struct A * b = (struct A *)malloc(sizeof(struct A));b->A_prinf = printf_A;b->id = id;strcpy(b->name,name);return b; }
A_new()函數(shù)相當(dāng)于1個初始化函數(shù).
無論靜態(tài)或動態(tài)定義1個結(jié)構(gòu)體之后, 只會在stuck區(qū)或heap區(qū)分配該內(nèi)存. 而內(nèi)存里結(jié)構(gòu)體的成員是垃圾數(shù)據(jù)().
也就是說,定義1個結(jié)構(gòu)體A的"對象"b后, ?b的id, name, 函數(shù)指針A_prinft都是垃圾數(shù)據(jù).
如果不經(jīng)初始化, 直接使用成員, 例如函數(shù)指針的話, 系統(tǒng)就出錯了.
所以初始化函數(shù)最重要的作用就是把每1個?對象的函數(shù)指針指針向正確的函數(shù). ?這個就是初始化函數(shù)的必要性.(當(dāng)然你也可以在main函數(shù)內(nèi)手動指向).
這里順便加兩個參數(shù), 把id和name也初始化了.

struct A * a;

注意, main函數(shù)里這1句定義的是1個結(jié)構(gòu)體A指針, 而不是結(jié)構(gòu)體.

任何類型的指針長度都是4byte(32 位系統(tǒng)), ?而這個指針是局部變量, ?會被分配在stuck區(qū)


a = A_new(1,"Jack");這里是關(guān)鍵了, 調(diào)用A_new()函數(shù), ?在heap去分配1個結(jié)構(gòu)體的內(nèi)存, 然后把頭部地址賦給a.?

那么在棧區(qū)的指針a就指向堆區(qū)的內(nèi)存了.

a->A_prinf(a)這里調(diào)用了結(jié)構(gòu)體對象a的函數(shù)指針A_prinft, ?這個指針在初始化函數(shù)A_new()執(zhí)行時已經(jīng)被指向了真實函數(shù)prinft_A().

所以, 實際上是調(diào)用了printf_A(). ?但是,參數(shù)是必要的.


后面的就是釋放內(nèi)存和致空指針. 因為c語言不會自動釋放動態(tài)內(nèi)存.


圖解:



其實單單只看這個例子main()函數(shù)的代碼, 是不是覺得很像面向?qū)ο笳Z言java 或 C++.

所以講, 面向?qū)ο笃鋵嵤?種編程思想. ?而Java的內(nèi)部實現(xiàn)還是離不開c/c++.


我們也可以在這里看出面向?qū)ο蟮囊恍┨匦? 這實際上也是動態(tài)分配內(nèi)存的優(yōu)點:

1. 對象的指針存放在棧區(qū), 而無論1個結(jié)構(gòu)體的內(nèi)存占用有多么龐大, 棧區(qū)的對象指針只會保存結(jié)構(gòu)體內(nèi)存的頭部指針.?

? ? 所以棧區(qū)的單個對象指針只占用4byte. ?相對于靜態(tài)分配的結(jié)構(gòu)體, 大大節(jié)省了棧區(qū)的空間.


2. 多個不同的對象會利用不同的heap區(qū)內(nèi)存存放各種的成員(例如id, name), ?但是各自的函數(shù)指針指向相同的代碼區(qū)函數(shù).

? ?也就是說, 每1個結(jié)構(gòu)體對象的內(nèi)部函數(shù)實際上都是一樣的 (除非手動再指向). 只不過參數(shù)不同.



2.2.3 Java里的類動態(tài)分配.

終于講回java了, 上面提到那么多c語言的東西其實是為了與java作對比, 更好地了解java里類的內(nèi)存分配.
Java 是完全面向?qū)ο笳Z言, 所以任何東西都必須用類來描述. ?而Java的類實際上是由c語言的結(jié)構(gòu)體擴展而來的. ? 而上面說過, 生產(chǎn)環(huán)境中的類往往非常復(fù)雜. 所以 Java 里的類都是動態(tài)分配內(nèi)存的.

看下面這個簡單例子: class B{int i = 10;int f(){System.out.printf("i is %d\n", i);return 0;} }public class A{public static void main(String[] args){B b;b = new B();b.i = 11;b.f();} }

上面的例子定義了兩個類,
在A的入口函數(shù)中, ?定義并實例化了類B. 下面講下, 類B是如何被分配內(nèi)存的.


class B{int i = 10;int f(){System.out.printf("i is %d\n", i);return 0;} }
上面就是類B的定義代碼, ?跟c語言的結(jié)構(gòu)體定義代碼一樣, 它也會被轉(zhuǎn)成2進制代碼而存放在代碼區(qū)中.

跟c語言的結(jié)構(gòu)體代碼做下對比: struct A{int id;char name[16];void (* A_prinf)(struct A *); };

實際上類與結(jié)構(gòu)體成員的定義都是類似的.
但是 結(jié)構(gòu)體只是一個簡單的不同類型的集合體. 里面的成員(包括函數(shù)指針)不允許具有初始值.

而類是允許的. 而且函數(shù)的定義代碼也寫在類里面..


B b;b = new B();b.i = 11;b.f();
我們看看這四句在入口函數(shù)的代碼.

B b;

?這個語句在c語言中可以理解成 靜態(tài)定義1個B的結(jié)構(gòu)體對象b.


但是在Java中, 我們應(yīng)該理解成為定義1個類B的指針b, ?注意java里雖然取消了指針操作, 但是java里的底層很多東西都還是需要指針來實現(xiàn).

相當(dāng)于c 語言里的

B * b;
所以這一句理解為簡單地定義1個局部指針變量b, 它的內(nèi)存是分配在stuck區(qū)的.

既然對象b只是相當(dāng)于一個指針. 當(dāng)執(zhí)行完這一句時, 它只是1個空指針, 所以它指向的內(nèi)存并不能使用.?


而我們就說對象b并沒有實例化.

當(dāng)我們直接對對象b的非static成員操作時就會彈出錯誤: 對象沒有實例化了, ?就是這個原因.



b = new B();

接下來這一句就比較重要了.

首先 new B() ?這個作用就是在heap區(qū)動態(tài)分配1個類B的內(nèi)存,其實就是相當(dāng)于c語言里的 (B *)malloc(sizeof(B))啦.

只不過在Java里, ?java把 malloc分配內(nèi)存的動作隱藏在new這個語句里面. ??


跟c語言一樣, 分配內(nèi)存后還需要把內(nèi)存的頭部指針賦于對象b. ?所以會有"b =" 這個寫法.

其實就相當(dāng)于c語言里的

b = (B *)malloc(sizeof(B))
當(dāng)執(zhí)行完這一句后

對象b實際上就指向了heap區(qū)的對應(yīng)內(nèi)存.


這時我們就可以對對象b的成員進行操作. 也就是對象b已經(jīng)被實例化.


所以其實實例化的真正意思就是1個對象指向了heap區(qū)的對應(yīng)內(nèi)存.

如圖:




這時, 我們看看對象b的成員i, ?它隨著對象b的內(nèi)存, 同樣被分配在heap區(qū)里面.

所以我們就說 在函數(shù)外定義的非static變量不是全局變量, ?而是類的成員, 它們是動態(tài)分配的.


既然new出來的東西是動態(tài)分配, 那么就需要手動釋放? ?java里有自動釋放的機制, 所以不必程序猿手動釋放了.



六. Java里的static關(guān)鍵字

但是有一種情況, ?有些類的成員并不需要實例化就可以使用, ?那么這種成員就是靜態(tài)成員, 肯定是用static修飾的. class B{int i = 1;int f(){System.out.printf("i is %d\n", i);return 0;}static int j = 1;static int g(){System.out.printf("j is %d\n", j);return 0;} }public class A{public static void main(String[] args){B.j += 1;B.g();B b;b = new B();b.i = 10;b.j += 1;b.f();b.g();} }

看看上面這個經(jīng)過簡單修改過的例子. B增加1個靜態(tài)成員j, 1個靜態(tài)函數(shù)g();
接下來從這個例下分析下java靜態(tài)成員的一些特性.

6.1 static 成員不需實例化就可以使用.

上面入口函數(shù)中直接使用了類B的靜態(tài)成員j 和 靜態(tài)函數(shù)g() B.j += 1; B.g();

這種情況其實就是把 成員j的內(nèi)存分配在了java的數(shù)據(jù)區(qū)中, 而不是heap區(qū), 這個內(nèi)存地址在程序執(zhí)行時是不變的. 而且在代碼區(qū)的類定義代碼里保留了這個地址(指針)




6.2 多個不同的對象共享1個static 成員.

如上面的例子, 當(dāng)執(zhí)行B.j += 1后, ?j的值變成2. ?然后再實例1個對象b, 執(zhí)行b.j += 1, 那么輸出對象b的成員的值j就是3了.
其實因為對象b里面的靜態(tài)成員j指針還是指向static 區(qū)的同1個內(nèi)存地址.
也就是說類B的多個不同對象實際上共享同1個靜態(tài)成員.
那么java里實例化1個具有靜態(tài)成員的對象時, 會同時把靜態(tài)成員的地址放入棧區(qū).




6.3 靜態(tài)函數(shù)不允許使用非靜態(tài)成員.

這個也不難理解, 看上圖, 靜態(tài)函數(shù)g()是可以在實例化之前使用的, 但是如果g() 引用了非靜態(tài)成員i. 那么當(dāng)沒有實例化對象b調(diào)用g()時, 就會找不到成員i的地址.
因為成員i必須在實例化之后才會被分配內(nèi)存啊.
這個就是java不允許靜態(tài)函數(shù)調(diào)用非靜態(tài)成員的原因!

6.4 靜態(tài)函數(shù)和非靜態(tài)函數(shù)的區(qū)別.

的確, 靜態(tài)函數(shù)與非靜態(tài)函數(shù)的2進制代碼都是存放在代碼區(qū)里面..
class B{int i = 10;int f(){System.out.printf("i is %d\n", i);return 0;} }
看看上面里的例子. 非靜態(tài)函數(shù)f() 是不帶參數(shù)的.. 而且里面的成員i 也不帶參數(shù).

再看看c語言的結(jié)構(gòu)體定義: struct A{int id;char name[16];void (* A_prinf)(struct A *); };
里面的函數(shù)指針必須帶參數(shù).

其實在java里面, 非靜態(tài)函數(shù)已經(jīng)隱藏了1個自帶參數(shù)"this". 因為上面說過了, 所有不同的實例化后的對象里面的成員內(nèi)存都是不同的, 但是它們的函數(shù)都是指向代碼區(qū)同1個函數(shù).
那么調(diào)用這個函數(shù)時, 這個函數(shù)必須知道到底是哪個對象調(diào)用它, 在這里就是到底要輸出哪個對象的成員i.
所以,? 在底層里, 我任務(wù)非靜態(tài)函數(shù)自帶參數(shù)"this"這個隱藏指針.

但是靜態(tài)函數(shù)不同, 因為它不允許使用非靜態(tài)成員. 就無需"this"了.



完...












?

《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的从内存分配角度分析c和java里的static 关键字.的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 日本肉体xxxx裸体137大胆图 | 制服丝袜在线看 | 中文字幕在线视频播放 | 成人国产精品久久久 | 中文字幕色网 | 日韩av电影手机在线观看 | 在线看成人| 久草视频免费播放 | 日韩色网| 日韩不卡的av | 毛片成人 | 天天干干| www.x日本 | 国产女人精品视频 | 大尺度舌吻呻吟声 | 欧美一区二三区 | 久久综合综合 | 久久久国产精品一区 | 欧美性生交大片免费看app麻豆 | 黄色国产在线 | 亚洲欧美va天堂人熟伦 | 久久精品视频在线观看 | 99视频精品免费 | 国产二区视频在线观看 | 96人xxxxxxxxx69 | 玖草在线观看 | 亚洲精品在线电影 | 日韩精品免费一区二区三区竹菊 | 亚洲欧美日韩精品永久在线 | 亚洲精品日韩av | 亚洲wwww| 国产天堂在线观看 | 国产黄色片在线 | 麻豆影视在线播放 | 最新中文字幕av专区 | 人人妻一区二区三区 | 成人a√| 亚洲欧洲成人精品久久一码二码 | 天天操网址 | 性chinese天美传媒麻 | www.亚洲欧美 | 熟女人妇 成熟妇女系列视频 | 99爱在线| 日韩蜜桃视频 | 国产av人人夜夜澡人人爽 | 人人干网站 | 男女黄色片 | 欧美群妇大交群 | 大乳女喂男人吃奶视频 | 青青精品| 国产日韩欧美 | 我爱52av| 99热这里只有精 | 人妻少妇精品中文字幕av蜜桃 | 涩涩视频在线播放 | 东京久久久| 人人九九 | 不用播放器的av网站 | 一级做a爰 | 狠狠狠狠狠狠狠干 | 精品黑人一区二区三区观看时间 | 超碰88| 在线成人中文字幕 | 国语对白一区 | 91黄色免费网站 | 精品乱码一区内射人妻无码 | 男人操女人视频网站 | 欧美一级片免费观看 | 亚洲成人精选 | 日韩激情文学 | 朝鲜黄色片 | 99国产精品久久久 | 国产精品一区二区在线免费观看 | 欧美精品久久久久久久久久 | 日本三级大片 | 先锋影音av资源在线 | 日韩精品中文字 | 诱惑av | missav|免费高清av在线看 | 国产综合久久久久久鬼色 | 日韩欧美在线一区二区三区 | 精品国产一区二区三区四 | 美女啪啪一区二区 | 不卡影院av | 一区二区三区天堂 | 国产伦理av| 亚洲av日韩精品久久久久久久 | 日本aⅴ片| xx久久 | 国产一级做a爰片久久毛片男男 | 亚色91 | 欧美精品99 | 久久影视中文字幕 | 国产一区二区免费在线观看 | 一品毛片 | 一区二区三区四区五区六区 | 长篇高h乱肉辣文 | 欧美最猛黑人xxxx黑人猛交 | 五月婷婷亚洲综合 |