動態內存分配及變量存儲類別(第二部分)

5. C語言變量的存儲類別和生存期

我們知道,變量是有數據類型的,用以說明它占用多大的內存空間,可以進行什么樣的操作。

除了數據類型,變量還有一個屬性,稱為“存儲類別”存儲類別就是數據在內存中的存放區域。一個正在運行的C程序的內存空間可以分為五個區域:程序代碼區、靜態數據區、堆區、棧區和命令行參數區,其中靜態數據區和棧區可以用來存放變量的值。

靜態數據區的內存在程序啟動時就已經由操作系統分配好,占用的空間固定,程序運行期間不再改變,程序運行結束后才由操作系統釋放;它可以存放全局變量、靜態變量、一般常量和字符串常量。

棧區的內存在程序運行期間由操作系統根據需要來分配使用到變量才分配內存;如果定義了變量但沒有執行到該代碼,也不會分配內存),占用的空間實時改變,使用完畢后立即釋放,不必等到程序運行結束;它可以存放局部變量、函數參數等。

可以通過C語言中的關鍵字來控制變量的存放區域;C語言共有 4 個關鍵字用來指明變量的存儲類別:auto(自動的)、static(靜態的)、register(寄存器的)、extern(外部的)。

知道了變量的存儲類別,就可以知道變量的生存期。通俗地講,生存期指的是在程序運行過程中,變量從創建到銷毀的一段時間,生存期的長短取決于變量的存儲類別,也就是它所在的內存區域。

auto 變量

auto 是自動或默認的意思,很少用到,因為所有的變量默認就是 auto 的。也就是說,定義變量時加不加 auto 都一樣,所以一般把它省略,不必多此一舉。

例如:

  1. int n = 10;

  1. auto int n = 10;

的效果完全一樣。

6. C語言extern變量和函數

在所有的代碼塊(函數、if 塊、switch 塊等)之外定義的變量稱為全局變量,它的作用范圍默認是整個程序,也就是所有的源文件,包括 .c 和 .h 文件。

如果你一直在編寫單個 .c 文件的程序,那么請注意,全局變量的作用范圍不是從變量定義處到該文件結束,在其他文件中也有效。

雖然全局變量的作用范圍是整個程序,但是如果希望在 a.c 中使用 b.c 中的變量,也必須先進行聲明。聲明使用 extern 關鍵字,請看下面的代碼。

a.c 源碼:

1 #include <stdio.h>
2 #include <stdlib.h>
3 extern int num; // 必須對 num 進行聲明
4 int main ()
5 {
6     printf("num = %d", num);
7     system("pause");
8     return 0;
9 }

b.c 源碼:

1 int num = 100; // 對 num 進行定義

運行結果:
num = 100

我們在 b.c 中定義了一個全局變量 num,在 a.c 中調用了它。extern int num; 的作用是告訴編譯器 num 不在 a.c 中,請到其他文件中查找。如果沒有 extern,編譯器就會在當前文件中查找,發現沒有就會報錯。

提示:編譯是針對單個源文件的,在編譯 a.c 時編譯器找不到 num,鏈接時才會在 b.c 的目標代碼(.obj 文件)中找到 num。

與其他變量不同,extern 變量有聲明和定義之分。

extern 變量的定義格式為:

  1. extern type name = value;

不過 extern 可以省略(我們通常就是這么做的),全局變量默認就是 extern 的,如 b.c 文件所示。

聲明格式為:

  1. extern type name;


注意:

  • 在定義 extern 變量時不能省略 value,否則就變成了變量聲明。
  • 聲明 extern 變量時要指明數據類型(必須和定義時的數據類型一致)。
  • 聲明可以有多次,定義只能有一次。


在 a.c 中,我們在所有代碼塊外部對 num 進行了聲明,這個時候 num 的作用范圍是 a.c 整個文件(確切的說是從聲明開始處到文件結束)。如果在代碼塊內部聲明會怎樣呢?

對 a.c 進行更改:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int main ()
 4 {
 5     {
 6         extern int num;
 7         printf("num = %d", num);
 8     }
 9     printf("num = %d", num);
10     system("pause");
11     return 0;
12 }

編譯時報錯,第 9 行的 num 未聲明。這說明 extern 變量的作用域跟它的聲明位置有關,在代碼塊內聲明的 extern 變量在代碼塊外無效。

extern 函數

從本質上講,函數和變量是類似的,它們都指向內存中的一塊區域:函數指向存放了函數體二進制代碼的程序代碼區,變量指向靜態數據區、棧區或堆區。

