第一篇:詳細(xì)解析C語(yǔ)言Sizeof關(guān)鍵字_(程序員筆試常見(jiàn)問(wèn)題)[小編推薦]
sizeof,一個(gè)其貌不揚(yáng)的家伙,引無(wú)數(shù)菜鳥(niǎo)竟折腰,小蝦我當(dāng)初也沒(méi)少犯迷糊,秉著“
辛苦我一個(gè),幸福千萬(wàn)人”的偉大思想,我決定將其盡可能詳細(xì)的總結(jié)一下。但當(dāng)我總結(jié)的時(shí)候才發(fā)現(xiàn),這個(gè)問(wèn)題既可以簡(jiǎn)單,又可以復(fù)雜,所以本文有的地方并不
適合初學(xué)者,甚至都沒(méi)有必要大作文章。但如果你想“知其然,更知其所以然”的話,那么這篇文章對(duì)你或許有所幫助。
菜鳥(niǎo)我對(duì)C++的掌握尚未深入,其中不乏錯(cuò)誤,歡迎各位指正啊
1.定義:
sizeof是何方神圣sizeof乃C/C++中的一個(gè)操作符(operator)是也,簡(jiǎn)單的說(shuō)其作
用就是返回一個(gè)對(duì)象或者類型所占的內(nèi)存字節(jié)數(shù)。MSDN上的解釋為:
The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type(including aggregate types).This keyword returns a value of type size_t.其返回值類型為size_t,在頭文件stddef.h中定義。這是一個(gè)依賴于編譯系統(tǒng)的值,一 般定義為
typedef unsigned int size_t;世上編譯器林林總總,但作為一個(gè)規(guī)范,它們都會(huì)保證char、signed char和unsigned char的sizeof值為1,畢竟char是我們編程能用的最小數(shù)據(jù)類型。2.語(yǔ)法:
sizeof有三種語(yǔ)法形式,如下: 1)sizeof(object);// sizeof(對(duì)象);2)sizeof(type_name);// sizeof(類型);3)sizeof object;// sizeof 對(duì)象;所以,int i;sizeof(i);// ok sizeof i;// ok sizeof(int);// ok sizeof int;// error 既然寫法3可以用寫法1代替,為求形式統(tǒng)一以及減少我們大腦的負(fù)擔(dān),第3種寫法,忘 掉它吧!
實(shí)際上,sizeof計(jì)算對(duì)象的大小也是轉(zhuǎn)換成對(duì)對(duì)象類型的計(jì)算,也就是說(shuō),同種類型的 不同對(duì)象其sizeof值都是一致的。這里,對(duì)象可以進(jìn)一步延伸至表達(dá)式,即sizeof可以
對(duì)一個(gè)表達(dá)式求值,編譯器根據(jù)表達(dá)式的最終結(jié)果類型來(lái)確定大小,一般不會(huì)對(duì)表達(dá)式
進(jìn)行計(jì)算。如:
sizeof(2);// 2的類型為int,所以等價(jià)于 sizeof(int);sizeof(2 + 3.14);// 3.14的類型為double,2也會(huì)被提升成double類型,所以等價(jià)
于 sizeof(double);sizeof也可以對(duì)一個(gè)函數(shù)調(diào)用求值,其結(jié)果是函數(shù)返回類型的大小,函數(shù)并不會(huì)被調(diào)用,我們來(lái)看一個(gè)完整的例子: char foo(){ printf(“foo()has been called.n”);return 'a';} int main(){ size_t sz = sizeof(foo());// foo()的返回值類型為char,所以sz = sizeof(char),foo()并不會(huì)被調(diào)用
printf(“sizeof(foo())= %dn”, sz);} C99標(biāo)準(zhǔn)規(guī)定,函數(shù)、不能確定類型的表達(dá)式以及位域(bit-field)成員不能被計(jì)算s izeof值,即下面這些寫法都是錯(cuò)誤的: sizeof(foo);// error void foo2(){ } sizeof(foo2());// error struct S { unsigned int f1 : 1;unsigned int f2 : 5;unsigned int f3 : 12;};sizeof(S.f1);// error 3.sizeof的常量性
sizeof的計(jì)算發(fā)生在編譯時(shí)刻,所以它可以被當(dāng)作常量表達(dá)式使用,如: char ary[ sizeof(int)* 10 ];// ok 最新的C99標(biāo)準(zhǔn)規(guī)定sizeof也可以在運(yùn)行時(shí)刻進(jìn)行計(jì)算,如下面的程序在Dev-C++中可以 正確執(zhí)行: int n;n = 10;// n動(dòng)態(tài)賦值
char ary[n];// C99也支持?jǐn)?shù)組的動(dòng)態(tài)定義 printf(“%dn”, sizeof(ary));// ok.輸出10 但在沒(méi)有完全實(shí)現(xiàn)C99標(biāo)準(zhǔn)的編譯器中就行不通了,上面的代碼在VC6中就通不過(guò)編譯。
所以我們最好還是認(rèn)為sizeof是在編譯期執(zhí)行的,這樣不會(huì)帶來(lái)錯(cuò)誤,讓程序的可移植 性強(qiáng)些。
4.基本數(shù)據(jù)類型的sizeof 這里的基本數(shù)據(jù)類型指short、int、long、float、double這樣的簡(jiǎn)單內(nèi)置數(shù)據(jù)類型,由于它們都是和系統(tǒng)相關(guān)的,所以在不同的系統(tǒng)下取值可能不同,這務(wù)必引起我們的注
意,盡量不要在這方面給自己程序的移植造成麻煩。一般的,在32位編譯環(huán)境中,sizeof(int)的取值為4。5.指針變量的sizeof 學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu)的你應(yīng)該知道指針是一個(gè)很重要的概念,它記錄了另一個(gè)對(duì)象的地址。既
然是來(lái)存放地址的,那么它當(dāng)然等于計(jì)算機(jī)內(nèi)部地址總線的寬度。所以在32位計(jì)算機(jī)中,一個(gè)指針變量的返回值必定是4(注意結(jié)果是以字節(jié)為單位),可以預(yù)計(jì),在將來(lái)的6 4位系統(tǒng)中指針變量的sizeof結(jié)果為8。char* pc = “abc”;int* pi;string* ps;char** ppc = &pc;void(*pf)();// 函數(shù)指針 sizeof(pc);// 結(jié)果為4 sizeof(pi);// 結(jié)果為4 sizeof(ps);// 結(jié)果為4 sizeof(ppc);// 結(jié)果為4 sizeof(pf);// 結(jié)果為4 指針變量的sizeof值與指針?biāo)傅膶?duì)象沒(méi)有任何關(guān)系,正是由于所有的指針變量所占內(nèi)
存大小相等,所以MFC消息處理函數(shù)使用兩個(gè)參數(shù)WPARAM、LPARAM就能傳遞各種復(fù)雜的消
息結(jié)構(gòu)(使用指向結(jié)構(gòu)體的指針)。6.數(shù)組的sizeof 數(shù)組的sizeof值等于數(shù)組所占用的內(nèi)存字節(jié)數(shù),如: char a1[] = “abc”;int a2[3];sizeof(a1);// 結(jié)果為4,字符 末尾還存在一個(gè)NULL終止符 sizeof(a2);// 結(jié)果為3*4=12(依賴于int)
一些朋友剛開(kāi)始時(shí)把sizeof當(dāng)作了求數(shù)組元素的個(gè)數(shù),現(xiàn)在,你應(yīng)該知道這是不對(duì)的,那么應(yīng)該怎么求數(shù)組元素的個(gè)數(shù)呢Easy,通常有下面兩種寫法: int c1 = sizeof(a1)/ sizeof(char);// 總長(zhǎng)度/單個(gè)元素的長(zhǎng)度 int c2 = sizeof(a1)/ sizeof(a1[0]);// 總長(zhǎng)度/第一個(gè)元素的長(zhǎng)度 寫到這里,提一問(wèn),下面的c3,c4值應(yīng)該是多少呢 void foo3(char a3[3]){ int c3 = sizeof(a3);// c3 == } void foo4(char a4[]){ int c4 = sizeof(a4);// c4 == } 也許當(dāng)你試圖回答c4的值時(shí)已經(jīng)意識(shí)到c3答錯(cuò)了,是的,c3!=3。這里函數(shù)參數(shù)a3已不
再是數(shù)組類型,而是蛻變成指針,相當(dāng)于char* a3,為什么仔細(xì)想想就不難明白,我
們調(diào)用函數(shù)foo1時(shí),程序會(huì)在棧上分配一個(gè)大小為3的數(shù)組嗎不會(huì)!數(shù)組是“傳址”的,調(diào)用者只需將實(shí)參的地址傳遞過(guò)去,所以a3自然為指針類型(char*),c3的值也就為 4。
7.結(jié)構(gòu)體的sizeof 這是初學(xué)者問(wèn)得最多的一個(gè)問(wèn)題,所以這里有必要多費(fèi)點(diǎn)筆墨。讓我們先看一個(gè)結(jié)構(gòu)體 :
struct S1 { char c;int i;};問(wèn)sizeof(s1)等于多少聰明的你開(kāi)始思考了,char占1個(gè)字節(jié),int占4個(gè)字節(jié),那么
加起來(lái)就應(yīng)該是5。是這樣嗎你在你機(jī)器上試過(guò)了嗎也許你是對(duì)的,但很可能你是錯(cuò) 的!VC6中按默認(rèn)設(shè)置得到的結(jié)果為8。Why為什么受傷的總是我 請(qǐng)不要沮喪,我們來(lái)好好琢磨一下sizeof的定義——sizeof的結(jié)果等于對(duì)象或者類型所
占的內(nèi)存字節(jié)數(shù),好吧,那就讓我們來(lái)看看S1的內(nèi)存分配情況: S1 s1 = { 'a', 0xFFFFFFFF };定義上面的變量后,加上斷點(diǎn),運(yùn)行程序,觀察s1所在的內(nèi)存,你發(fā)現(xiàn)了什么 以我的VC6.0為例,s1的地址為0x0012FF78,其數(shù)據(jù)內(nèi)容如下: 0012FF78: 61 CC CC CC FF FF FF FF 發(fā)現(xiàn)了什么怎么中間夾雜了3個(gè)字節(jié)的CC看看MSDN上的說(shuō)明:
When applied to a structure type or variable, sizeof returns the actual siz e, which may include padding bytes inserted for alignment.原來(lái)如此,這就是傳說(shuō)中的字節(jié)對(duì)齊??!一個(gè)重要的話題出現(xiàn)了。
為什么需要字節(jié)對(duì)齊計(jì)算機(jī)組成原理教導(dǎo)我們這樣有助于加快計(jì)算機(jī)的取數(shù)速度,否
則就得多花指令周期了。為此,編譯器默認(rèn)會(huì)對(duì)結(jié)構(gòu)體進(jìn)行處理(實(shí)際上其它地方的數(shù)
據(jù)變量也是如此),讓寬度為2的基本數(shù)據(jù)類型(short等)都位于能被2整除的地址上,讓寬度為4的基本數(shù)據(jù)類型(int等)都位于能被4整除的地址上,以此類推。這樣,兩個(gè)
數(shù)中間就可能需要加入填充字節(jié),所以整個(gè)結(jié)構(gòu)體的sizeof值就增長(zhǎng)了。讓我們交換一下S1中char與int的位置: struct S2 { int i;char c;};看看sizeof(S2)的結(jié)果為多少,怎么還是8再看看內(nèi)存,原來(lái)成員c后面仍然有3個(gè)填
充字節(jié),這又是為什么啊別著急,下面總結(jié)規(guī)律。
字節(jié)對(duì)齊的細(xì)節(jié)和編譯器實(shí)現(xiàn)相關(guān),但一般而言,滿足三個(gè)準(zhǔn)則: 1)結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2)結(jié)構(gòu)體每個(gè)成員相對(duì)于結(jié)構(gòu)體首地址的偏移量(offset)都是成員大小的整數(shù)倍,如有需要編譯器會(huì)在成員之間加上填充字節(jié)(internal adding);
3)結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會(huì)在最
末一個(gè)成員之后加上填充字節(jié)(trailing padding)。對(duì)于上面的準(zhǔn)則,有幾點(diǎn)需要說(shuō)明:
1)前面不是說(shuō)結(jié)構(gòu)體成員的地址是其大小的整數(shù)倍,怎么又說(shuō)到偏移量了呢因?yàn)橛?了第1點(diǎn)存在,所以我們就可以只考慮成員的偏移量,這樣思考起來(lái)簡(jiǎn)單。想想為什么。
結(jié)構(gòu)體某個(gè)成員相對(duì)于結(jié)構(gòu)體首地址的偏移量可以通過(guò)宏offsetof()來(lái)獲得,這個(gè)宏也
在stddef.h中定義,如下:
#define offsetof(s,m)(size_t)&(((s *)0)->m)例如,想要獲得S2中c的偏移量,方法為 size_t pos = offsetof(S2, c);// pos等于4 2)基本類型是指前面提到的像char、short、int、float、double這樣的內(nèi)置數(shù)據(jù)類型,這里所說(shuō)的“數(shù)據(jù)寬度”就是指其sizeof的大小。由于結(jié)構(gòu)體的成員可以是復(fù)合類型,比如另外一個(gè)結(jié)構(gòu)體,所以在尋找最寬基本類型成員時(shí),應(yīng)當(dāng)包括復(fù)合類型成員的子
成員,而不是把復(fù)合成員看成是一個(gè)整體。但在確定復(fù)合類型成員的偏移位置時(shí)則是將
復(fù)合類型作為整體看待。
這里敘述起來(lái)有點(diǎn)拗口,思考起來(lái)也有點(diǎn)撓頭,還是讓我們看看例子吧(具體數(shù)值仍以
VC6為例,以后不再說(shuō)明): struct S3 { char c1;S1 s;char c2 };S1的最寬簡(jiǎn)單成員的類型為int,S3在考慮最寬簡(jiǎn)單類型成員時(shí)是將S1“打散”看的,所以S3的最寬簡(jiǎn)單類型為int,這樣,通過(guò)S3定義的變量,其存儲(chǔ)空間首地址需要被4整
除,整個(gè)sizeof(S3)的值也應(yīng)該被4整除。
c1的偏移量為0,s的偏移量呢這時(shí)s是一個(gè)整體,它作為結(jié)構(gòu)體變量也滿足前面三個(gè)
準(zhǔn)則,所以其大小為8,偏移量為4,c1與s之間便需要3個(gè)填充字節(jié),而c2與s之間就不需
要了,所以c2的偏移量為12,算上c2的大小為13,13是不能被4整除的,這樣末尾還得補(bǔ)
上3個(gè)填充字節(jié)。最后得到sizeof(S3)的值為16。通過(guò)上面的敘述,我們可以得到一個(gè)公式:
結(jié)構(gòu)體的大小等于最后一個(gè)成員的偏移量加上其大小再加上末尾的填充字節(jié)數(shù)目,即:
sizeof(struct)= offsetof(last item)+ sizeof(last item)+ sizeof(tr ailing padding)
到這里,朋友們應(yīng)該對(duì)結(jié)構(gòu)體的sizeof有了一個(gè)全新的認(rèn)識(shí),但不要高興得太早,有
一個(gè)影響sizeof的重要參量還未被提及,那便是編譯器的pack指令。它是用來(lái)調(diào)整結(jié)構(gòu)
體對(duì)齊方式的,不同編譯器名稱和用法略有不同,VC6中通過(guò)#pragma pack實(shí)現(xiàn),也可以
直接修改/Zp編譯開(kāi)關(guān)。#pragma pack的基本用法為:#pragma pack(n),n為字節(jié)對(duì)齊
數(shù),其取值為1、2、4、8、16,默認(rèn)是8,如果這個(gè)值比結(jié)構(gòu)體成員的sizeof值小,那么
該成員的偏移量應(yīng)該以此值為準(zhǔn),即是說(shuō),結(jié)構(gòu)體成員的偏移量應(yīng)該取二者的最小值,公式如下:
offsetof(item)= min(n, sizeof(item))再看示例:
#pragma pack(push)// 將當(dāng)前pack設(shè)置壓棧保存 #pragma pack(2)// 必須在結(jié)構(gòu)體定義之前使用 struct S1 { char c;int i;};struct S3 { char c1;S1 s;char c2 };#pragma pack(pop)// 恢復(fù)先前的pack設(shè)置 計(jì)算sizeof(S1)時(shí),min(2, sizeof(i))的值為2,所以i的偏移量為2,加上sizeof(i)等于6,能夠被2整除,所以整個(gè)S1的大小為6。
同樣,對(duì)于sizeof(S3),s的偏移量為2,c2的偏移量為8,加上sizeof(c2)等于9,不能
被2整除,添加一個(gè)填充字節(jié),所以sizeof(S3)等于10?,F(xiàn)在,朋友們可以輕松的出一口氣了,:)還有一點(diǎn)要注意,“空結(jié)構(gòu)體”(不含數(shù)據(jù)成員)的大小不為0,而是1。試想一個(gè)“不
占空間”的變量如何被取地址、兩個(gè)不同的“空結(jié)構(gòu)體”變量又如何得以區(qū)分呢于是,“空結(jié)構(gòu)體”變量也得被存儲(chǔ),這樣編譯器也就只能為其分配一個(gè)字節(jié)的空間用于占
位了。如下: struct S5 { };sizeof(S5);// 結(jié)果為1
8.含位域結(jié)構(gòu)體的sizeof 前面已經(jīng)說(shuō)過(guò),位域成員不能單獨(dú)被取sizeof值,我們這里要討論的是含有位域的結(jié)構(gòu)
體的sizeof,只是考慮到其特殊性而將其專門列了出來(lái)。
C99規(guī)定int、unsigned int和bool可以作為位域類型,但編譯器幾乎都對(duì)此作了擴(kuò)展,允許其它類型類型的存在。
使用位域的主要目的是壓縮存儲(chǔ),其大致規(guī)則為:
1)如果相鄰位域字段的類型相同,且其位寬之和小于類型的sizeof大小,則后面的字
段將緊鄰前一個(gè)字段存儲(chǔ),直到不能容納為止;
2)如果相鄰位域字段的類型相同,但其位寬之和大于類型的sizeof大小,則后面的字
段將從新的存儲(chǔ)單元開(kāi)始,其偏移量為其類型大小的整數(shù)倍;
3)如果相鄰的位域字段的類型不同,則各編譯器的具體實(shí)現(xiàn)有差異,VC6采取不壓縮方
式,Dev-C++采取壓縮方式;
4)如果位域字段之間穿插著非位域字段,則不進(jìn)行壓縮; 5)整個(gè)結(jié)構(gòu)體的總大小為最寬基本類型成員大小的整數(shù)倍。
還是讓我們來(lái)看看例子。示例1: struct BF1 { char f1 : 3;char f2 : 4;char f3 : 5;};其內(nèi)存布局為:
|_f1__|__f2__|_|____f3___|____| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| 0 3 7 8 1316 位域類型為char,第1個(gè)字節(jié)僅能容納下f1和f2,所以f2被壓縮到第1個(gè)字節(jié)中,而f3只
能從下一個(gè)字節(jié)開(kāi)始。因此sizeof(BF1)的結(jié)果為2。示例2: struct BF2 { char f1 : 3;short f2 : 4;char f3 : 5;};由于相鄰位域類型不同,在VC6中其sizeof為6,在Dev-C++中為2。示例3: struct BF3 { char f1 : 3;char f2;char f3 : 5;};非位域字段穿插在其中,不會(huì)產(chǎn)生壓縮,在VC6和Dev-C++中得到的大小均為3。
9.聯(lián)合體的sizeof 結(jié)構(gòu)體在內(nèi)存組織上是順序式的,聯(lián)合體則是重疊式,各成員共享一段內(nèi)存,所以整個(gè)
聯(lián)合體的sizeof也就是每個(gè)成員sizeof的最大值。結(jié)構(gòu)體的成員也可以是復(fù)合類型,這
里,復(fù)合類型成員是被作為整體考慮的。
所以,下面例子中,U的sizeof值等于sizeof(s)。union U { int i;char c;S1 s;}
第二篇:嵌入式程序員C語(yǔ)言筆試題目
華碩_嵌入式程序員C語(yǔ)言筆試題目
預(yù)處理器(Preprocessor).用預(yù)處理指令#define 聲明一個(gè)常數(shù),用以表明1年中有多少秒(忽略閏年問(wèn)題)
#define SECONDS_PER_YEAR(60 * 60 * 24 * 365)UL
我在這想看到幾件事情:
1)#define 語(yǔ)法的基本知識(shí)(例如:不能以分號(hào)結(jié)束,括號(hào)的使用,等等)
2)懂得預(yù)處理器將為你計(jì)算常數(shù)表達(dá)式的值,因此,直接寫出你是如何計(jì)算一年中有多少秒而不是計(jì)算出實(shí)際的值,是更清晰而沒(méi)有代價(jià)的。
3)意識(shí)到這個(gè)表達(dá)式將使一個(gè)16位機(jī)的整型數(shù)溢出-因此要用到長(zhǎng)整型符號(hào)L,告訴編譯器這個(gè)常數(shù)是的長(zhǎng)整型數(shù)。
4)如果你在你的表達(dá)式中用到UL(表示無(wú)符號(hào)長(zhǎng)整型),那么你有了一個(gè)好的起點(diǎn)。記住,第一印象很重要。.寫一個(gè)“標(biāo)準(zhǔn)”宏MIN,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè)。
#define MIN(A,B)((A)<=(B)?(A):(B))
這個(gè)測(cè)試是為下面的目的而設(shè)的:
1)標(biāo)識(shí)#define在宏中應(yīng)用的基本知識(shí)。這是很重要的。因?yàn)樵?嵌入(inline)操作符 變?yōu)闃?biāo)準(zhǔn)C的一部分之前,宏是方便產(chǎn)生嵌入代碼的唯一方法,對(duì)于嵌入式系統(tǒng)來(lái)說(shuō),為了能達(dá)到要求的性能,嵌入代碼經(jīng)常是必須的方法。
2)三重條件操作符的知識(shí)。這個(gè)操作符存在C語(yǔ)言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個(gè)用法是很重要的。
3)懂得在宏中小心地把參數(shù)用括號(hào)括起來(lái)
4)我也用這個(gè)問(wèn)題開(kāi)始討論宏的副作用,例如:當(dāng)你寫下面的代碼時(shí)會(huì)發(fā)生什么事?
least = MIN(*p++, b);
3.預(yù)處理器標(biāo)識(shí)#error的目的是什么?
Error directives produce compiler-time error messages.死循環(huán)(Infinite loops)
4.嵌入式系統(tǒng)中經(jīng)常要用到無(wú)限循環(huán),你怎么樣用C編寫死循環(huán)呢?
這個(gè)問(wèn)題用幾個(gè)解決方案。我首選的方案是:
while(1){ }
一些程序員更喜歡如下方案:
for(;;){ }
這個(gè)實(shí)現(xiàn)方式讓我為難,因?yàn)檫@個(gè)語(yǔ)法沒(méi)有確切表達(dá)到底怎么回事。如果一個(gè)應(yīng)試者給出這個(gè)作為方案,我將用這個(gè)作為一個(gè)機(jī)會(huì)去探究他們這樣做的基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒(méi)有想到過(guò)為什么。”這會(huì)給我留下一個(gè)壞印象。
第三個(gè)方案是用 goto Loop:...goto Loop;
應(yīng)試者如給出上面的方案,這說(shuō)明或者他是一個(gè)匯編語(yǔ)言程序員(這也許是好事)或者他是一個(gè)想進(jìn)入新領(lǐng)域的BASIC/FORTRAN程序員。
數(shù)據(jù)聲明(Data declarations)
5.用變量a給出下面的定義
a)一個(gè)整型數(shù)(An integer)
b)一個(gè)指向整型數(shù)的指針(A pointer to an integer)c)一個(gè)指向指針的的指針,它指向的指針是指向一個(gè)整型數(shù)(A pointer to a pointer to an intege)r
d)一個(gè)有10個(gè)整型數(shù)的數(shù)組(An array of 10 integers)e)一個(gè)有10個(gè)指針的數(shù)組,該指針是指向一個(gè)整型數(shù)的。(An array of 10 pointers to integers)
f)一個(gè)指向有10個(gè)整型數(shù)數(shù)組的指針(A pointer to an array of 10 integers)
g)一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)
h)一個(gè)有10個(gè)指針的數(shù)組,該指針指向一個(gè)函數(shù),該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)(An array of ten pointers to functions that take an integer argument and return an integer)
答案是:
a)int a;// An integer
b)int *a;// A pointer to an integer
c)int **a;// A pointer to a pointer to an integer
d)int a[10];// An array of 10 integers
e)int *a[10];// An array of 10 pointers to integers
f)int(*a)[10];// A pointer to an array of 10 integers
g)int(*a)(int);// A pointer to a function a that takes an integer argument and returns an integer
h)int(*a[10])(int);// An array of 10 pointers to functions that take an integer argument and return an integer
人們經(jīng)常聲稱這里有幾個(gè)問(wèn)題是那種要翻一下書才能回答的問(wèn)題,我同意這種說(shuō)法。當(dāng)我寫這篇文章時(shí),為了確定語(yǔ)法的正確性,我的確查了一下書。但是當(dāng)我被面試的時(shí)候,我期望被問(wèn)到這個(gè)問(wèn)題(或者相近的問(wèn)題)。因?yàn)樵诒幻嬖嚨倪@段時(shí)間里,我確定我知道這個(gè)問(wèn)題的答案。應(yīng)試者如果不知道所有的答案(或至少大部分答案),那么也就沒(méi)有為這次面試做準(zhǔn)備,如果該面試者沒(méi)有為這次面試做準(zhǔn)備,那么他又能為什么出準(zhǔn)備呢? Static
6.關(guān)鍵字static的作用是什么?
這個(gè)簡(jiǎn)單的問(wèn)題很少有人能回答完全。在C語(yǔ)言中,關(guān)鍵字static有三個(gè)明顯的作用:
1)在函數(shù)體,一個(gè)被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過(guò)程中維持其值不變。
2)在模塊內(nèi)(但在函數(shù)體外),一個(gè)被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問(wèn),但不能被模塊外其它函數(shù)訪問(wèn)。它是一個(gè)本地的全局變量。
3)在模塊內(nèi),一個(gè)被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個(gè)函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
大多數(shù)應(yīng)試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個(gè)應(yīng)試者的嚴(yán)重的缺點(diǎn),因?yàn)樗@然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。
Const
7.關(guān)鍵字const有什么含意?
我只要一聽(tīng)到被面試者說(shuō):“const意味著常數(shù)”,我就知道我正在和一個(gè)業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒(méi)有讀到那篇文章,只要能說(shuō)出const意味著“只讀”就可以了。盡管這個(gè)答案不是完全的答案,但我接受它作為一個(gè)正確的答案。(如果你想知道更詳細(xì)的答案,仔細(xì)讀一下Saks的文章吧。)
如果應(yīng)試者能正確回答這個(gè)問(wèn)題,我將問(wèn)他一個(gè)附加的問(wèn)題:
下面的聲明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * const a=new int(1);
/******/
前兩個(gè)的作用是一樣,a是一個(gè)常整型數(shù)。第三個(gè)意味著a是一個(gè)指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個(gè)意識(shí)a是一個(gè)指向整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個(gè)意味著a是一個(gè)指向常整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是不可修改的,同時(shí)指針也是不可修改的)。如果應(yīng)試者能正確回答這些問(wèn)題,那么他就給我留下了一個(gè)好印象。順帶提一句,也許你可能會(huì)問(wèn),即使不用關(guān)鍵字 const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關(guān)鍵字const呢?我也如下的幾下理由:
1)關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息,實(shí)際上,聲明一個(gè)參數(shù)為常量是為了告訴了用戶這個(gè)參數(shù)的應(yīng)用目的。如果你曾花很多時(shí)間清理其它人留下的垃圾,你就會(huì)很快學(xué)會(huì)感謝這點(diǎn)多余的信息。(當(dāng)然,懂得用const的程序員很少會(huì)留下的垃圾讓別人來(lái)清理的。)
第三篇:嵌入式程序員C語(yǔ)言筆試經(jīng)典題
新一篇: 存儲(chǔ)過(guò)程,無(wú)限級(jí)分類 | 舊一篇: 類繼承中構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用
這個(gè)測(cè)試適于不同水平的應(yīng)試者,大多數(shù)初級(jí)水平的應(yīng)試者的成績(jī)會(huì)很差,經(jīng)驗(yàn)豐富的程序員應(yīng)該有很好的成績(jī)。為了讓你能自己決定某些問(wèn)題的偏好,每個(gè)問(wèn)題沒(méi)有分配分?jǐn)?shù),如果選擇這些考題為你所用,請(qǐng)自行按你的意思分配分?jǐn)?shù)。
預(yù)處理器(Preprocessor).用預(yù)處理指令#define 聲明一個(gè)常數(shù),用以表明1年中有多少秒(忽略閏年問(wèn)題)#define SECONDS_PER_YEAR(60 * 60 * 24 * 365)UL 我在這想看到幾件事情:
1)#define 語(yǔ)法的基本知識(shí)(例如:不能以分號(hào)結(jié)束,括號(hào)的使用,等等)
2)懂得預(yù)處理器將為你計(jì)算常數(shù)表達(dá)式的值,因此,直接寫出你是如何計(jì)算一年中有多少秒而不是計(jì)算出實(shí)際的值,是更清晰而沒(méi)有代價(jià)的。
3)意識(shí)到這個(gè)表達(dá)式將使一個(gè)16位機(jī)的整型數(shù)溢出-因此要用到長(zhǎng)整型符號(hào)L,告訴編譯器這個(gè)常數(shù)是的長(zhǎng)整型數(shù)。
4)如果你在你的表達(dá)式中用到UL(表示無(wú)符號(hào)長(zhǎng)整型),那么你有了一個(gè)好的起點(diǎn)。記住,第一印象很重要。.寫一個(gè)“標(biāo)準(zhǔn)”宏MIN,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè)。#define MIN(A,B)((A)<=(B)?(A):(B))這個(gè)測(cè)試是為下面的目的而設(shè)的:
1)標(biāo)識(shí)#define在宏中應(yīng)用的基本知識(shí)。這是很重要的。因?yàn)樵谇度?inline)操作符 變?yōu)闃?biāo)準(zhǔn)C的一部分之前,宏是方便產(chǎn)生嵌入代碼的唯一方法,對(duì)于嵌入式系統(tǒng)來(lái)說(shuō),為了能達(dá)到要求的性能,嵌入代碼經(jīng)常是必須的方法。
2)三重條件操作符的知識(shí)。這個(gè)操作符存在C語(yǔ)言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個(gè)用法是很重要的。3)懂得在宏中小心地把參數(shù)用括號(hào)括起來(lái)
4)我也用這個(gè)問(wèn)題開(kāi)始討論宏的副作用,例如:當(dāng)你寫下面的代碼時(shí)會(huì)發(fā)生什么事? least = MIN(*p++, b);
3.預(yù)處理器標(biāo)識(shí)#error的目的是什么?
如果你不知道答案,請(qǐng)看參考文獻(xiàn)1。這問(wèn)題對(duì)區(qū)分一個(gè)正常的伙計(jì)和一個(gè)書呆子是很有用的。只有書呆子才會(huì)讀C語(yǔ)言課本的附錄去找出象這種問(wèn)題的答案。當(dāng)然如果你不是在找一個(gè)書呆子,那么應(yīng)試者最好希望自己不要知道答案。指令 用途
# 空指令,無(wú)任何效果
#include 包含一個(gè)源代碼文件
#define 定義宏
#undef 取消已定義的宏
#if 如果給定條件為真,則編譯下面代碼
#ifdef 如果宏已經(jīng)定義,則編譯下面代碼
#ifndef 如果宏沒(méi)有定義,則編譯下面代碼
#elif 如果前面的#if給定條件不為真,當(dāng)前條件為真,則編譯下面代碼 #endif 結(jié)束一個(gè)#if……#else條件編譯塊
#error 停止編譯并顯示錯(cuò)誤信息
死循環(huán)(Infinite loops)
4.嵌入式系統(tǒng)中經(jīng)常要用到無(wú)限循環(huán),你怎么樣用C編寫死循環(huán)呢? 這個(gè)問(wèn)題用幾個(gè)解決方案。我首選的方案是:
while(1){ }
一些程序員更喜歡如下方案:
for(;;){ }
這個(gè)實(shí)現(xiàn)方式讓我為難,因?yàn)檫@個(gè)語(yǔ)法沒(méi)有確切表達(dá)到底怎么回事。如果一個(gè)應(yīng)試者給出這個(gè)作為方案,我將用這個(gè)作為一個(gè)機(jī)會(huì)去探究他們這樣做的基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒(méi)有想到過(guò)為什么?!边@會(huì)給我留下一個(gè)壞印象。
第三個(gè)方案是用 goto Loop:...goto Loop;應(yīng)試者如給出上面的方案,這說(shuō)明或者他是一個(gè)匯編語(yǔ)言程序員(這也許是好事)或者他是一個(gè)想進(jìn)入新領(lǐng)域的BASIC/FORTRAN程序員。
數(shù)據(jù)聲明(Data declarations)
5.用變量a給出下面的定義 a)一個(gè)整型數(shù)(An integer)
//int a me b)一個(gè)指向整型數(shù)的指針(A pointer to an integer)
//int *a me c)一個(gè)指向指針的的指針,它指向的指針是指向一個(gè)整型數(shù)(A pointer to a pointer to an intege)r
//int ** a me
d)一個(gè)有10個(gè)整型數(shù)的數(shù)組(An array of 10 integers)//int a[10] me e)一個(gè)有10個(gè)指針的數(shù)組,該指針是指向一個(gè)整型數(shù)的。(An array of 10 pointers to integers)
// int * a[10] me f)一個(gè)指向有10個(gè)整型數(shù)數(shù)組的指針(A pointer to an array of 10 integers)
//in t(*a)[10] me g)一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)
int(*fun)(int)
h)一個(gè)有10個(gè)指針的數(shù)組,該指針指向一個(gè)函數(shù),該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)(An array of ten pointers to functions that take an integer argument and return an integer)
//int(*fun[10])(int)答案是:
a)int a;// An integer b)int *a;// A pointer to an integer c)int **a;// A pointer to a pointer to an integer d)int a[10];// An array of 10 integers e)int *a[10];// An array of 10 pointers to integers f)int(*a)[10];// A pointer to an array of 10 integers g)int(*a)(int);// A pointer to a function a that takes an integer argument and returns an integer h)int(*a[10])(int);// An array of 10 pointers to functions that take an integer argument and return an integer
人們經(jīng)常聲稱這里有幾個(gè)問(wèn)題是那種要翻一下書才能回答的問(wèn)題,我同意這種說(shuō)法。當(dāng)我寫這篇文章時(shí),為了確定語(yǔ)法的正確性,我的確查了一下書。但是當(dāng)我被面試的時(shí)候,我期望被問(wèn)到這個(gè)問(wèn)題(或者相近的問(wèn)題)。因?yàn)樵诒幻嬖嚨倪@段時(shí)間里,我確定我知道這個(gè)問(wèn)題的答案。應(yīng)試者如果不知道所有的答案(或至少大部分答案),那么也就沒(méi)有為這次面試做準(zhǔn)備,如果該面試者沒(méi)有為這次面試做準(zhǔn)備,那么他又能為什么出準(zhǔn)備呢?
Static
6.關(guān)鍵字static的作用是什么?
這個(gè)簡(jiǎn)單的問(wèn)題很少有人能回答完全。在C語(yǔ)言中,關(guān)鍵字static有三個(gè)明顯的作用: 1)在函數(shù)體,一個(gè)被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過(guò)程中維持其值不變。
2)在模塊內(nèi)(但在函數(shù)體外),一個(gè)被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問(wèn),但不能被模塊外其它函數(shù)訪問(wèn)。它是一個(gè)本地的全局變量。
3)在模塊內(nèi),一個(gè)被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個(gè)函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
大多數(shù)應(yīng)試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個(gè)應(yīng)試者的嚴(yán)重的缺點(diǎn),因?yàn)樗@然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。
Const
7.關(guān)鍵字const有什么含意?
我只要一聽(tīng)到被面試者說(shuō):“const意味著常數(shù)”,我就知道我正在和一個(gè)業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒(méi)有讀到那篇文章,只要能說(shuō)出const意味著“只讀”就可以了。盡管這個(gè)答案不是完全的答案,但我接受它作為一個(gè)正確的答案。(如果你想知道更詳細(xì)的答案,仔細(xì)讀一下Saks的文章吧。)
如果應(yīng)試者能正確回答這個(gè)問(wèn)題,我將問(wèn)他一個(gè)附加的問(wèn)題: 下面的聲明都是什么意思?
const int a;int const a;const int *a;int * const a;int const * a const;
/******/ 前兩個(gè)的作用是一樣,a是一個(gè)常整型數(shù)。第三個(gè)意味著a是一個(gè)指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個(gè)意識(shí)a是一個(gè)指向整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個(gè)意味著a是一個(gè)指向常整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是不可修改的,同時(shí)指針也是不可修改的)。如果應(yīng)試者能正確回答這些問(wèn)題,那么他就給我留下了一個(gè)好印象。順帶提一句,也許你可能會(huì)問(wèn),即使不用關(guān)鍵字 const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關(guān)鍵字const呢?我也如下的幾下理由:
1)關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息,實(shí)際上,聲明一個(gè)參數(shù)為常量是為了告訴了用戶這個(gè)參數(shù)的應(yīng)用目的。如果你曾花很多時(shí)間清理其它人留下的垃圾,你就會(huì)很快學(xué)會(huì)感謝這點(diǎn)多余的信息。(當(dāng)然,懂得用const的程序員很少會(huì)留下的垃圾讓別人來(lái)清理的。)2)通過(guò)給優(yōu)化器一些附加的信息,使用關(guān)鍵字const也許能產(chǎn)生更緊湊的代碼。
3)合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無(wú)意的代碼修改。簡(jiǎn)而言之,這樣可以減少bug的出現(xiàn)。
Volatile
8.關(guān)鍵字volatile有什么含意?并給出三個(gè)不同的例子。
一個(gè)定義為volatile的變量是說(shuō)這變量可能會(huì)被意想不到地改變,這樣,編譯器就不會(huì)去假設(shè)這個(gè)變量的值了。精確地說(shuō)就是,優(yōu)化器在用到這個(gè)變量時(shí)必須每次都小心地重新讀取這個(gè)變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個(gè)例子: 1)并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
2)一個(gè)中斷服務(wù)子程序中會(huì)訪問(wèn)到的非自動(dòng)變量(Non-automatic variables)3)多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量
回答不出這個(gè)問(wèn)題的人是不會(huì)被雇傭的。我認(rèn)為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問(wèn)題。搞嵌入式的家伙們經(jīng)常同硬件、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的內(nèi)容將會(huì)帶來(lái)災(zāi)難。
假設(shè)被面試者正確地回答了這是問(wèn)題(嗯,懷疑是否會(huì)是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
1)一個(gè)參數(shù)既可以是const還可以是volatile嗎?解釋為什么。2);一個(gè)指針可以是volatile 嗎?解釋為什么。3);下面的函數(shù)有什么錯(cuò)誤:
int square(volatile int *ptr){ return *ptr * *ptr;}
下面是答案:
1)是的。一個(gè)例子是只讀的狀態(tài)寄存器。它是volatile因?yàn)樗赡鼙灰庀氩坏降馗淖?。它是const因?yàn)槌绦虿粦?yīng)該試圖去修改它。
2);是的。盡管這并不很常見(jiàn)。一個(gè)例子是當(dāng)一個(gè)中服務(wù)子程序修該一個(gè)指向一個(gè)buffer的指針時(shí)。3)這段代碼有點(diǎn)變態(tài)。這段代碼的目的是用來(lái)返指針*ptr指向值的平方,但是,由于*ptr指向一個(gè)volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼:
int square(volatile int *ptr){ int a,b;a = *ptr;b = *ptr;return a * b;}
由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結(jié)果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr){ int a;a = *ptr;return a * a;}
位操作(Bit manipulation)
9.嵌入式系統(tǒng)總是要用戶對(duì)變量或寄存器進(jìn)行位操作。給定一個(gè)整型變量a,寫兩段代碼,第一個(gè)設(shè)置a的bit 3,第二個(gè)清除a 的bit 3。在以上兩個(gè)操作中,要保持其它位不變。對(duì)這個(gè)問(wèn)題有三種基本的反應(yīng)
1)不知道如何下手。該被面者從沒(méi)做過(guò)任何嵌入式系統(tǒng)的工作。
2)用bit fields。Bit fields是被扔到C語(yǔ)言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時(shí)也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon為其較復(fù)雜的通信芯片寫的驅(qū)動(dòng)程序,它用到了bit fields因此完全對(duì)我無(wú)用,因?yàn)槲业木幾g器用其它的方式來(lái)實(shí)現(xiàn)bit fields的。從道德講:永遠(yuǎn)不要讓一個(gè)非嵌入式的家伙粘實(shí)際硬件的邊。
3)用 #defines 和 bit masks 操作。這是一個(gè)有極高可移植性的方法,是應(yīng)該被用到的方法。最佳的解決方案如下:
#define BIT3(0x1 << 3)static int a;
void set_bit3(void){ a |= BIT3;} void clear_bit3(void){ a &= ~BIT3;}
一些人喜歡為設(shè)置和清除值而定義一個(gè)掩碼同時(shí)定義一些說(shuō)明常數(shù),這也是可以接受的。我希望看到幾個(gè)要點(diǎn):說(shuō)明常數(shù)、|=和&=~操作。
訪問(wèn)固定的內(nèi)存位置(Accessing fixed memory locations)
10.嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問(wèn)某特定的內(nèi)存位置的特點(diǎn)。在某工程中,要求設(shè)置一絕對(duì)地址為0x67a9的整型變量的值為0xaa66。編譯器是一個(gè)純粹的ANSI編譯器。寫代碼去完成這一任務(wù)。這一問(wèn)題測(cè)試你是否知道為了訪問(wèn)一絕對(duì)地址把一個(gè)整型數(shù)強(qiáng)制轉(zhuǎn)換(typecast)為一指針是合法的。這一問(wèn)題的實(shí)現(xiàn)方式隨著個(gè)人風(fēng)格不同而不同。典型的類似代碼如下: int *ptr;ptr =(int *)0x67a9;*ptr = 0xaa55;
A more obscure approach is: 一個(gè)較晦澀的方法是:
*(int * const)(0x67a9)= 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時(shí)使用第一種方案。
中斷(Interrupts)
11.中斷是嵌入式系統(tǒng)中重要的組成部分,這導(dǎo)致了很多編譯開(kāi)發(fā)商提供一種擴(kuò)展—讓標(biāo)準(zhǔn)C支持中斷。具代表事實(shí)是,產(chǎn)生了一個(gè)新的關(guān)鍵字 __interrupt。下面的代碼就使用了__interrupt關(guān)鍵字去定義了一個(gè)中斷服務(wù)子程序(ISR),請(qǐng)?jiān)u論一下這段代碼的。
__interrupt double compute_area(double radius){ double area = PI * radius * radius;printf(“nArea = %f”, area);return area;}
這個(gè)函數(shù)有太多的錯(cuò)誤了,以至讓人不知從何說(shuō)起了:
1)ISR 不能返回一個(gè)值。如果你不懂這個(gè),那么你不會(huì)被雇用的。
2)ISR 不能傳遞參數(shù)。如果你沒(méi)有看到這一點(diǎn),你被雇用的機(jī)會(huì)等同第一項(xiàng)。
3)在許多的處理器/編譯器中,浮點(diǎn)一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點(diǎn)運(yùn)算。此外,ISR應(yīng)該是短而有效率的,在ISR中做浮點(diǎn)運(yùn)算是不明智的。
4)與第三點(diǎn)一脈相承,printf()經(jīng)常有重入和性能上的問(wèn)題。如果你丟掉了第三和第四點(diǎn),我不會(huì)太為難你的。不用說(shuō),如果你能得到后兩點(diǎn),那么你的被雇用前景越來(lái)越光明了。
代碼例子(Code examples).下面的代碼輸出是什么,為什么?
void foo(void){ unsigned int a = 6;int b =-20;(a+b > 6)? puts(“> 6”): puts(“<= 6”);} 這個(gè)問(wèn)題測(cè)試你是否懂得C語(yǔ)言中的整數(shù)自動(dòng)轉(zhuǎn)換原則,我發(fā)現(xiàn)有些開(kāi)發(fā)者懂得極少這些東西。不管如何,這無(wú)符號(hào)整型問(wèn)題的答案是輸出是 “>6”。原因是當(dāng)表達(dá)式中存在有符號(hào)類型和無(wú)符號(hào)類型時(shí)所有的操作數(shù)都自動(dòng)轉(zhuǎn)換為無(wú)符號(hào)類型。因此-20變成了一個(gè)非常大的正整數(shù),所以該表達(dá)式計(jì)算出的結(jié)果大于6。這一點(diǎn)對(duì)于應(yīng)當(dāng)頻繁用到無(wú)符號(hào)數(shù)據(jù)類型的嵌入式系統(tǒng)來(lái)說(shuō)是豐常重要的。如果你答錯(cuò)了這個(gè)問(wèn)題,你也就到了得不到這份工作的邊緣。
13.評(píng)價(jià)下面的代碼片斷:
unsigned int zero = 0;unsigned int compzero = 0xFFFF;/*1's complement of zero */
對(duì)于一個(gè)int型不是16位的處理器為說(shuō),上面的代碼是不正確的。應(yīng)編寫如下:
unsigned int compzero = ~0;這一問(wèn)題真正能揭露出應(yīng)試者是否懂得處理器字長(zhǎng)的重要性。在我的經(jīng)驗(yàn)里,好的嵌入式程序員非常準(zhǔn)確地明白硬件的細(xì)節(jié)和它的局限,然而PC機(jī)程序往往把硬件作為一個(gè)無(wú)法避免的煩惱。
到了這個(gè)階段,應(yīng)試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應(yīng)試者不是很好,那么這個(gè)測(cè)試就在這里結(jié)束了。但如果顯然應(yīng)試者做得不錯(cuò),那么我就扔出下面的追加問(wèn)題,這些問(wèn)題是比較難的,我想僅僅非常優(yōu)秀的應(yīng)試者能做得不錯(cuò)。提出這些問(wèn)題,我希望更多看到應(yīng)試者應(yīng)付問(wèn)題的方法,而不是答案。不管如何,你就當(dāng)是這個(gè)娛樂(lè)吧...動(dòng)態(tài)內(nèi)存分配(Dynamic memory allocation)
14.盡管不像非嵌入式計(jì)算機(jī)那么常見(jiàn),嵌入式系統(tǒng)還是有從堆(heap)中動(dòng)態(tài)分配內(nèi)存的過(guò)程的。那么嵌入式系統(tǒng)中,動(dòng)態(tài)分配內(nèi)存可能發(fā)生的問(wèn)題是什么?
這里,我期望應(yīng)試者能提到內(nèi)存碎片,碎片收集的問(wèn)題,變量的持行時(shí)間等等。這個(gè)主題已經(jīng)在ESP雜志中被廣泛地討論過(guò)了(主要是 P.J.Plauger, 他的解釋遠(yuǎn)遠(yuǎn)超過(guò)我這里能提到的任何解釋),所有回過(guò)頭看一下這些雜志吧!讓應(yīng)試者進(jìn)入一種虛假的安全感覺(jué)后,我拿出這么一個(gè)小節(jié)目: 下面的代碼片段的輸出是什么,為什么?
char *ptr;if((ptr =(char *)malloc(0))== NULL)puts(“Got a null pointer”);else puts(“Got a valid pointer”);
這是一個(gè)有趣的問(wèn)題。最近在我的一個(gè)同事不經(jīng)意把0值傳給了函數(shù)malloc,得到了一個(gè)合法的指針之后,我才想到這個(gè)問(wèn)題。這就是上面的代碼,該代碼的輸出是“Got a valid pointer”。我用這個(gè)來(lái)開(kāi)始討論這樣的一問(wèn)題,看看被面試者是否想到庫(kù)例程這樣做是正確。得到正確的答案固然重要,但解決問(wèn)題的方法和你做決定的基本原理更重要些。
Typedef Typedef 在C語(yǔ)言中頻繁用以聲明一個(gè)已經(jīng)存在的數(shù)據(jù)類型的同義字。也可以用預(yù)處理器做類似的事。例如,思考一下下面的例子:
#define dPS struct s * typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個(gè)指向結(jié)構(gòu)s指針。哪種方法更好呢?(如果有的話)為什么?
這是一個(gè)非常微妙的問(wèn)題,任何人答對(duì)這個(gè)問(wèn)題(正當(dāng)?shù)脑颍┦菓?yīng)當(dāng)被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;tPS p3,p4;第一個(gè)擴(kuò)展為
struct s * p1, p2;.上面的代碼定義p1為一個(gè)指向結(jié)構(gòu)的指,p2為一個(gè)實(shí)際的結(jié)構(gòu),這也許不是你想要的。第二個(gè)例子正確地定義了p3 和p4 兩個(gè)指針。
晦澀的語(yǔ)法.C語(yǔ)言同意一些令人震驚的結(jié)構(gòu),下面的結(jié)構(gòu)是合法的嗎,如果是它做些什么?
int a = 5, b = 7, c;c = a+++b;
這個(gè)問(wèn)題將做為這個(gè)測(cè)驗(yàn)的一個(gè)愉快的結(jié)尾。不管你相不相信,上面的例子是完全合乎語(yǔ)法的。問(wèn)題是編譯器如何處理它?水平不高的編譯作者實(shí)際上會(huì)爭(zhēng)論這個(gè)問(wèn)題,根據(jù)最處理原則,編譯器應(yīng)當(dāng)能處理盡可能所有合法的用法。因此,上面的代碼被處理成:
c = a++ + b;
因此, 這段代碼持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個(gè)當(dāng)作問(wèn)題。我發(fā)現(xiàn)這個(gè)問(wèn)題的最大好處是這是一個(gè)關(guān)于代碼編寫風(fēng)格,代碼的可讀性,代碼的可修改性的好的話題。
第四篇:程序員教你學(xué)C語(yǔ)言
程序員教你學(xué)C語(yǔ)言
很多小伙伴都老是會(huì)碰到疑問(wèn),其實(shí)還是基礎(chǔ)沒(méi)打扎實(shí),這些題如果你不看答案你能知道多少呢?如果還有很多不知道就證明基礎(chǔ)沒(méi)打扎實(shí),如果你還在入門糾結(jié),如果你還在苦惱怎么入門!小編有個(gè)建議,可以加小編弄的一個(gè)C語(yǔ)言交流基地,大家可以進(jìn)入交流基地:565122788,里面新手入門資料,可以說(shuō)從零到項(xiàng)目實(shí)戰(zhàn),都是可以免費(fèi)獲取的,還有程序員大牛為各位免費(fèi)解答問(wèn)題,熱心腸的小伙伴也是蠻多的。不失為是一個(gè)交流的的好地方,小編在這里邀請(qǐng)大家加入我的大家庭。歡迎你的到來(lái)。一起交流學(xué)習(xí)!共同進(jìn)步!小編等你!還有前面沒(méi)有看的同學(xué)最好從程序員教你學(xué)C語(yǔ)言
(一)開(kāi)始看哦,尤其是基礎(chǔ)還沒(méi)打扎實(shí)的同學(xué)!
今天只舉幾個(gè)例子,主要幫大家鞏固循環(huán)的知識(shí),每個(gè)例子大家都要敲鍵盤敲出來(lái),然后運(yùn)行成功了才算掌握了,不然還是眼高手低,看上去懂了,一到寫程序又犯難了。我發(fā)現(xiàn)有不少人熱衷于打印圖形,所以就弄了幾個(gè)圖形。第一個(gè)是打印金字塔。代碼和運(yùn)行圖如下:
首先定義了兩個(gè)變量i、j,然后使用system(“color 0e”)改變顏色。接下來(lái)會(huì)進(jìn)入一個(gè)外層循環(huán),其中的i代表層數(shù),我們可以看到這里金字塔有6層,所以i的取值范圍也是0<=i<6,第一次進(jìn)循環(huán)時(shí),i=0,然后到第一個(gè)內(nèi)層循環(huán),這個(gè)循環(huán)的初始條件是j=6-i=6,結(jié)束條件是j>0,所以這里會(huì)打印6個(gè)空格,然后來(lái)到第二個(gè)內(nèi)層循環(huán),這個(gè)循環(huán)的初始條件是j=1;結(jié)束條件是j<=2*i+1=1,所以這里會(huì)打印一個(gè)星,然后會(huì)以printf(“ ”)打印一個(gè)換行結(jié)束第一次外層循環(huán),然后開(kāi)始第二次外層循環(huán),如此反復(fù),最后就得到了如上所示的圖形。
不理解的話可以把外層循環(huán)for(i = 0;i < 6;i++)的i<6改成i<3,這樣就只會(huì)打印三行,可以便于理解。
第二個(gè)是打印菱形,其實(shí)就是上一個(gè)圖形的變化,效果和代碼如下:
可以看到,我們這個(gè)圖形是上下對(duì)稱的,所以打印菱形上半部分(就是上一個(gè)例子的打印金字塔)的代碼和下半部分的代碼十分相似,只是把外層循環(huán)的頭部從for(i = 0;i < 6;i++)變成了for(i = 6;i >= 0;i--),大家理解一下代碼,菱形的上半部分,打印的星星數(shù)會(huì)越來(lái)越多,從1到3再到5再到7...而星星前面的空格數(shù)會(huì)越來(lái)越少,從6到5再到4再到3...而菱形的下半部分剛好反過(guò)來(lái)了,所以只需要修改很少的代碼就能實(shí)現(xiàn)菱形了
接下來(lái)是打印一個(gè)五角星,這是之前一個(gè)萌萌噠妹紙學(xué)習(xí)的代碼,因?yàn)槲冶容^懶啦,所以沒(méi)做修改就直接拿來(lái)了,希望不要介意 #include
這個(gè)程序很明顯分成了四塊,由四個(gè)外層for循環(huán)構(gòu)成,for(n1=1;n1<6;n1++)打印最上面5行,for(n2=1;n2<5;n2++)打印中間4行,for(n3=1;n3<3;n3++)打印下面2行行,for(n4=1;n4<5;n4++)打印最下面4行,大家自己理解下代碼,不懂的就問(wèn)
最后一個(gè)圖形是我剛剛寫的六芒星,完整的代碼輸出結(jié)果是這樣的:
學(xué)習(xí)交流群(565122788)
但是我這里只給出一半代碼,剩下的需要大家自己學(xué)完成,當(dāng)是對(duì)自己的考驗(yàn)也好,作業(yè)也罷,還是希望大家能夠自己親自動(dòng)手試一下的。不懂的就再問(wèn) #include
更多的數(shù)據(jù)類型和循環(huán)
前面我們說(shuō)為了讓計(jì)算機(jī)能夠識(shí)別一個(gè)變量到底占了多少字節(jié),我們需要為變量定義數(shù)據(jù)類型,那究竟有多少種數(shù)據(jù)類型呢,其實(shí)前面我給出32個(gè)關(guān)鍵字里面就已經(jīng)包括了,short、int、long、char、float、double這6個(gè)關(guān)鍵字代表了C語(yǔ)言里的6中基本數(shù)據(jù)類型,怎么去理解它們呢,舉個(gè)例子:大家都見(jiàn)過(guò)剪卡器吧?(沒(méi)見(jiàn)過(guò)?手機(jī)卡總見(jiàn)過(guò)吧)。我們知道不同的手機(jī)使用的手機(jī)卡的大小是有區(qū)別的,我們通常是用剪卡器,拿著它把原來(lái)移動(dòng)的大卡這么一咔,一個(gè)小卡就出來(lái)了,不同型號(hào)的剪卡器咔出來(lái)的手機(jī)卡大小不一樣,比如蘋果手機(jī)的卡就特別小,三星的卡稍微大點(diǎn)......,現(xiàn)在我們聯(lián)想一下,short、int、long、char、float、double這六個(gè)東東是不是很像不同類型的剪卡器?拿著它們?cè)趦?nèi)存上咔咔咔,不同大小的內(nèi)存就分配好了。在32位的系統(tǒng)下short咔出來(lái)的內(nèi)存大小是2個(gè)字節(jié)(也叫byte);int咔出來(lái)的內(nèi)存大小是4個(gè)byte;long咔出來(lái)的內(nèi)存大小是4個(gè)byte;float咔出來(lái)的內(nèi)存大小是4個(gè)byte;double咔出來(lái)的內(nèi)存大小是8個(gè)byte;char咔出來(lái)的內(nèi)存大小是1個(gè)byte。接下來(lái)我們就寫程序看看這些基本的數(shù)據(jù)類型在我們自己電腦上的大小吧。
其中sizeof關(guān)鍵字可以確定給定的類型占據(jù)了多少字節(jié),它后面可以直接跟類型的關(guān)鍵字,比如sizeof(int),也可以跟變量(比如sizeof(i))甚至是表達(dá)式,比如最后一行的sizeof(i-1),它的結(jié)果是表達(dá)式的計(jì)算結(jié)果所占據(jù)的字節(jié)數(shù),i-1的結(jié)果為0,0也是整數(shù),所以占據(jù)的字節(jié)數(shù)為4。(注意這里是指的32位的編譯環(huán)境下的情況,具體平臺(tái)大家可以運(yùn)行這個(gè)程序測(cè)試一下)。然后接下來(lái)是對(duì)這6種基本數(shù)據(jù)的使用情況
可以看到,兩組都是同樣的數(shù)據(jù),但是最后打印出來(lái)的結(jié)果,上面一組數(shù)據(jù)中字符變量、浮點(diǎn)變量和雙精度變量打印出來(lái)的結(jié)果都不對(duì)。原因是什么呢,因?yàn)槭莗rintf的第一個(gè)參數(shù),%d這個(gè)符號(hào),前面的%是占位符,后面的這個(gè)d代表是以整數(shù)形式打印出來(lái),而不同的數(shù)據(jù)類型要以不同的形式打印出來(lái),所以總結(jié)一下,%c表示打印字符、%f是打印浮點(diǎn)數(shù)、%lf是打印雙精度,%hd、%d、%ld分別是打印短整型、整型和長(zhǎng)整型
關(guān)于上面的字符c='a'為什么按%d整數(shù)打印是97的問(wèn)題,這個(gè)其實(shí)就涉及到ascii碼表了,我們知道在計(jì)算機(jī)底層,所有的數(shù)據(jù)都是以0和1存儲(chǔ)的,那計(jì)算機(jī)如何識(shí)別像a、b、c這樣的字符呢,其他它們最終在計(jì)算機(jī)里也是被以0、1數(shù)據(jù)的形式存放的,而且美國(guó)人就為它們指定了一個(gè)統(tǒng)一的標(biāo)準(zhǔn),就是ascii編碼,圖片如下
可以看到小寫字符a的ascii碼值的十進(jìn)制就是97,而大寫A的ascii碼是65,printf中的%d就是以十進(jìn)制整數(shù)方式輸出它在內(nèi)存中的數(shù)據(jù),所以就輸出了97 接下來(lái)將大家使用這些基本數(shù)據(jù)類型最容易犯錯(cuò)的一點(diǎn),就是極限值,我們知道計(jì)算機(jī)里的一位只能表示0或者1,而兩位只能表示0、1、2、3,依次類推,我們?nèi)绻蠳位,那也只能表示2的N次方個(gè)數(shù)據(jù),我們說(shuō)int是4字節(jié)的,就是32位,所以int也是有極限值的,那是不是就是2的32次方呢,理論上來(lái)講,它能表示這么多的數(shù)據(jù),但是因?yàn)橛姓?fù)數(shù)的存在,這個(gè)值還得減半,我們接下來(lái)的程序就是測(cè)試你機(jī)器上的這些基本類型的極限值的,注意unsigned這個(gè)修飾符就是無(wú)符號(hào)的數(shù),比如unsigned int,這就是無(wú)符號(hào)整數(shù),這樣它能表示的范圍就是0~4294967295(2的32次方-1)了。不小心極限值的話,就會(huì)經(jīng)常犯錯(cuò)
C/C++學(xué)習(xí)交流群,歡迎大家一起來(lái)交流提升。565122788進(jìn)群就能獲取C語(yǔ)言新手學(xué)習(xí)大禮包
另外兩種循環(huán):while循環(huán)和do...while循環(huán)(還有一種可以構(gòu)成循環(huán)的是goto,但是先不講).while循環(huán)的格式: while(表達(dá)式){ 循環(huán)執(zhí)行語(yǔ)句;} 下一條語(yǔ)句;while循環(huán)和for循環(huán)的區(qū)別在于它的循環(huán)頭部沒(méi)有賦初值的操作,一開(kāi)始就會(huì)進(jìn)行循環(huán)表達(dá)式的判斷,如果表達(dá)式成立,則進(jìn)入循環(huán),否則跳到循環(huán)的下一條語(yǔ)句。看一個(gè)例子 # include
第五篇:C語(yǔ)言 C++ volatile關(guān)鍵字作用與嵌入式系統(tǒng)程序員
C語(yǔ)言 C++ volatile關(guān)鍵字作用與嵌入式系統(tǒng)程序員
volatile關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。
用volatile關(guān)鍵字聲明的變量i每一次被訪問(wèn)時(shí),執(zhí)行部件都會(huì)從i相應(yīng)的內(nèi)存單元中取出i的值。
沒(méi)有用volatile關(guān)鍵字聲明的變量i在被訪問(wèn)的時(shí)候可能直接從cpu的寄存器中取值(因?yàn)橹癷被訪問(wèn)過(guò),也就是說(shuō)之前就從內(nèi)存中取出i的值保存到某個(gè)寄存器中),之所以直接從寄存器中取值,而不去內(nèi)存中取值,是因?yàn)榫幾g器優(yōu)化代碼的結(jié)果(訪問(wèn)cpu寄存器比訪問(wèn)ram快的多)。
以上兩種情況的區(qū)別在于被編譯成匯編代碼之后,兩者是不一樣的。之所以這樣做是因?yàn)樽兞縤可能會(huì)經(jīng)常變化,保證對(duì)特殊地址的穩(wěn)定訪問(wèn)。
volatile關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對(duì)訪問(wèn)該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對(duì)特殊地址的穩(wěn)定訪問(wèn)。
使用該關(guān)鍵字的例子如下:
int volatile nVint;
當(dāng)要求使用volatile 聲明的變量的值的時(shí)候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過(guò)數(shù)據(jù)。而且讀取的數(shù)據(jù)立刻被保存。
例如:
volatile int i=10;
int a = i;
...//其他代碼,并未明確告訴編譯器,對(duì)i進(jìn)行過(guò)操作
int b = i;
volatile 指出 i是隨時(shí)可能發(fā)生變化的,每次使用它的時(shí)候必須從i的地址中讀取,因而編譯器生成的匯編代碼會(huì)重新從i的地址讀取數(shù)據(jù)放在b中。而優(yōu)化做法是,由于編譯器發(fā)現(xiàn)兩次從i讀數(shù)據(jù)的代碼之間的代碼沒(méi)有對(duì)i進(jìn)行過(guò)操作,它會(huì)自動(dòng)把上次讀的數(shù)據(jù)放在b中。而不是重新從i里面讀。這樣以來(lái),如果i是一個(gè)寄存器變量或者表示一個(gè)端口數(shù)據(jù)就容易出錯(cuò),所以說(shuō)volatile可以保證對(duì)特殊地址的穩(wěn)定訪問(wèn)。
注意,在vc6中,一般調(diào)試模式?jīng)]有進(jìn)行代碼優(yōu)化,所以這個(gè)關(guān)鍵字的作用看不出來(lái)。下面通過(guò)插入?yún)R編代碼,測(cè)試有無(wú)volatile關(guān)鍵字,對(duì)程序最終代碼的影響:
首先,用classwizard建一個(gè)win32 console工程,插入一個(gè)voltest.cpp文件,輸入下面的代碼:
#i nclude
void main()
{
int i=10;
int a = i;
printf(“i= %dn”,a);
//下面匯編語(yǔ)句的作用就是改變內(nèi)存中i的值,但是又不讓編譯器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf(“i= %dn”,b);
}
然后,在調(diào)試版本模式運(yùn)行程序,輸出結(jié)果如下:
i = 10
i = 32
然后,在release版本模式運(yùn)行程序,輸出結(jié)果如下:
i = 10
i = 10
輸出的結(jié)果明顯表明,release模式下,編譯器對(duì)代碼進(jìn)行了優(yōu)化,第二次沒(méi)有輸出正確的i值。
下面,我們把 i的聲明加上volatile關(guān)鍵字,看看有什么變化:
#i nclude
void main()
{
volatile int i=10;
int a = i;
printf(“i= %dn”,a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf(“i= %dn”,b);
}
分別在調(diào)試版本和release版本運(yùn)行程序,輸出都是:
i = 10
i = 32
這說(shuō)明這個(gè)關(guān)鍵字發(fā)揮了它的作用!
文章一:
講講volatile的作用
一個(gè)定義為volatile的變量是說(shuō)這變量可能會(huì)被意想不到地改變,這樣,編譯器就不會(huì)去假設(shè)這個(gè)變量的值了。精確地說(shuō)就是,優(yōu)化器在用到這個(gè)變量時(shí)必須每次都小心地重新讀取這個(gè)變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個(gè)例子: 1).并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
2).一個(gè)中斷服務(wù)子程序中會(huì)訪問(wèn)到的非自動(dòng)變量(Non-automatic variables)3).多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量 回答不出這個(gè)問(wèn)題的人是不會(huì)被雇傭的。我認(rèn)為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問(wèn)題。嵌入式系統(tǒng)程序員經(jīng)常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內(nèi)容將會(huì)帶來(lái)災(zāi)難。
假設(shè)被面試者正確地回答了這是問(wèn)題(嗯,懷疑這否會(huì)是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
1).一個(gè)參數(shù)既可以是const還可以是volatile嗎?解釋為什么。2).一個(gè)指針可以是volatile 嗎?解釋為什么。3).下面的函數(shù)有什么錯(cuò)誤:
int square(volatile int *ptr){ return *ptr * *ptr;} 下面是答案:
1).是的。一個(gè)例子是只讀的狀態(tài)寄存器。它是volatile因?yàn)樗赡鼙灰庀氩坏降馗淖儭K莄onst因?yàn)槌绦虿粦?yīng)該試圖去修改它。
2).是的。盡管這并不很常見(jiàn)。一個(gè)例子是當(dāng)一個(gè)中服務(wù)子程序修該一個(gè)指向一個(gè)buffer的指針時(shí)。
3).這段代碼的有個(gè)惡作劇。這段代碼的目的是用來(lái)返指針*ptr指向值的平方,但是,由于*ptr指向一個(gè)volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼: int square(volatile int *ptr){ int a,b;a = *ptr;b = *ptr;return a * b;} 由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結(jié)果,這段代碼可能返不是你所期望的平方值!正確的代碼如下: long square(volatile int *ptr){ int a;a = *ptr;return a * a;} 講講我的理解:(歡迎打板子...~~?。?/p>
關(guān)鍵在于兩個(gè)地方:
1.編譯器的優(yōu)化(請(qǐng)高手幫我看看下面的理解)
在本次線程內(nèi), 當(dāng)讀取一個(gè)變量時(shí),為提高存取速度,編譯器優(yōu)化時(shí)有時(shí)會(huì)先把變量讀取到一個(gè)寄存器中;以后,再取變量值時(shí),就直接從寄存器中取值;
當(dāng)變量值在本線程里改變時(shí),會(huì)同時(shí)把變量的新值copy到該寄存器中,以便保持一致
當(dāng)變量在因別的線程等而改變了值,該寄存器的值不會(huì)相應(yīng)改變,從而造成應(yīng)用程序讀取的值和實(shí)際的變量值不一致
當(dāng)該寄存器在因別的線程等而改變了值,原變量的值不會(huì)改變,從而造成應(yīng)用程序讀取的值和實(shí)際的變量值不一致
舉一個(gè)不太準(zhǔn)確的例子:
發(fā)薪資時(shí),會(huì)計(jì)每次都把員工叫來(lái)登記他們的銀行卡號(hào);一次會(huì)計(jì)為了省事,沒(méi)有即時(shí)登記,用了以前登記的銀行卡號(hào);剛好一個(gè)員工的銀行卡丟了,已掛失該銀行卡號(hào);從而造成該員工領(lǐng)不到工資
員工 -- 原始變量地址
銀行卡號(hào) -- 原始變量在寄存器的備份
2.在什么情況下會(huì)出現(xiàn)(如1樓所說(shuō))
1).并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
2).一個(gè)中斷服務(wù)子程序中會(huì)訪問(wèn)到的非自動(dòng)變量(Non-automatic variables)3).多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量
補(bǔ)充: volatile應(yīng)該解釋為“直接存取原始內(nèi)存地址”比較合適,“易變的”這種解釋簡(jiǎn)直有點(diǎn)誤導(dǎo)人;
“易變”是因?yàn)橥庠谝蛩匾鸬模蠖嗑€程,中斷等,并不是因?yàn)橛胿olatile修飾了的變量就是“易變”了,假如沒(méi)有外因,即使用volatile定義,它也不會(huì)變化;
而用volatile定義之后,其實(shí)這個(gè)變量就不會(huì)因外因而變化了,可以放心使用了; 大家看看前面那種解釋(易變的)是不是在誤導(dǎo)人
------------簡(jiǎn)明示例如下:------------------
volatile關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對(duì)訪問(wèn)該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對(duì)特殊地址的穩(wěn)定訪問(wèn)。使用該關(guān)鍵字的例子如下: int volatile nVint;>>>>當(dāng)要求使用volatile 聲明的變量的值的時(shí)候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過(guò)數(shù)據(jù)。而且讀取的數(shù)據(jù)立刻被保存。例如:
volatile int i=10;int a = i;...//其他代碼,并未明確告訴編譯器,對(duì)i進(jìn)行過(guò)操作 int b = i;>>>>volatile 指出 i是隨時(shí)可能發(fā)生變化的,每次使用它的時(shí)候必須從i的地址中讀取,因而編譯器生成的匯編代碼會(huì)重新從i的地址讀取數(shù)據(jù)放在b中。而優(yōu)化做法是,由于編譯器發(fā)現(xiàn)兩次從i讀數(shù)據(jù)的代碼之間的代碼沒(méi)有對(duì)i進(jìn)行過(guò)操作,它會(huì)自動(dòng)把上次讀的數(shù)據(jù)放在b中。而不是重新從i里面讀。這樣以來(lái),如果i是一個(gè)寄存器變量或者表示一個(gè)端口數(shù)據(jù)就容易出錯(cuò),所以說(shuō)volatile可以保證對(duì)特殊地址的穩(wěn)定訪問(wèn)。
>>>>注意,在vc6中,一般調(diào)試模式?jīng)]有進(jìn)行代碼優(yōu)化,所以這個(gè)關(guān)鍵字的作用看不出來(lái)。下面通過(guò)插入?yún)R編代碼,測(cè)試有無(wú)volatile關(guān)鍵字,對(duì)程序最終代碼的影響:
>>>>首先,用classwizard建一個(gè)win32 console工程,插入一個(gè)voltest.cpp文件,輸入下面的代碼: >> #i nclude
------------------------------------
volatile對(duì)應(yīng)的變量可能在你的程序本身不知道的情況下發(fā)生改變
比如多線程的程序,共同訪問(wèn)的內(nèi)存當(dāng)中,多個(gè)程序都可以操縱這個(gè)變量 你自己的程序,是無(wú)法判定合適這個(gè)變量會(huì)發(fā)生變化
還比如,他和一個(gè)外部設(shè)備的某個(gè)狀態(tài)對(duì)應(yīng),當(dāng)外部設(shè)備發(fā)生操作的時(shí)候,通過(guò)驅(qū)動(dòng)程序和中斷事件,系統(tǒng)改變了這個(gè)變量的數(shù)值,而你的程序并不知道。
對(duì)于volatile類型的變量,系統(tǒng)每次用到他的時(shí)候都是直接從對(duì)應(yīng)的內(nèi)存當(dāng)中提取,而不會(huì)利用cache當(dāng)中的原有數(shù)值,以適應(yīng)它的未知何時(shí)會(huì)發(fā)生的變化,系統(tǒng)對(duì)這種變量的處理不會(huì)做優(yōu)化——顯然也是因?yàn)樗臄?shù)值隨時(shí)都可能變化的情況。
------------------
典型的例子
for(int i=0;i<100000;i++);這個(gè)語(yǔ)句用來(lái)測(cè)試空循環(huán)的速度的
但是編譯器肯定要把它優(yōu)化掉,根本就不執(zhí)行 如果你寫成
for(volatile int i=0;i<100000;i++);它就會(huì)執(zhí)行了
volatile的本意是“易變的” 由于訪問(wèn)寄存器的速度要快過(guò)RAM,所以編譯器一般都會(huì)作減少存取外部RAM的優(yōu)化。比如:
static int i=0;
int main(void){...while(1){ if(i)dosomething();} }
/* Interrupt service routine.*/ void ISR_2(void){ i=1;}
程序的本意是希望ISR_2中斷產(chǎn)生時(shí),在main當(dāng)中調(diào)用dosomething函數(shù),但是,由于編譯器判斷在main函數(shù)里面沒(méi)有修改過(guò)i,因此
可能只執(zhí)行一次對(duì)從i到某寄存器的讀操作,然后每次if判斷都只使用這個(gè)寄存器里面的“i副本”,導(dǎo)致dosomething永遠(yuǎn)也不會(huì)被
調(diào)用。如果將將變量加上volatile修飾,則編譯器保證對(duì)此變量的讀寫操作都不會(huì)被優(yōu)化(肯定執(zhí)行)。此例中i也應(yīng)該如此說(shuō)明。
一般說(shuō)來(lái),volatile用在如下的幾個(gè)地方:
1、中斷服務(wù)程序中修改的供其它程序檢測(cè)的變量需要加volatile;
2、多任務(wù)環(huán)境下各任務(wù)間共享的標(biāo)志應(yīng)該加volatile;
3、存儲(chǔ)器映射的硬件寄存器通常也要加volatile說(shuō)明,因?yàn)槊看螌?duì)它的讀寫都可能由不同意義;
另外,以上這幾種情況經(jīng)常還要同時(shí)考慮數(shù)據(jù)的完整性(相互關(guān)聯(lián)的幾個(gè)標(biāo)志讀了一半被打斷了重寫),在1中可以通過(guò)關(guān)中斷來(lái)實(shí)
現(xiàn),2中可以禁止任務(wù)調(diào)度,3中則只能依靠硬件的良好設(shè)計(jì)了。