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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

存储类、作用域、生命周期、链接属性

發布時間:2023/12/20 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 存储类、作用域、生命周期、链接属性 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

以下內容源于朱有鵬嵌入式課程的學習,如有侵權,請告知刪除。

補充:https://blog.csdn.net/oqqHuTu12345678/article/details/71214255

一、概念集合

1、存儲類

(1)存儲類就是存儲類型,也就是描述C語言變量在何種地方存儲

(2)內存有多種管理方法:棧、堆、數據段、bss段、.text段(代碼段)……一個變量的存儲類屬性就是描述這個變量存儲在何種內存段中。

?

  • 譬如局部變量分配在棧上,所以它的存儲類就是棧;
  • 顯式初始化為非0的全局變量分配在數據段,顯式初始化為0和沒有顯示初始化(默認為0)的全局變量分配在bss段。

?

2、作用域

(1)作用域是描述這個變量起作用的代碼范圍

(2)基本來說,C語言變量的作用域規則是代碼塊作用域。

?

  • 即這個變量起作用的范圍是當前的代碼塊。代碼塊就是一對大括號{}括起來的范圍,所以一個變量的作用域是:這個變量定義所在的{}范圍內從這個變量定義開始往后的部分。(這就解釋了為什么變量定義總是在一個函數的最前面)

?

3、生命周期

(1)生命周期是描述這個變量什么時候誕生(運行時分配內存空間給這個變量)及什么時候死亡(運行時收回這個內存空間,此后再不能訪問這個內存地址,或者訪問這個內存地址已經和這個變量無關了)的。

(2)變量和內存的關系,就和人(變量)去圖書館借書(內存)一樣。變量的生命周期就好象我人借書的這段周期一樣。

(3)研究變量的生命周期可以我們理解程序運行的一些現象、理解C語言的一些規則。

?

4、鏈接屬性

?

(1)程序從源代碼到最終可執行程序,經歷的過程:編譯、鏈接。

?

  • 編譯階段就是把源代碼搞成.o目標文件。
  • 目標文件里面有很多符號和代碼段、數據段、bss段等分段。
  • 符號就是編程中的變量名、函數名等。
  • 運行時變量名、函數名能夠和相應的內存對應起來,靠符號來做鏈接的。

(2).o的目標文件鏈接生成最終可執行程序的時候,其實就是把符號和相對應的段給鏈接起來。

?

(3)C語言中的符號有三種鏈接屬性:外連接屬性、內鏈接屬性、無連接屬性。

?

二、linux下C程序的內存映像

?

1、代碼段、只讀數據段

  • 對應著程序中的代碼(函數),代碼段在linux中又叫文本段(.text);
  • 只讀數據段就是在程序運行期間只能讀不能寫的數據,const修飾的常量有可能是存在只讀數據段的(但是不一定,const常量的實現方法在不同平臺是不一樣的)

2、數據段、bss段

?

  • 數據段存:1、顯式初始化為非0的全局變量;2、顯式初始化為非0的static局部變量
  • bss段存:1、顯式初始化為0或者未顯式初始化的全局變量;2、顯式初始化為0或未顯式初始化的static局部變量。

?

3、堆

?

  • C語言中什么樣變量存在堆內存中?
  • C語言不會自動向堆中存放東西,堆的操作是程序員自己手工操作的。
  • 程序員根據需求自己判斷要不要使用堆內存,用的時候自己申請,自己使用,完了自己釋放。

?

4、文件映射區

?

  • 文件映射區就是進程打開了文件后,將這個文件的內容從硬盤讀到進程的文件映射區,以后就直接在內存中操作這個文件,讀寫完了后在保存時再將內存中的文件寫到硬盤中去。

?

5、棧

  • 局部變量分配在棧上;函數調用傳參過程也會用到棧

7、內核映射區

?

  • 內核映射區就是將操作系統內核程序映射到這個區域了。
  • 對于linux中的每一個進程來說,它都以為整個系統中只有它自己和內核而已。
  • 它認為內存地址0xC0000000以下都是它自己的活動空間,0xC0000000以上是OS內核的活動空間。
  • 每一個進程都活在自己獨立的進程空間中,0-3G的空間每一個進程是不同的(因為用了虛擬地址技術),但是內核是唯一的。

?

8、OS下和裸機下C程序加載執行的差異

(1)C語言程序運行時環境有一定要求

?

  • 意思是單獨個人寫的C語言程序沒法直接在內存中運行,需要外部一定的協助,這段協助的代碼叫加載運行代碼(或者叫構建C運行時環境的代碼,這一段代碼在操作系統下是別人寫好的,會自動添加到我們寫的程序上,這段代碼的主要作用是:給全局變量賦值、清bss段)。

?