extern 除了用于變量,也可以用于函數,請看下面的代碼:

sum.c 源碼:

1 int sum(int n1, int n2)
2 {
3     return n1 + n2;
4 }

main.c 源碼:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 extern int sum(int, int);
 4 int main ()
 5 {
 6     int num1 = 20, num2 = 110;
 7     printf("%d + %d = %d", num1, num2, sum(num1, num2));
 8     system("pause");
 9     return 0;
10 }

運行結果:
20 + 110 = 130

我們在 sum.c 中定義了一個函數 sum() 用來計算兩個數的和,在 main.c 中對函數進行了調用。extern 的作用是告訴編譯器 sum() 函數不在 main.c 中,請到其他文件中去查找。

但是,函數和變量的聲明有所不同,對于函數,你可以省略 extern。例如將 main.c 中的:

  1. extern int sum(int, int);

改為:

  1. int sum(int, int);

仍然能夠編譯通過并正確運行。

這是因為,函數的定義和聲明區別很明顯,有函數體就是定義,沒有函數體就是聲明,所以有沒有 extern 都是函數聲明。但是變量不一樣,沒有 extern 就是變量定義,重復定義是錯誤的。

?

?7.?C語言static變量和函數

上一節我們講到,全局變量和函數的作用范圍默認是整個程序,也就是所有的源文件。這給我們帶來了很大的方便,讓我們能夠在 A 文件中調用 B 文件中定義的變量和函數,不必把所有的代碼都集中到一個文件,有利于模塊化的程序設計。

但是有時候這也會帶來沖突,例如在 a.c 中定義了一個全局變量 n,在 b.c 中又定義了一次,編譯時就會發生重復定義的錯誤,因為變量只能定義一次。

如果兩個文件都是我們自己編寫的或者其中一個是,遇到這樣的情況還比較好處理,改變變量的名字就可以;但如果兩個文件都是其他程序員編寫的,或者是第三方的庫,修改起來就頗費精力了。所以,實際開發中我們一般將不需要被其他文件調用的全局變量或函數的作用范圍限制在當前文件中。

可以通過 static 關鍵字來限制,請看下面的代碼。

a.c 源碼:

1 #include <stdio.h>
2 static int n = 10;
3 void print_n_a()
4 {
5     printf("n(a.c) = %d\n", n);
6 }

b.c 源碼:

1 #include <stdio.h>
2 static int n = 20;
3 void print_n_b()
4 {
5     printf("n(b.c) = %d\n", n);
6 }

main.c 源碼:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int n = 100;
 4 int main ()
 5 {
 6     print_n_a();
 7     print_n_b();
 8     printf("n(main.c) = %d\n", n);
 9     system("pause");
10     return 0;
11 }

運行結果:
n(a.c) = 10
n(b.c) = 20
n(main.c) = 100

我們在 a.c、b.c 和 main.c 中都定義了變量 n,a.c 和 b.c 中的變量 n 都只在各自的文件內有效,main.c 中的變量 n 在整個程序內有效。

由此可見,加了 static 的變量或函數的作用范圍僅限于當前文件,對其他源文件隱藏,利用這一特性可以在不同的文件中定義同名的變量或函數,而不必擔心命名沖突。

static 局部變量

static 聲明的變量稱為靜態變量,不管是全局變量還是局部變量,都存儲在靜態數據區(全局變量本來就存儲在靜態數據區,即使不加 static)。

靜態數據區的數據在程序啟動時就會初始化,直到程序運行結束;對于代碼塊中的靜態局部變量,即使代碼塊執行結束,也不會銷毀。

注意:靜態數據區的變量只能初始化(定義)一次,以后只能改變它的值,不能再被初始化,即使有這樣的語句,也無效。

請看下面的代碼:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int main ()
 4 {
 5     int result, i;
 6     for(i = 1; i<=100; i++)
 7     {
 8         result = sum(i);
 9     }
10     printf("1+2+3+...+99+100 = %d\n", result);
11     system("pause");
12     return 0;
13 }
14 int sum(int n)
15 {
16 // 也可以不賦初值 0,靜態數據區的變量默認初始化為 0
17     static int result = 0;
18     result += n;
19     return result;
20 }

運行結果:
1+2+3+...+99+100 = 5050

