關於C語言中static,const,volatile與typedef的一些總結

        最近開始要找工作,開始不停的做筆試題,在這個過程中發現不少筆試題對C語言的基礎都十分重視,幾乎關鍵字的考察基本都會涉及到。於是就開始把一些C語言中的常考到的關鍵字都總結一下,一方面方便自己學習的時候可以查閱,另一方面也希望可以幫助到其他人。好的,廢話不多說,內容正式開始……
1、關鍵字static的作用:
1)在函數體內,一個被聲明爲靜態的變量在這一函數調用的過程中只會被初始化一次,該變量存放在靜態變量區,而且並不會因爲函數的退出就釋放其內存,其生命週期爲整個程序生命週期。
2)被聲明爲static的全局變量,僅在當前文件中可見,對於其他文件則無法實現對該變量的訪問。
3)static也可以修飾函數,表示該函數僅能在當前文件中被使用,不能在其他文件中被調用。
4)static修飾的變量只會被初始化一次

例:
#include <stdio.h>
void func_1(void)
{
	static int i = 100;
	i++;
	printf("%s:i=%d\n"__func__,%d);
}
void func_2(void)
{
	int i = 100;
	i++;
	printf("%s:i=%d\n"__func__,%d);
}
int main(void)
{
	func_1();
	func_2();
	func_1();
	func_2();
	func_1();
	func_2();
	return 0;
}

運行結果是:


2、關鍵字const的作用
1、在C語言中,const修飾的變量是隻讀的,其本質仍然是變量。這與常量不同,常量存放在內存的只讀區域,本質上是不允許修改的,但是被const修飾的變量在內存中仍然佔用空間,仍然是有辦法修改的,但是編譯器並不允許你去修改它的值
2、const是給編譯器看的,只有在編譯期間有效。一旦程序運行起來,const便失去作用,如果發生中斷,在中斷處理函數中仍然可以改變該變量的值
3、const並不會影響變量的取值範圍,也不影響變量的算術屬性。換言之,所謂的const int或者int const都是int類型,只是它們比一般的int類型變量多了"只讀"屬性。
 const的常用用法:
	const int i;	//定義一個整型只讀變量i,其值不能以左值的方式進行修改
	int const i;	//這種定義方法與上一種方法是等價的

 易混用法:

	const int* p;	//p可變,p所指向的內容不可改變
	int const* p;	//這種用法與上面相同
	int* const p;	//p不可變,p所指向的內容可變
	const int* const p;	//p與p所指向的內容均不可變

簡單的記爲:左數右指

        當const修飾*號左邊時,說明該變量是一個常量指針,其指向的數據不可改變

        當const修飾*號右邊時,說明該變量是一個指針常量,其地址不可改變

這裏需要注意的一點是,被const關鍵字所修飾的變量不能以左值的方式直接修改,但仍然可以使用指針的形式間接的修改,請看下面代碼

#include <stdio.h>
int main(void)
{
	const int i = 100;
	int* p = (int*)&i;
	printf("i=%d\n",i);
	*p = 200;
	printf("i=%d\n",i);
	return 0;
}

運行結果是:


const也可用於修飾函數參數表示在函數體內不希望改變參數的值。


3、關鍵字volatile的作用

        在《C程序設計語言》一書中,對volatile的描述是這樣的:"volatile用於強制某個實現屏蔽可能的優化。例如,對於具有內存映像輸入/輸出的機器,指向設備寄存器的指針可以聲明爲指向volatile的指針,目的是爲了防止編譯器通過指針刪除明顯多餘的引用。"這樣可能難以理解,所以通過以下一個例子來說明:
#include <stdio.h>
int main(void)
{
	int obj = 100;
	int a = 0;
	int b = 0;
	a = obj;
	sleep(5);		//硬件中斷髮生處
	b = obj;
	printf("a=%d\n",a);
	printf("b=%d\n",b);
	return 0;
}
在上述代碼中,由於obj變量在聲明並定義後沒有再做爲左值,爲了提高效率,編譯器對它進行了一次優化。它將這個變量從內存中讀到寄存器中,並在
寄存器中保留了這個變量的備份。程序每次使用obj變量給a和b兩個變量賦值時,程序直接從寄存器中讀取obj的備份。由於訪問寄存器的速度要遠遠大於訪問內存的速度,這樣在提高程序運行效率的同時也帶來了安全隱患。假設在程序中sleep(5)語句處發生了一次中斷,在中斷服務程序中,obj的值被改爲200了。由於程序已經被優化,在讀取obj時並不是從內存中(obj=200)讀取obj變量,而是直接從寄存器中(obj=100)讀取obj變量之前的拷貝。那麼上述代碼運行完後的a、b的值均爲100。
通過上面的例子,我們可以知道volatile關鍵字的作用是強制編譯器每次都從內存中讀取變量的值,而不去優化變量。
在實際應用中經常用於:
1、被中斷處理函數修改後仍要供其他程序訪問的變量
2、多線程編程中共享的變量
3、在嵌入式編程中,對帶有存儲器映射的硬件寄存器通常也需要使用volatile聲明,因爲每次對硬件寄存器的讀寫都可能有不同的意義。