(2)ARM裸機第十六部分,寫shell時有一次定義了一個全局變量初始化為0但是實際不為0,后來在裸機的start.S中加了清bss段代碼就變0了。

?

  • 這就說明在裸機程序中沒人幫我們來做這一段加載運行時代碼,要程序員自己做(start.S中的重定位和清bss段就是在做這個事);
  • 在操作系統中運行程序時程序員自己不用操心,會自動完成重定位和清bss,所以我們看到的現象:C語言中未初始化的全局變量默認為0。

?

(3)數據段的全局變量或靜態局部變量都是有非0的初值的,這些初值在main函數運行之前就已經被初始化了,是重定位期間完成的初始化。

?

?

三、存儲類相關的關鍵字

?

?

1、auto

?

  • auto關鍵字在C語言中只有一個作用,那就是修飾局部變量
  • 平時定義局部變量時就是定義的auto的,只是省略了auto關鍵字而已。可見,auto的局部變量其實就是默認定義的普通的局部變量。

?

2、static

(1)static的第一種用法是:用來修飾局部變量,形成靜態局部變量。

?

  • 要搞清楚靜態局部變量和非靜態局部變量的區別。本質區別是存儲類不同(存儲類不同就衍生出很多不同)。
  • 非靜態局部變量分配在棧上,而靜態局部變量分配在數據段/bss段上。

?

(2)static的第二種用法是:用來修飾全局變量,形成靜態全局變量。

?

  • 要搞清楚靜態全局變量和非靜態全局變量的區別。區別是在鏈接屬性上不同。

?

3、register

(1)register修飾的變量。編譯器會盡量將它分配在寄存器中(平時分配的一般的變量都是在內存中的)

?

  • 分配在寄存器中一樣的用,但是讀寫效率會高很多。所以register修飾的變量用在那種變量被反復高頻率的使用,通過改善這個變量的訪問效率可以極大的提升程序運行效率時。所以register是一種極致提升程序運行效率的手段。

?

(2)uboot中用到了一個register類型的變量gd

?

  • 這個變量是用來存uboot的全局變量(gd就是global data)。因為這個全局變量在整個uboot中到處都被訪問,所以定義成register的。

?

(3)平時寫代碼要被定義成register這種情況很少,一般慎用。

(4)register編譯器只能承諾盡量將register修飾的變量放在寄存器中,但是不保證一定放在寄存器中。主要原因是因為寄存器數量有限,不一定有空用。

4、extern

(1)extern主要用來聲明全局變量,聲明的目的主要是在a.c中定義全局變量而在b.c中使用該變量。

?

  • C語言中程序的編譯時以單個.c源文件為單位的,因此編譯a.c時只考慮a.c中的內容(不會考了b.c的內容),這就導致a.c中使用了b.c中定義的變量時在編譯時報錯。解決方案是聲明。
  • 應該在a.c中使用g_b之前先聲明g_b,聲明就是告訴a.c我在別的文件中定義了g_b,并且它的原型和聲明的一樣,將來在鏈接的時候鏈接器會在別的.o文件中找到這個同名變量。聲明一個全局變量就要用到extern關鍵字

?

5、volatile

(1)C語言中volatile用來修飾一個變量,表示這個變量可以被編譯器之外的東西改變。

?

  • 編譯器之內的意思是變量的值的改變是代碼的作用,編譯器之外的改變就是這個改變不是代碼造成的,或者不是當前代碼造成的,編譯器在編譯當前代碼時無法預知。
  • 譬如在中斷處理程序isr中更改了變量的值,譬如多線程中在別的線程更改了這個變量的值,譬如硬件自動更改了這個變量的值(一般這個變量是一個寄存器的值)。
  • 中斷isr中引用的變量,多線程中共用的變量,硬件會更改的變量,三者都是編譯器在編譯時無法預知的更改,此時應用使用volatile告訴編譯器這個變量屬于這種(可變的、易變的)情況。編譯器在遇到volatile修飾的變量時就不會對改變量的訪問進行優化,就不會出現錯誤。

?

(2)編譯器的優化在一般情況下非常好,可以幫助提升程序效率。但是在特殊情況(volatile)下,變量會被編譯器想象之外的力量所改變,此時如果編譯器沒有意識到而去優化則就會造成優化錯誤,優化錯誤就會帶來執行時錯誤。而且這種錯誤很難被發現。

(3)volatile是程序員意識到需要volatile然后在定義變量時加上volatile,如果你遇到了應該加volatile的情況而沒有加程序可能會被錯誤的優化。如果在不應該加volatile而加了的情況程序不會出錯只是會降低效率。所以我們對于volatile的態度應該是:正確區分,該加的時候加不該加的時候不加,如果不能確定該不該加為了保險起見就加上。

6、restrict

(1)c99中才支持的,所以很多延續c89的編譯器是不支持restrict關鍵字,gcc支持的。