我們在 sum() 中定義了一個靜態局部變量 result,它存儲在靜態數據區,sum() 函數執行結束也不會銷毀,下次調用繼續有效。靜態數據區的變量只能初始化一次,第一次調用 sum() 時已經對 result 進行了初始化,所以再次調用時就不會初始化了,也就是說 static int result = 0; 語句無效。

靜態局部變量雖然存儲在靜態數據區,但是它的作用域僅限于定義它的代碼塊,sum() 中的 result 在函數外無效,與 main() 中的 result 不沖突,除了變量名一樣,沒有任何關系。

總結起來,static 變量的主要作用有兩個。

1) 隱藏

程序有多個源文件時,將全局變量或函數的作用范圍限制在當前文件,對其他文件隱藏。

2) 保持變量內容的持久化

將局部變量存儲到靜態數據區。靜態數據區的內存在程序啟動時就已分配好(內存中所有的字節默認值都是0x00),直到程序運行結束。

?

8. C語言register變量

一般情況下,變量的值是存儲在內存中的,CPU 每次使用數據都要從內存中讀取。如果有一些變量使用非常頻繁,從內存中讀取就會消耗很多時間,例如 for 循環中的增量控制:

1 int i;
2 for(i=0; i<1000; i++)
3 {
4 // Some Code
5 }

執行這段代碼,CPU 為了獲得 i,會讀取 1000 次內存。

為了解決這個問題,可以將使用頻繁的變量放在CPU的通用寄存器中,這樣使用該變量時就不必訪問內存,直接從寄存器中讀取,大大提高程序的運行效率。

寄存器、緩存、內存

為了加深對 register 變量的理解,這里有必要講一下CPU寄存器。

按照與CPU的遠近來分,離CPU最近的是寄存器,然后是緩存,最后是內存。

寄存器是最貼近CPU的,而且CPU只在寄存器中進行存取。寄存的意思是暫時存放數據,不用每次都從內存中取,它是一個臨時的存放數據的空間。

而寄存器的數據又來源于內存,于是 CPU <-- 寄存器 <-- 內存,這就是它們之間的信息交換。

那么為什么還需要緩存呢?因為如果頻繁地操作內存中同一地址上的數據會影響速度,于是就在寄存器和內存之間設置一個緩存,把使用頻繁的數據暫時保存到緩存,如果寄存器需要讀取內存中同一地址上的數據,就不用大老遠地再去訪問內存,直接從緩存中讀取即可。

緩存的速度遠高于內存,價格也是如此。

注意:緩存的容量是有限的,寄存器只能從緩存中讀取到部分數據,對于使用不是很頻繁的數據,會繞過緩存,直接到內存中讀取。所以不是每次都能從緩存中得到數據,這就是緩存的命中率,能夠從緩存中讀取就命中,否則就沒命中。

關于緩存的命中率又是一門學問,哪些數據保留在緩存,哪些數據不保留,都有復雜的算法。


注意:上面所說的CPU是指CPU核心,從市場上購買的CPU已是封裝好的套件,附帶了寄存器和緩存,插到主板上就可以用。

從經濟和速度的綜合考慮,緩存又被分為一級緩存、二級緩存和三級緩存,它們的存取速度和價格依次降低,容量依次增加。購買到的CPU一般會標出三級緩存的容量。

register 變量

寄存器的數量是有限的,通常是把使用最頻繁的變量定義為 register 的。

來看一個計算 π 的近似值的例子,求解的一個近似公式如下:


為了提高精度,循環的次數越多越好,可以將循環的增量控制定義為寄存器變量,如下所示:

 1 #include <stdio.h>
 2 #include <conio.h>
 3 int main()
 4 {
 5     register int i = 0; // 寄存器變量
 6     double sign = 1.0, res = 0, ad = 1.0;
 7     for(i=1; i<=100000000; i++)
 8     {
 9         res += ad;
10         sign=-sign;
11         ad=sign/(2*i+1);
12     }
13     res *= 4;
14     printf("pi is %f", res);
15     getch();
16     return 0;
17 }

運行結果:
pi is 3.141593

關于寄存器變量有以下事項需要注意:
1) 為寄存器變量分配寄存器是動態完成的,因此,只有局部變量和形式參數才能定義為寄存器變量。

2) 局部靜態變量不能定義為寄存器變量,因為一個變量只能聲明為一種存儲類別。

3) 寄存器的長度一般和機器的字長一致,所以,只有較短的類型如int、char、short等才適合定義為寄存器變量,諸如double等較大的類型,不推薦將其定義為寄存器類型。

