C语言中关键字
關鍵字
1、Volatile關鍵字有什么含意 并給出三個不同的例子?
一個定義為volatile的變量說明這變量可能會被改變,這樣編譯器不會對這個變量作優化。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子:?
1). 并行設備的硬件寄存器(如:狀態寄存器)?
2). 一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)?
3). 多線程應用中被幾個任務共享的變量?
這是區分C程序員和嵌入式系統程序員的最基本的問題。嵌入式系統程序員經常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。回答以下問題:?
1). 一個參數既可以是const還可以是volatile嗎?解釋為什么。?
2). 一個指針可以是volatile 嗎?解釋為什么。?
3). 下面的函數有什么錯誤:?
int square(volatile int *ptr) {?
return *ptr * *ptr;?
}?
下面是答案:?
1). 是的。一個例子是只讀的狀態寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。?
2). 是的。盡管這并不很常見。一個例子是當一個中服務子程序修該一個指向一個buffer的指針時。?
3). 這段代碼的有個惡作劇。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數,編譯器將產生類似下面的代碼:?
int square(volatile int *ptr) {?
int a,b;?
a = *ptr;?
b = *ptr;?
return a * b;?
}?
由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:?
long square(volatile int *ptr) {?
int a;?
a = *ptr;?
return a * a;?
}
2、關鍵字const有什么含意?
1)只讀。
2)使用關鍵字const也許能產生更緊湊的代碼。
3)使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。
我只要一聽到被面試者說:"const意味著常數",我就知道我正在和一個業余者打交道。去年Dan Saks已經在他的文章里完全概括了const的所有用法,因此ESP(Embedded Systems Programming)的每一位讀者應該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著"只讀"就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細的答案,仔細讀一下Saks的文章吧。)
如果應試者能正確回答這個問題,我將問他一個附加的問題:
下面的聲明都是什么意思?
const int a;
int const a;
onst int *a;
int * const a;
int const * a const;
前兩個的作用是一樣,a是一個常整型數。
第三個意味著a是一個指向常整型數的指針(也就是,整型數是不可修改的,但指針可以)。第四個意思a是一個指向整型數的常指針(也就是說,指針指向的整型數是可以修改的,但指針是不可修改的)。
最后一個意味著a是一個指向常整型數的常指針(也就是說,指針指向的整型數是不可修改的,同時指針也是不可修改的)。
如果應試者能正確回答這些問題,那么他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關鍵字const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關鍵字const呢?我也如下的幾下理由:
?; 關鍵字const的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數為常量是為了告訴了用戶這個參數的應用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學會感謝這點多余的信息。(當然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)
?; 通過給優化器一些附加的信息,使用關鍵字const也許能產生更緊湊的代碼。
?; 合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。關鍵字const有什么含意?
3、關鍵字cregister有什么含意?
使用cregister關鍵字,當我們定義的該類型的對象與C28x的標準的控制寄存器匹配時,編譯器會自動產生相關的代碼去控制對應的寄存器,使得我們可以在高級編程語言C/C++中對寄存器進行控制;如果不匹配則產生編譯器錯誤。目前可匹配此類型的寄存器包括:
IER:中斷使能寄存器
IFR:中斷標志寄存器
其定義方式為;
extern cregistervolatile unsigned int IFR;
extern cregistervolatile unsigned int IER;
cregister類型只能對整形或者指針類型進行定義,并且只在本文件的作用域內生效,它既不能在函數內定義,也不能被用在浮點類型、結構體或者共同體類型上面。如果cregister類型定義的變量是可以被外部控制修改的,那么該變量也必須同時使用volatile類型進行聲明。
在定義了寄存器之后,我們就可以直接使用寄存器的名字了,但是還有以下的限制(如果不按照規范來,則會有“Illegal use of control register”的錯誤提示):
1)IFR是不能直接寫的,它的置位操作只能通過“或”操作(操作符是|)進行修改,且操作數必須是立即數,它的復位操作只能被“與”操作(操作符是&)進行修改,例如:
IFR |= 0x4;
IFR &= 0x0800
2)IER寄存器除了通過“或”操作或者“與”操作進行修改之外,也可直接賦值,例如:
IER = x;
IER |= 0x100;
printf("IER =%x\n", IER);
4、關鍵字restrict有什么含意?
C99中新增加了restrict修飾的指針,由restrict修飾的指針是最初唯一對指針所指向的對象進行存取的方法,僅當第二個指針基于第一個時,才能對對象進行存取。對對象的存取都限定于基于由restrict修飾的指針表達式中。
由restrict修飾的指針主要用于函數形參,或指向由malloc()分配的內存空間。restrict數據類型不改變程序的語義。編譯器能通過作出restrict修飾的指針是存取對象的唯一方法的假設,更好地優化某些類型的例程。
restrict是c99標準引入的,它只可以用于限定和約束指針,并表明指針是訪問一個數據對象的唯一且初始的方式.即它告訴編譯器,所有修改該指針所指向內存中內容的操作都必須通過該指針來修改,而不能通過其它途徑(其它變量或指針)來修改;這樣做的好處是,能幫助編譯器進行更好的優化代碼,生成更有效率的匯編代碼.如
int *restrict ptr;
ptr指向的內存單元只能被 ptr 訪問到,任何同樣指向這個內存單元的其他指針都是未定義的,直白點就是無效指針。
restrict 的出現是因為 C 語言本身固有的缺陷,C 程序員應當主動地規避這個缺陷,而編譯器也會很配合地優化你的代碼.
例子 :
int ar[10];
int * restrict restar=(int *)malloc(10*sizeof(int));
int *par=ar;
for(n=0;n<10;n++)
{?? par[n]+=5;
restar[n]+=5;
ar[n]*=2;
par[n]+=3;
restar[n]+=3;
}
因為restar是訪問分配的內存的唯一且初始的方式,那么編譯器可以將上述對restar的操作進行優化:restar[n]+=8;而par并不是訪問數組ar的唯一方式,因此并不能進行下面的優化:par[n]+=8;因為在par[n]+=3前,ar[n]*=2進行了改變。使用了關鍵字restrict,編譯器就可以放心地進行優化了。
關鍵字restrict有兩個讀者。
一個是編譯器,它告訴編譯器可以自由地做一些有關優化的假定。另一個讀者是用戶,他告訴用戶僅使用滿足restrict要求的參數。一般,編譯器無法檢查您是否遵循了這一限制,如果您蔑視它也就是在讓自己冒險。
5. 關鍵字static的作用是什么?
在C語言中,關鍵字static有三個明顯的作用:
?;一旦聲明為靜態變量,在編譯時刻開始永遠存在,不受作用域范圍約束,但是如果是局部靜態變量,則此靜態變量只能在局部作用域內使用,超出范圍不能使用,但是它確實還占用內存,還存在.
?;在模塊內(但在函數體外),一個被聲明為靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
?;在模塊內,一個被聲明為靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地范圍內使用。
大多數應試者能正確回答第一部分,一部分能正確回答第二部分,很少人能懂得第三部分。這是一個應試者的嚴重的缺點,因為他顯然不懂得本地化數據和代碼范圍的好處和重要性。
6. 關鍵字far的作用是什么?
默認情況下,C/C++的編譯器只支持到低64K的存儲空間,且所有的指針都默認為16位的。但是C28x的存儲空間一般都在16bit以上,此時通過使用far類型,C代碼中的指針可以為22bit寬(需要兩個存儲單元來存儲),并支持對高達4M的存儲空間的存取。(在C++中,不支持far關鍵字,對高地址的存取是通過使用在編譯器選項中開啟large memory model選項實現的。)
當一個變量被定義為far類型時,它被存儲在高于64K的地址范圍中,此時far類型的全局變量不再保存在.bss段中,而是保存在一個新的段,即.ebss中,相同的道理,far類型的const變量也被保存到.econst段中。注意:只有全局變量和靜態變量可以被定義為far類型,函數中的非靜態變量(自動存儲對象)因為被分配到棧中,被自動當near類型來處理。對于結構體,如果結構體被聲明為far類型,則全部成員都會自動繼承為far類型。舉例如下;
int far *ptr; // 指針指向far類型的int,但是指針本身是near類型的
int * far ptr; // 指針指向near類型的int,但是指針本身是far類型的
int far * far ptr;//指針和指向的內容都是far類型的
int far*memcpy_ff(far void *dest, const far void *src, int count);// 函數的參數為兩個far類型的指針,且返回值也為far類型的指針
int *far func();//錯誤:far類型只能用于數據,不能用于函數
//因為程序地址空間本身就是22位的,最后需要注意的是,目前對于兩個far類型指針相減的操作,其結果是16位的指針。
7. 關鍵字_interrupt的作用是什么?
_interrupt用來聲明一個函數是中斷處理函數;在嚴格的ANSIC/C++模式下,也可以使用interrupt關鍵字來代替。中斷處理函數要遵循特殊的寄存器保存規則和退出順序,從而保證代碼的安全。在C/C++程序中產生中斷時,所有被中斷子程序使用,或者被中斷子程序調用的函數使用的狀態都需要被保留。此外,_interrupt定義的函數不能有參數,也沒有返回值,即:
_interrupt voidint_handler()
{
unsigned int flags;
...
}
唯一特殊的是c_int00函數,它是C/C++程序的入口點,被系統保留為默認的復位中斷函數,并在中調用main函數。因為c_int00函數不被任何函數所調用,所以它不需要保存任何狀態(畢竟是在復位和初始化狀態)。在DSP/BIOS和SYS/BIOS HWI對象中,不需要使用_interrupt關鍵字,因為Hwi_enter/Hwi_exit宏和Hwi解包器已經包含了該函數,此時使用_interrupt關鍵字會產生負面的效果。
8. 關鍵字inline的作用?
?在C語言中,如果一些函數被頻繁調用,不斷地有函數入棧,即函數棧,會造成棧空間或棧內存的大量消耗。為了解決這個問題,特別的引入了inline修飾符,表示為內聯函數。它是一種用空間換取效率的一種手段。棧空間就是指放置程式的局部數據也就是函數內數據的內存空間,在系統下,棧空間是有限的,假如頻繁大量的使用就會造成因棧空間不足所造成的程式出錯的問題,函數的死循環遞歸調用的最終結果就是導致棧內存空間枯竭。
下面我們來看一個例子:
#include <stdio.h>?
//函數定義為inline即:內聯函數?
inline char* dbtest(int a)
{?
??? return (i % 2 > 0) ? "奇" : "偶";?
}??
int main()?
{ ?
??? int i = 0;?
??? for (i=1; i < 100; i++) {?
??? ???printf("i:%d??? 奇偶性:%s /n", i, dbtest(i));
?? ?}
}
上面的例子就是標準的內聯函數的用法,使用inline修飾帶來的好處我們表面看不出來,其實在內部的工作就是在每個for循環的內部任何調用dbtest(i)的地方都換成了(i%2>0)?"奇":"偶"這樣就避免了頻繁調用函數對棧內存重復開辟所帶來的消耗。
其實這種有點類似咱們前面學習的動態庫和靜態庫的問題,使 dbtest 函數中的代碼直接被放到main 函數中,執行for 循環時,會不斷調用這段代碼,而不是不斷地開辟一個函數棧。
內聯函數的編程風格
1、關鍵字inline 必須與函數定義體放在一起才能使函數成為內聯,僅將inline 放在函數聲明前面不起任何作用。
如下風格的函數Foo 不能成為內聯函數:
inline void Foo(int x, int y); // inline 僅與函數聲明放在一起
void Foo(int x, int y)
{
}
而如下風格的函數Foo 則成為內聯函數:
void Foo(int x, int y);
inline void Foo(int x, int y) // inline 與函數定義體放在一起
{
}
所以說,inline 是一種“用于實現的關鍵字”,而不是一種“用于聲明的關鍵字”。一般地,用戶可以閱讀函數的聲明,但是看不到函數的定義。盡管在大多數教科書中內聯函數的聲明、定義體前面都加了inline 關鍵字,但我認為inline 不應該出現在函數的聲明中。這個細節雖然不會影響函數的功能,但是體現了高質量C++/C 程序設計風格的一個基本原則:聲明與定義不可混為一談,用戶沒有必要、也不應該知道函數是否需要內聯。
2、inline的使用是有所限制的
inline只適合涵數體內代碼簡單的函數數使用,不能包含復雜的結構控制語句例如while、switch,并且內聯函數本身不能是直接遞歸函數(自己內部還調用自己的函數)。
慎用內聯
內聯能提高函數的執行效率,為什么不把所有的函數都定義成內聯函數?如果所有的函數都是內聯函數,還用得著“內聯”這個關鍵字嗎?
內聯是以代碼膨脹(復制)為代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。如果執行函數體內代碼的時間,相比于函數調用的開銷較大,那么效率的收獲會很少。另一方面,每一處內聯函數的調用都要復制代碼,將使程序的總代碼量增大,消耗更多的內存空間。
以下情況不宜使用內聯:
(1) 如果函數體內的代碼比較長,使用內聯將導致內存消耗代價較高。
(2) 如果函數體內出現循環,那么執行函數體內代碼的時間要比函數調用的開銷大。
一個好的編譯器將會根據函數的定義體,自動地取消不值得的內聯(這進一步說明了inline 不應該出現在函數的聲明中)。
總結:因此,將內聯函數放在頭文件里實現是合適的,省卻你為每個文件實現一次的麻煩.而所以聲明跟定義要一致,其實是指,如果在每個文件里都實現一次該內聯函數的話,那么,最好保證每個定義都是一樣的,否則,將會引起未定義的行為,即是說,如果不是每個文件里的定義都一樣,那么,編譯器展開的是哪一個,那要看具體的編譯器而定.所以,最好將內聯函數定義放在頭文件中.?
?
總結
- 上一篇: Linux命令快捷
- 下一篇: linux-2.6.29内核配置、编译与