(2)restrict也是和編譯器行為特征有關的。

(3)restrict只用來修飾指針,不能修飾普通變量。

(4)http://blog.chinaunix.net/uid-22197900-id-359209.html

(5)memcpy和memmove的區別

7、typedef

?

  • typedef在C語言關鍵字歸類上屬于存儲類關鍵字,但是實際上和存儲類沒關系。

?

?

四、作用域詳解

?

1、局部變量的代碼塊作用域

  • 代碼塊基本可以理解為一對大括號{}括起來的部分。
  • 代碼塊不等于函數,因為if ?while for都有{}。所以代碼塊<=函數。
  • 局部變量的作用域是代碼塊作用域,也就是說一個局部變量可以被訪問和使用的范圍僅限于定義這個局部變量的代碼塊中定義式之后的部分。

?

2、函數名和全局變量的文件作用域

?

?

  • 文件作用域的意思就是全局的訪問權限,也就是說整個.c文件中都可以訪問這些東西。這就是平時所說的局部和全局,全局就是文件作用域。
  • 詳細準確的說:函數和全局變量的作用域是定義所在的整個.c文件之內定義式之后的部分。

?

3、注意

?

  • 在c89標準的編譯器中(現在很多編譯器還延續使用c89標準),所有的局部變量必須先定義在最前面,在變量定義之前不能有一句執行代碼。
  • 在c99標準的編譯器中(gcc兼容c99標準)可以允許在代碼塊內任意地方定義變量。但是允許定義的變量還是只能使用在定義了之后,定義之前還是不能用的。

?

4、同名變量的掩蔽規則

?

  • 問題:編程時,不可避免會出現同名變量。變量同名后不一定會出錯。
  • 首先,如果兩個同名變量作用域不同且沒有交疊,這種情況下同名沒有任何影響。
  • 其次,如果兩個同名變量作用域有交疊,C語言規定在作用域交疊范圍內,作用域小的一個變量會掩蔽掉作用域大的那個(縣官不如現管)。

?

五、變量的生命周期

1、棧變量的生命周期

  • 局部變量(棧變量)存儲在棧上,生命周期是臨時的。臨時的意思就是說:代碼執行過程中按照需要去創建、使用、消亡的。
  • 譬如一個函數內定義的局部變量,在這個函數每一次被調用時都會創建一次,然后使用,最后在函數返回的時候消亡。
  • 思考:一個函數內的局部變量為什么在函數外不能使用?
  • 思考:局部變量為什么分配在棧上?或者說局部變量為什么是臨時生命周期?

?

2、堆變量的生命周期

?

  • 首先要明白:堆內存空間是客觀存在的,是由操作系統維護的。我們程序只是去申請然后使用然后釋放。
  • 我們只關心我們程序使用堆內存的這一段時間,因此堆變量也有了自己的生命周期,就是:從malloc申請時誕生,然后使用,直到free時消亡。
  • 所以堆內存在malloc之前和free之后不能再去訪問,因此堆內存在實踐編程時都是被反復的malloc和free的。

?

3、數據段、bss段變量的生命周期

?

  • 全局變量的生命周期是永久的。永久的意思就是在程序被執行時誕生,在程序終止時消亡。
  • 全局變量所占用的內存是不能被程序自己釋放的,所以程序如果申請了過多的全局變量會導致這個程序一直占用大量內存。

?

4、代碼段、只讀段的生命周期

?

  • 其實就是程序執行的代碼,其實就是函數,它的生命周期是永久的。不過一般代碼的生命周期我們并不關注。
  • 有時候放在代碼段的不只是代碼,還有const類型的常量,還有字符串常量。(const類型的常量、字符串常量有時候放在rodata段,有時候放在代碼段,取決于平臺)

?

六、鏈接屬性

?