4) CPU的寄存器數目有限,因此,即使定義了寄存器變量,編譯器可能并不真正為其分配寄存器,而是將其當做普通的auto變量來對待,為其分配棧內存。當然,有些優秀的編譯器,能自動識別使用頻繁的變量,如循環控制變量等,在有可用的寄存器時,即使沒有使用 register 關鍵字,也自動為其分配寄存器,無須由程序員來指定。

?

轉載于:https://www.cnblogs.com/chunlanse2014/articles/4422153.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/257434.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/257434.shtml
英文地址,請注明出處:http://en.pswp.cn/news/257434.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

oracle的em能干什么,轉載?解決Oracle的EM登錄

轉載 解決Oracle的EM登錄(2011-03-13 20:53:39)標簽&#xff1a;雜談這幾天解決了EM無法登錄的問題&#xff0c;順便也把j數據庫程序中常出現的ORA_12518錯誤解決了&#xff0c;有必要總結一下&#xff0c;我最初遇到的情況是這樣的&#xff1a;1. 編寫java程序訪問oracle數據庫…

python 回溯法 子集樹模板 系列 —— 1、8 皇后問題

問題 88格的國際象棋上擺放八個皇后&#xff0c;使其不能互相攻擊&#xff0c;即任意兩個皇后都不能處于同一行、同一列或同一斜線上&#xff0c;問有多少種擺法。 分析 為了簡化問題&#xff0c;考慮到8個皇后不同行&#xff0c;則每一行放置一個皇后&#xff0c;每一行的皇后…

Core Java Volume I — 3.6. Strings

3.6. StringsConceptually, Java strings are sequences of Unicode characters&#xff08;Java的字符串是一個Unicode序列&#xff09;. For example, the string "Java\u2122" consists of the five Unicode characters J, a, v, a, and ?. Java does not have a…

Android實用代碼七段(五)

前言 每次分享意味著每次都有進步&#xff0c;本系列以實用為主&#xff0c;歡迎和我分享和推薦好用的代碼段~~聲明歡迎轉載&#xff0c;但請保留文章原始出處:) 博客園&#xff1a;http://www.cnblogs.com農民伯伯&#xff1a; http://over140.cnblogs.com 正文 1、展開、收起…

oracle 自增1,oracle自增無法從1開始

問題描述我想讓XH字段從1開始增加,由于是varchar類型的,所以就用這種方式,但我發現我的數據表中XH字段是從217開始增加的,為什么啊問題出現的環境背景及自己嘗試過哪些方法相關代碼// 請把代碼文本粘貼到下方(請勿用圖片代替代碼)declarej number;i number;begini:1;j:1;for i …

ceph Luminous版手動安裝零散記錄

1.安裝必要的依賴包&#xff0c;關防火墻&#xff0c;向/etc/hosts內添加域名等 2.安裝ceph 配置yum源 (如果嫌慢&#xff0c;可以配置cachedir/home/yum/$basearch/$releasever和keepcache1兩個參數&#xff0c;在第一次安裝時將安裝包下載到本地做成yum源&#xff0c;給后面的…

C#最簡單最完整的webservice實例

我做java&#xff0c;但最近接觸crm所以必須研究一下C#中的webservice以備后用&#xff0c;其實就是個新手&#xff0c;哈哈&#xff0c;這個實例是我在參考了網上諸多不完整的例子的情況下&#xff0c;自己摸索完成的。期間遇到過一系列的棘手的問題&#xff0c;經過個人努力終…

2015 UESTC 數據結構專題G題 秋實大哥去打工 單調棧

秋實大哥去打工 Time Limit: 1 Sec Memory Limit: 256 MB 題目連接 http://acm.uestc.edu.cn/#/contest/show/59Description 天行健&#xff0c;君子以自強不息。地勢坤&#xff0c;君子以厚德載物。天天過節的秋實大哥又要過節了&#xff0c;于是他要給心愛的妹子買禮物。但由…

oracle怎么通過sid確定表名,如何獲取Oracle的SID列表

更好的方法是&#xff0c;如果您有權訪問主機并且Oracle安裝使用以下命令&#xff1a;lsnrctl status。這適用于Unix&#xff0c;Linux和Windows機器。 status命令將顯示所有監聽器(及其相關的SID)。C:\>lsnrctl statusLSNRCTL for 32-bit Windows: Version 10.2.0.1.0 - Pr…

51 Nod 1007 正整數分組【類01背包】