在筆試中關於volatile的問題有:
1.Q:const與volatile能修飾同一個變量嗎?爲什麼?
A:可以,const和volatile的語義並不衝突。const表示該程序不應該試圖去修改這個變量,而volatile表示該變量可能會被未知的因素所修改,因此每次讀取該變量都應該從內存中去讀取。這個問題中最常見的例子是在嵌入式編程中經常出現對只讀狀態寄存器的訪問。
2.Q:volatile能修飾指針嗎?
A:可以,在嵌入式中經常會有這樣的語句:
#define GPH0CFG   (*((volatile unsigned int*)0xE0200C00))	//一個宏定義,GPH0CFG是寄存器的名字

3.Q:以下這個函數能實現求函數的平方嗎?如果不能,請回答出它存在什麼問題?

int square(volatile int *ptr)
{
	return ((*ptr) * (*ptr));
}

A:這個函數不一定能求平方。雖然在C語言中((*ptr)*(*ptr))是一個語句,但在彙編中仍是分爲幾步執行的。如果程序在將*ptr的值使用MOV指令讀入寄存器後,突然發生了一次中斷,而中斷處理函數又修改了*ptr的值。那麼再將*ptr使用MOV指令讀入寄存器就不再是原來的值了。這樣返回的值自然也就不是原來傳入參數的平方了。

4關鍵字typedef
        typedef是C語言中用來建立新的數據類型名的。它的用法是這樣的:typedef int Length;這樣Length就與int具有同等意義,可以使用Length來進行類型聲明,類型轉換等,它和類型int是完全相同的。但是這裏有一點需要注意:typedef並沒有創建出一個新的類型Length出來,它只是爲某個已經存在的類型添加了一個別名而已。typedef也並沒有賦予Length任何新的語義:使用Length定義的變量與使用int定義的變量完全相同。事實上,typedef有些類似於#define語句,但是由於typedef是由編譯器解釋的,因此它的文本替換功能要強於預處理器的能力。
在實際應用中,使用typedef有3個好處:
1、簡化表達式,降低編程中出錯的機會,例如:
typedef int (*PFI)(char *,char *);
PfI strcmp,numcmp;
2、可以使程序參數化,以提高程序的可移植性。如果typedef聲明的數據類型與機器有關,那麼當程序移植到其他機器上時,只需修改typedef類型定義即可。最常見的是標準庫中的size_t和ptrdiff_t等
3、提高程序的可閱讀性,便於維護。
關於typedef的一個問題:
Q:閱讀以下代碼。這段代碼哪裏運行會出錯?爲什麼?
#include <stdio.h>
#define Pchar char*
typedef char* pStr;
int main(void)
{
	char string[8] = "Phoenix";
	const char* p1 = string;
	char* const p2 = string;
	const pStr p3 = string;
	pStr const p4 = string;
	const Pchar p5 = string;
	Pchar const p6 = string;
	p1++;	//語句1
	p2++;	//語句2
	p3++;	//語句3
	p4++;	//語句4
	P5++;	//語句5
	p6++;	//語句6
}
A:程序中的語句2,語句3,語句4和語句6會報錯,因爲這4個語句嘗試去修改一個指針常量的地址。在這個程序中,關於const單獨修飾的變量的問題在文章上部分已經講過,這裏不再重提。這裏主要說一下#define語句和typedef跟const的問題。首先是#define語句。這是一個宏定義,在程序的運行過程中由預處理器處理。在程序中直接使用char*對Pchar進行文本替換,所以下面兩個語句跟其後面的註釋是等價的的:
	const Pchar p5 = string;	//等價於const char* p5 = string;
	Pchar const p6 = string;	//等價於char* const p6 = string;
而在文章的上面也提到了typedef是由編譯器處理的,它並不是單純的進行文本替換。它爲char*類型聲明瞭一個別名。讓我們來進行一下類比:
	const pStr p3 = string;		//修飾的是變量p3,而p3是一個指向char類型變量的指針變量,它指向的內容(也就是string的首地址)不可改變
	pStr const p4 = string;		//修飾的是變量p4,而p4是一個指向char類型變量的指針變量,它指向的內容(也就是string的首地址)不可改變
上面兩個語句有沒有讓你聯想到const的某種用法?下面讓我們來進行一下類比:
	const int a = 1;	
	int const b = 1;
上述兩條語句其實和使用pStr定義的語句在形式上是一樣的。變量其實只是一塊內存而已。const修飾了這個變量,就表示這塊內存中的內容是不可改變的。同樣的,在使用pStr定義的語句中,const修飾的是一個指針變量p3,在這塊內存(p3)當中,它所存放的內容是string數組的首地址,這個內容也是不可改變的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章