1、C語言程序的組織架構:多個C文件+多個h文件

  • (完整的一個C語言程序(譬如linux內核、uboot)由多個c文件和多個h文件組成的。
  • 程序的生成過程就是:編譯+鏈接。
  • 編譯是為了將函數/變量等變成.o二進制的機器碼格式,鏈接是為了將各個獨立分開的二進制的函數鏈接起來形成一個整體的二進制可執行程序。

2、編譯以文件為單位、鏈接以工程為單位

  • 編譯器工作時是將所有源文件依次讀進來,單個為單位進行編譯的。
  • 鏈接的時候實際上是把第一步編譯生成個單個的.o文件整體的輸入,然后處理鏈接成一個可執行程序。

3、三種鏈接屬性:外連接、內鏈接、無鏈接

  • 外連接的意思就是外部鏈接屬性,也就是說這家伙可以在整個程序范圍內(言下之意就是可以跨文件)進行鏈接,譬如普通的函數和全局變量屬于外連接。
  • 內鏈接的意思就是(c文件內部)內部鏈接屬性,也就是說這家伙可以在當前c文件內部范圍內進行鏈接(言下之意就是不能在當前c文件外面的其他c文件中進行訪問、鏈接)。static修飾的函數/全局變量屬于內鏈接。
  • 無連接的意思就是這個符號本身不參與鏈接,它跟鏈接沒關系。所有的局部變量(auto的、static的)都是無連接的

4、函數和全局變量的同名沖突

(1)因為函數和全局變量是外部鏈接屬性,就是說每一個函數和全局變量將來在整個程序中所有的c文件都能被訪問,因此在一個程序中的所有c文件中不能出現同名的函數/同名的全局變量。

(2)最簡單的解決方案就是起名字不要重復,但是很難做到。主要原因是一個很大的工程中函數和全局變量名字太多了,而且一個大工程不是一個人完成的,是很多人協作完成,所以很難保證不會重名。解決方案呢?

(3)現代高級語言中完美解決這個問題的方法是命名空間namespace(其實就是給一個變量帶上各個級別的前綴)。但是C語言不是這么解決的。

?

  • C語言比較早碰到這個問題,當時還沒發明namespace概念,當時C語言就發明了一種不是很完美但是湊活能用的解決方案,就是三種鏈接屬性的方法。
  • C語言的鏈接屬性解決重名問題思路是這樣的:我們將明顯不會在其他c文件中引用(只在當前c文件中引用)的函數/全局變量,使用static修飾使其成為內鏈接屬性,這樣在將來連接時即使2個c文件中有重名的函數/全局變量,只要其中一個或2個為內鏈接屬性就沒事。
  • 這種解決方案在一定程度上解決了問題。但是沒有從根本上解決問題,留下了很多麻煩。所以這個就導致了C語言寫很大型的項目難度很大。

?

5、static的第二種用法:修飾全局變量和函數

  • 普通的(非靜態)的函數/全局變量,默認的鏈接屬性是外部的;
  • static(靜態)的函數/全局變量,鏈接屬性是內部鏈接。

?

七、總結

1、普通(自動)局部變量分配在棧上,作用域為代碼塊作用域,生命周期是臨時,連接屬性為無連接。定義時如果未顯式初始化則其值隨機,變量地址由運行時在棧上分配得到,多次執行時地址不一定相同,函數不能返回該類變量的地址(指針)作為返回值。

2、靜態局部變量分配在數據段/bss段(顯式初始化為非0則在數據段,顯式初始化為0或未顯示初始化則在bss段),作用域為代碼塊作用域(人為規定的),生命周期為永久(天然的),鏈接屬性為無連接(天然的)。定義時如果未顯式初始化則其值為0(天然的),變量地址由運行時環境在加載程序時確定,整個程序運行過程中唯一不變;靜態局部變量其實就是作用域為代碼塊作用域(同時鏈接屬性為無連接)的全局變量。靜態局部變量可以改為用全局變量實現(程序中盡量避免用全局變量,因為會破壞結構性)。

3、靜態全局變量/靜態函數和普通全局變量/普通函數的唯一差別是:static使全局變量/函數的鏈接屬性由外部鏈接(整個程序所有文件范圍)轉為內部鏈接(當前c文件內)。這是為了解決全局變量/函數的重名問題(C語言沒有命名空間namespace的概念,因此在程序中文件變多之后全局變量/函數的重名問題非常嚴重,將不必要被其他文件引用的全局變量/函數聲明為static可以很大程度上改善重名問題,但是仍未徹底解決)。

4、寫程序盡量避免使用全局變量,尤其是非static類型的全局變量。能確定不會被其他文件引用的全局變量一定要static修飾。

5、注意區分全局變量的定義和聲明。一般規律如下:如果定義的同時有初始化則一定會被認為是定義;如果只是定義而沒有初始化則有可能被編譯器認為是定義,也可能被認為是聲明,要具體分析;如果使用extern則肯定會被認為是聲明(實際上使用extern也可以有定義,實際上加extern就是明確聲明這個變量為外部鏈接屬性)。

6、全局變量應該定義在c文件中并且在頭文件中聲明,而不要定義在頭文件中(因為如果定義在頭文件中,則該頭文件被多個c文件包含時該全局變量會重復定義)。

7、在b.c中引用a.c中定義的全局變量/函數有2種方法:一是在a.h中聲明該函數/全局變量,然后在b.c中#include <a.h>;二是在b.c中使用extern顯式聲明要引用的函數/全局變量。其中第一種方法比較正式。

8、存儲類決定生命周期,作用域決定鏈接屬性。

9、宏和inline函數的鏈接屬性為無連接。

總結

以上是生活随笔為你收集整理的存储类、作用域、生命周期、链接属性的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。