1007 正整數分組 基準時間限制&#xff1a;1 秒 空間限制&#xff1a;131072 KB 分值: 10難度&#xff1a;2級算法題將一堆正整數分為2組&#xff0c;要求2組的和相差最小。例如&#xff1a;1 2 3 4 5&#xff0c;將1 2 4分為1組&#xff0c;3 5分為1組&#xff0c;兩組和相差1…

YTU 2924: 文件操作--二進制文件讀入

2924: 文件操作--二進制文件讀入 時間限制: 1 Sec 內存限制: 128 MB提交: 58 解決: 20題目描述 現有100名學生的姓名(name)、學號(num)、英語(English)、數學(Math)、語文(Chinese)成績存儲在一個二進制文件student.dic中(姓名用char[20]&#xff0c;學號和各科成績用int存儲…

oracle 9.2.0.4,CentOS 4.7 安裝Oracle 9.2.0.4的一些問題

#vi/etc/sysconfig/iptables&#xff0c;增加如下-A INPUT -p udp -s 0/0 -d 0/0 --dport 177 -j ACCEPT-A INPUT -p tcp -s 0/0 -d 0/0 --dport telnet -j ACCEPT-A INPUT -p tcp -s 0/0 -d 0/0 --dport ssh -j ACCEPT-A INPUT -p tcp -s 0/0 -d 0/0 --dport login -j ACCEPT-…

《機電傳動控制》----學習筆記六

《機電傳動控制》與其他學科的聯系 1、《液壓傳動與氣壓傳動》中提到的液壓控制閥中的電液伺服閥與《機電傳動控制》中的控制電動機里的伺服電機有著密切的聯系&#xff0c;都要求我們對伺服系統有著很好的理解。 2、《電路理論》中電機作為獨立的一章&#xff0c;講到了用向量…

Oracle Imp and Exp (導入和導出) 數據 工具使用

Oracle 提供兩個工具imp.exe 和exp.exe分別用于導入和導出數據。這兩個工具位于Oracle_home/bin目錄下。 導入數據exp 1 將數據庫ATSTestDB完全導出,用戶名system 密碼123456 導出到c:\export.dmp中 exp system/123456ATSTestDB filec:\export.dmp fully 其中ATSTestDB為數據庫…

[Oracle][Corruption]究竟哪些檢查影響到 V$DATABASE_BLOCK_CORRUPTION

根據 471716.1&#xff0c;11g 之后&#xff0c;下列動作如果遇到壞塊&#xff0c;都會輸出記錄到 V$DATABASE_BLOCK_CORRUPTION。- Analyze table .. Validate structure- CTAS(Create table as Select)- Export另外&#xff0c;這些也會記錄的&#xff1a;RMAN > Vali…

oracle使用loop將增加十天,使用loop循環操作DML語句

---loop循環&#xff1a;--創建測試表&#xff1a;suxingPROD>create table total3(2 t1 number(8),3 t2 number(8),4 cr date default sysdate);Table created.#測試表已經創建。--查看表中原來的數據&#xff1a;suxingPROD>select * from total3;T1 T2 CR-…

iOS富文本

iOS富文本 背景&#xff1a;前些天突然想做一個筆記本功能&#xff0c;一開始&#xff0c;覺得挺簡單的呀&#xff0c;一個UITextView,網絡緩存也不干了&#xff0c;直接本地NSUserDefault存儲&#xff0c;然后完事了&#xff0c;美工&#xff0c;弄幾張好看的圖片&#xff0c;…

SQL編程題-----1

首先&#xff0c;題目給出這個數據庫表格 要求寫出SQL語句使之變成如下表格 解決方法&#xff1a; SELECT t1.Rq,t1.勝,t2.負 FROM //t1和t2是自己命的新表格的名字 (SELECT Rq,COUNT(*) AS 勝 //As 勝意思是輸出結果時列名為”勝“FROM testtableWHERE Sh…

maven設置jdk版本

兩種方式&#xff1a;一、可以修改 MAVEN 的 setting.xml 文件&#xff0c;統一修改。<profiles> <profile> <id>jdk-1.6</id> <activation> <activeByDefault>true</activeByDefault>…

獲取系統時間出錯oracle-,oracle 獲取系統時間(轉)

Oracle中如何獲取系統當前時間select to_char(sysdate,yyyy-mm-dd hh24:mi:ss) from dual;ORACLE里獲取一個時間的年、季、月、周、日的函數select to_char(sysdate, yyyy ) from dual; --年select to_char(sysdate, MM ) from dual; --月select to_char(sysdate, dd ) f…