題目一
上個星期,去深圳一家搞ARM開發的公司面試,HR叫我做了一份卷子,裡面都是C程式設計,心中暗喜,因為這些題基本上都在程式員面試寶典裡見過。後來回到學校,在網上搜尋,原來這些題都是嵌入式工程師的經典面試題目,很多網站上都可以找得到。現把他貼出來,附上網上的答案,跟大家分享,因為這些題實在太經典了。
預處理器(Preprocessor)
1 . 用預處理指令#define 聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在這想看到幾件事情:
- #define 文法的基本知識(例如:不能以分号結束,括号的使用,等等)
- 懂得預處理器将為你計算常數表達式的值,是以直接寫出你如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。
- 意識到這個表達式将使一個16位機的整型數溢出-是以要用到長整型符号L,告訴編譯器這個常數是的長整型數。
- 如果你在你的表達式中用到UL(表示無符号長整型),那麼你有了一個好的起點。記住,第一印象很重要。
2 . 寫一個"标準"宏MIN ,這個宏輸入兩個參數并傳回較小的一個。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
這個測試是為下面的目的而設的:
- 辨別#define在宏中應用的基本知識。這是很重要的。因為在 嵌入(inline)操作符 變為标準C的一部分之前,宏是友善産生嵌入代碼的唯一方法,對于嵌入式系統來說,為了能達到要求的性能,嵌入代碼經常是必須的方法。
- 三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能産生比if-then-else更優的代碼,了解這個用法是很重要的。
- 懂得在宏中小心地把參數用括号括起來
-
我也用這個問題開始讨論宏的副作用,例如:當你寫下面的代碼時會發生什麼事?
least = MIN(*p++, b); ->((p++) <b ?(p++) : b) 此時前後兩個的p++ 的值已經不是同一個了,多處使用這個宏時,每次的p++ 都不是同一個值
-
預處理器辨別#error的目的是什麼?
如果你不知道答案,請看參考文獻1。這問題對區分一個正常的夥計和一個書呆子是很有用的。隻有書呆子才會讀C語言課本的附錄去找出象這種問題的答案。當然如果你不是在找一個書呆子,那麼應試者最好希望自己不要知道答案。
死循環(Infinite loops)
-
嵌入式系統中經常要用到無限循環,你怎麼樣用C編寫死循環呢?
這個問題用幾個解決方案。我首選的方案是:
while(1)
{
}
一些程式員更喜歡如下方案:
for(;?
{
}
這個實作方式讓我為難,因為這個文法沒有确切表達到底怎麼回事。如果一個應試者給出這個作為方案,我将用這個作為一個機會去探究他們這樣做的基本原理。如果他們的基本答案是:"我被教着這樣做,但從沒有想到過為什麼。"這會給我留下一個壞印象。
第三個方案是用 goto
Loop:
…
goto Loop;
應試者如給出上面的方案,這說明或者他是一個彙編語言程式員(這也許是好事)或者他是一個想進入新領域的BASIC/FORTRAN程式員。
資料聲明(Data declarations)
-
用變量a給出下面的定義
a) 一個整型數(An integer)
b)一個指向整型數的指針( A pointer to an integer)
c)一個指向指針的的指針,它指向的指針是指向一個整型數( A pointer to a pointer to an intege)r
d)一個有10個整型數的數組( An array of 10 integers)
e) 一個有10個指針的數組,該指針是指向一個整型數的。(An array of 10 pointers to integers)
f) 一個指向有10個整型數數組的指針( A pointer to an array of 10 integers)
g) 一個指向函數的指針,該函數有一個整型參數并傳回一個整型數(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數并傳回一個整型數( 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
人們經常聲稱這裡有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當我寫這篇文章時,為了确定文法的正确性,我的确查了一下書。但是當我被面試的時候,我期望被問到這個問題(或者相近的問題)。因為在被面試的這段時間裡,我确定我知道這個問題的答案。應試者如果不知道所有的答案(或至少大部分答案),那麼也就沒有為這次面試做準備,如果該面試者沒有為這次面試做準備,那麼他又能為什麼出準備呢?
Static
-
關鍵字static的作用是什麼?
這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用:
- 在函數體,一個被聲明為靜态的變量在這一函數被調用過程中維持其值不變。
- 在子產品内(但在函數體外),一個被聲明為靜态的變量可以被子產品内所用函數通路,但不能被子產品外其它函數通路。它是一個本地的全局變量。
-
在子產品内,一個被聲明為靜态的函數隻可被這一子產品内的其它函數調用。那就是,這個函數被限制在聲明它的子產品的本地範圍内使用。
大多數應試者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。這是一個應試者的嚴重的缺點,因為他顯然不懂得本地化資料和代碼範圍的好處和重要性。
Const
7.關鍵字const有什麼含意?
我隻要一聽到被面試者說:“const意味着常數”,我就知道我正在和一個業餘者打交道。去年Dan Saks已經在他的文章裡完全概括了const的所有用法,是以ESP(譯者:Embedded Systems Programming)的每一位讀者應該非常熟悉const能做什麼和不能做什麼.如果你從沒有讀到那篇文章,隻要能說出const意味着"隻讀"就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正确的答案。(如果你想知道更詳細的答案,仔細讀一下Saks的文章吧。)
如果應試者能正确回答這個問題,我将問他一個附加的問題:
下面的聲明都是什麼意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前兩個的作用是一樣,a是一個常整型數。第三個意味着a是一個指向常整型數的指針(也就是,整型數是不可修改的,但指針可以)。第四個意思a是一個指向整型數的常指針(也就是說,指針指向的整型數是可以修改的,但指針是不可修改的)。最後一個意味着a是一個指向常整型數的常指針(也就是說,指針指向的整型數是不可修改的,同時指針也是不可修改的)。如果應試者能正确回答這些問題,那麼他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關鍵字 const,也還是能很容易寫出功能正确的程式,那麼我為什麼還要如此看重關鍵字const呢?我也如下的幾下理由:
- 關鍵字const的作用是為給讀你代碼的人傳達非常有用的資訊,實際上,聲明一個參數為常量是為了告訴了使用者這個參數的應用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學會感謝這點多餘的資訊。(當然,懂得用const的程式員很少會留下的垃圾讓别人來清理的。)
- 通過給優化器一些附加的資訊,使用關鍵字const也許能産生更緊湊的代碼。
- 合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。
Volatile
-
關鍵字volatile有什麼含意?并給出三個不同的例子。
一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精确地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用儲存在寄存器裡的備份。下面是volatile變量的幾個例子:
- 并行裝置的硬體寄存器(如:狀态寄存器)
- 一個中斷服務子程式中會通路到的非自動變量(Non-automatic variables)
-
多線程應用中被幾個任務共享的變量
回答不出這個問題的人是不會被雇傭的。我認為這是區分C程式員和嵌入式系統程式員的最基本的問題。搞嵌入式的家夥們經常同硬體、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的内容将會帶來災難。
假設被面試者正确地回答了這是問題(嗯,懷疑是否會是這樣),我将稍微深究一下,看一下這家夥是不是直正懂得volatile完全的重要性。
- 一個參數既可以是const還可以是volatile嗎?解釋為什麼。
- 一個指針可以是volatile 嗎?解釋為什麼。
- 下面的函數有什麼錯誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
- 是的。一個例子是隻讀的狀态寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程式不應該試圖去修改它。
- 是的。盡管這并不很常見。一個例子是當一個中服務子程式修該一個指向一個buffer的指針時。
- 這段代碼有點變态。這段代碼的目的是用來返指針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;
}
位操作(Bit manipulation)
-
嵌入式系統總是要使用者對變量或寄存器進行位操作。給定一個整型變量a,寫兩段代碼,第一個設定a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
對這個問題有三種基本的反應
- 不知道如何下手。該被面者從沒做過任何嵌入式系統的工作。
- 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon為其較複雜的通信晶片寫的驅動程式,它用到了bit fields是以完全對我無用,因為我的編譯器用其它的方式來實作bit fields的。從道德講:永遠不要讓一個非嵌入式的家夥粘實際硬體的邊。
- 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,是應該被用到的方法。最佳的解決方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜歡為設定和清除值而定義一個掩碼同時定義一些說明常數,這也是可以接受的。我希望看到幾個要點:說明常數、|=和&=~操作。
通路固定的記憶體位置(Accessing fixed memory locations)
-
嵌入式系統經常具有要求程式員去通路某特定的記憶體位置的特點。在某工程中,要求設定一絕對位址為0x67a9的整型變量的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務。
這一問題測試你是否知道為了通路一絕對位址把一個整型數強制轉換(typecast)為一指針是合法的。這一問題的實作方式随着個人風格不同而不同。典型的類似代碼如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一個較晦澀的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。
中斷(Interrupts)
- 中斷是嵌入式系統中重要的組成部分,這導緻了很多編譯開發商提供一種擴充—讓标準C支援中斷。具代表事實是,産生了一個新的關鍵字 __interrupt。下面的代碼就使用了__interrupt關鍵字去定義了一個中斷服務子程式(ISR),請評論一下這段代碼的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
這個函數有太多的錯誤了,以至讓人不知從何說起了:
- ISR 不能傳回一個值。如果你不懂這個,那麼你不會被雇用的。
- ISR 不能傳遞參數。如果你沒有看到這一點,你被雇用的機會等同第一項。
- 在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。
- 與第三點一脈相承,printf()經常有重入和性能上的問題。如果你丢掉了第三和第四點,我不會太為難你的。不用說,如果你能得到後兩點,那麼你的被雇用前景越來越光明了。
代碼例子(Code examples)
12 . 下面的代碼輸出是什麼,為什麼?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
這個問題測試你是否懂得C語言中的整數自動轉換原則,我發現有些開發者懂得極少這些東西。不管如何,這無符号整型問題的答案是輸出是 “>6”。原因是當表達式中存在有符号類型和無符号類型時所有的操作數都自動轉換為無符号類型。是以-20變成了一個非常大的正整數,是以該表達式計算出的結果大于6。這一點對于應當頻繁用到無符号資料類型的嵌入式系統來說是豐常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。
- 評價下面的代碼片斷:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
對于一個int型不是16位的處理器為說,上面的代碼是不正确的。應編寫如下:
unsigned int compzero = ~0;
這一問題真正能揭露出應試者是否懂得處理器字長的重要性。在我的經驗裡,好的嵌入式程式員非常準确地明白硬體的細節和它的局限,然而PC機程式往往把硬體作為一個無法避免的煩惱。
到了這個階段,應試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應試者不是很好,那麼這個測試就在這裡結束了。但如果顯然應試者做得不錯,那麼我就扔出下面的追加問題,這些問題是比較難的,我想僅僅非常優秀的應試者能做得不錯。提出這些問題,我希望更多看到應試者應付問題的方法,而不是答案。不管如何,你就當是這個娛樂吧...
動态記憶體配置設定(Dynamic memory allocation)
-
盡管不像非嵌入式計算機那麼常見,嵌入式系統還是有從堆(heap)中動态配置設定記憶體的過程的。那麼嵌入式系統中,動态配置設定記憶體可能發生的問題是什麼?
這裡,我期望應試者能提到記憶體碎片,碎片收集的問題,變量的持行時間等等。這個主題已經在ESP雜志中被廣泛地讨論過了(主要是 P.J. Plauger, 他的解釋遠遠超過我這裡能提到的任何解釋),所有回過頭看一下這些雜志吧!讓應試者進入一種虛假的安全感覺後,我拿出這麼一個小節目:
下面的代碼片段的輸出是什麼,為什麼?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts(“Got a null pointer”);
else
puts(“Got a valid pointer”);
這是一個有趣的問題。最近在我的一個同僚不經意把0值傳給了函數malloc,得到了一個合法的指針之後,我才想到這個問題。這就是上面的代碼,該代碼的輸出是"Got a valid pointer"。我用這個來開始讨論這樣的一問題,看看被面試者是否想到庫例程這樣做是正确。得到正确的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。
Typedef
15 Typedef 在C語言中頻繁用以聲明一個已經存在的資料類型的同義字。也可以用預處理器做類似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結構s指針。哪種方法更好呢?(如果有的話)為什麼?
這是一個非常微妙的問題,任何人答對這個問題(正當的原因)是應當被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一個擴充為
struct s * p1, p2;
.
上面的代碼定義p1為一個指向結構的指,p2為一個實際的結構,這也許不是你想要的。第二個例子正确地定義了p3 和p4 兩個指針。
晦澀的文法
16 . C語言同意一些令人震驚的結構,下面的結構是合法的嗎,如果是它做些什麼?
int a = 5, b = 7, c;
c = a+++b;
這個問題将做為這個測驗的一個愉快的結尾。不管你相不相信,上面的例子是完全合乎文法的。問題是編譯器如何處理它?水準不高的編譯作者實際上會争論這個問題,根據最處理原則,編譯器應當能處理盡可能所有合法的用法。是以,上面的代碼被處理成:
c = a++ + b;
是以, 這段代碼持行後a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把這個當作問題。我發現這個問題的最大好處是這是一個關于代碼編寫風格,代碼的可讀性,代碼的可修改性的好的話題
題目二
1、int a[10]={1,2,3,4,5,6,7,8,9,0};
int *p=&a[1];
則p[6]等于8
2、整數數組清零:bzero(),memset()。
3、sizeof();測試變量所占位址的位元組數
4、 main()
{
char *str[]={“ab”,“cd”,“ef”,“gh”,“ij”,“kl”};
char t;
t=(str+4)[-1];
printf("%s",t);
}則顯示"gh"
5、小端:低位位元組資料存儲在低位址
大端:高位位元組資料存儲在低位址
例如:int a=0x12345678;(a首位址為0x2000)
0x2000 0x2001 0x2002 0x2003
0x12 0x34 0x56 0x78 大端格式
6、異步IO和同步IO差別
如果是同步IO,當一個IO操作執行時,應用程式必須等待,直到此IO執行完,相反,異步IO操作在背景運作,
IO操作和應用程式可以同時運作,提高系統性能,提高IO流量; 在同步檔案IO中,線程啟動一個IO操作然後就立即進入等待狀态,直到IO操作完成後才醒來繼續執行,而異步檔案IO中,
線程發送一個IO請求到核心,然後繼續處理其他事情,核心完成IO請求後,将會通知線程IO操作完成了。
7、用變量a定義
一個整型數 int a;
一個指向整型數的指針 int a;
一個指向指針的指針,它指向的指針式指向一個整型數 int **a;
一個有10個整型數的數組 int a[10];
一個有10指針的數組,該指針是指向一個整型數 int a[10];
一個指向有10個整型數數組的指針 int (a)[10];
一個指向函數的指針,該函數有一個整型數參數并傳回一個整型數 int (a)(int);
一個有10個指針的數組,該指針指向一個函數,該函數有一個整型數參數并傳回一個整型 int (a[10])(int);
8、int foo(void)
{
int i;
char c=0x80;
i=c;
if(i>0)
return 1;
return 2;
}傳回值為2;因為i=c=-128;如果c=0x7f,則i=c=127。
9、a=b2;a=b/4;a=b%8;a=b/88+b%4;a=b15;效率最高的算法
a=b2 -> a=b<<1;
a=b/4 -> a=b>>2;
a=b%8 -> a=b&7;
a=b/88+b%4 -> a=((b>>3)<<3)+(b&3)
a=b15 -> a=(b<<4)-b
10、c關鍵字
c的關鍵字共32個
*資料類型關鍵字(12)
char,short,int,long,float,double,unsigned,signed,union,enum,void,struct
*控制語句關鍵字(12)
if,else,switch,case,default,for,do,while,break,continue,goto,return
*存儲類關鍵字(5)
auto,extern,register,static,const
*其他關鍵字(3)
sizeof,typedef,volatile
11、int main(void)
{
unsigned int a = 6;
int b = -20;
char c;
(a+b>6)?(c=1):(c=0);
}則c=1,但a+b=-14;如果a為int類型則c=0。
原來有符号數和無符号數進行比較運算時(==,<,>,<=,>=),有符号數隐式轉換成了無符号數(即底層的補碼不變,但是此數從有符号數變成了無符号數),
比如上面 (a+b)>6這個比較運算,a+b=-14,-14的補碼為1111111111110010。此數進行比較運算時,
被當成了無符号數,它遠遠大于6,是以得到上述結果。
12、給定一個整型變量a,寫兩段代碼,第一個設定a的bit3,第二個清除a的bit,在以上兩個操作中,
要保持其它位不變。
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
13、要求設定一絕對位址為0x67a9的整型變量的值為0xaa66。
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;(建議用這種)
一個較晦澀的方法是:
*(int * const)(0x67a9) = 0xaa66;
14、中斷是嵌入式系統中重要的組成部分,這導緻了很多編譯開發商提供一種擴充—讓标準C支援中斷。
具代表性的是,産生了一個新的關鍵字__interrupt。下面的代碼就使用了__interrupt關鍵字去定義了一個中斷服務子程式(ISR),請評論一下這段代碼的。
__interrupt void compute_area (void)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
ISR不可能有參數和傳回值的!
ISR盡量不要使用浮點數處理程式,浮點數的處理程式一般來說是不可重入的,而且是消耗大量CPU時間的!!
printf函數一般也是不可重入的,UART屬于低速裝置,printf函數同樣面臨大量消耗CPU時間的問題!
15、評價下面的代碼片斷:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
對于一個int型不是16位的處理器為說,上面的代碼是不正确的。應編寫如下:
unsigned int compzero = ~0;
16、main()
{
char *ptr;
if ((ptr = (char )malloc(0)) == NULL)
puts(“Got a null pointer”);
else
puts(“Got a valid pointer”);
}
該代碼的輸出是“Got a valid pointer”。還可以ptr=‘a’;不出現段錯誤
17、Typedef 在C語言中頻繁用以聲明一個已經存在的資料類型的同義字。也可以用預處理器做類似的事。
例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結構s指針。哪種方法更好呢?(如果有的話)為什麼?
這是一個非常微妙的問題,任何人答對這個問題(正當的原因)是應當被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一個擴充為
struct s * p1, p2;
上面的代碼定義p1為一個指向結構的指,p2為一個實際的結構,這也許不是你想要的。第二個例子正确地定義了p3 和p4 兩個指針。
18、int a = 5, b = 7, c;
c = a+++b;
則c=12。
19、int main()
{
int j=2;
int i=1;
if(i = 1) j=3;
if(i = 2) j=5;
printf("%d",j);
}
輸出為5;如果再加上if(i=3)j=6;則輸出6。
20、宏定義是在預編譯階段被處理的。
21、Norflash與Nandflash的差別
(1)、NAND閃存的容量比較大
(2)、由于NandFlash沒有挂接在位址總線上,是以如果想用NandFlash作為系統的啟動盤,就需要CPU具備特殊的功能,
如s3c2410在被選擇為NandFlash啟動方式時會在上電時自動讀取NandFlash的4k資料到位址0的SRAM中。
(3)、NAND Flash一般位址線和資料線共用,對讀寫速度有一定影響。NOR Flash閃存資料線和位址線分開,
是以相對而言讀寫速度快一些。
22、反碼:對原碼除符号位外的其餘各位逐位取反就是反碼
補碼:負數的補碼就是對反碼加1
正數的原碼、反碼、補碼都一樣
23、pthread_t tid;
pthread_create(&tid,NULL,pthread_func,NULL);//建立線程
pthread_join(tid,NULL);//等待子線程結束,并回收資源
pthread_detach(tid);//與目前程序分離
pthread_exit(NULL);//退出調用線程
pthread_cancel(tid);//取消線程
pthread_mutex mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(&mutex,NULL);//初始化一個互斥鎖
pthread_mutex_lock(&mutex);//對互斥鎖上鎖
pthread_mutex_unlock(&mutex);//對互斥鎖解鎖
sem_t sem;
sem_init(&sem,0,1);//建立信号量并初始化它的值
sem_wait(&sem);//信号量的值減1
sem_post(&sem);//信号量的值加1
24、記憶體管理MMU的作用
*記憶體配置設定和回收
*記憶體保護
*記憶體擴充
*位址映射
25、ROM是隻讀存儲器,掉電不丢失
RAM是讀寫存儲器,掉電丢失
26、SRAM:CPU的緩存就是SRAM,靜态的随機存取存儲器,加電情況下,不需要重新整理,資料不會丢失
DRAM,動态随機存取存儲器最為常見的系統記憶體,需要不斷重新整理,才能儲存資料
SDRAM:同步動态随機存儲器,即資料的讀取需要時鐘來同步。
27、signed char 的取值範圍-128~127.
28、編譯和連結有什麼不同?(如外部符号的處理)
編譯生成的是目标檔案(object *.o);
編譯過程中對于外部符号不做任何解釋和處理。外部符号對應的就是“符号”
連結生成的是可執行程式
連結将會解釋和處理外部符号。外部符号對應的是位址
29、已知strcpy函數的函數原型是:
char *strcpy(char *strDest, const char *strSrc)。其中,strDest是目的字元串,strSrc是源字元串。
不調用C++/C的字元串庫函數,請編寫函數strcpy
char *strcpy(char *strDest, const char *strSrc)
{
int i=0;
if(!(strDest && strSrc))
return;
while(strDest[i++] = *strSrc++);
return strDest;
}
30、strcpy能把strSrc的内容複制到strDest,為什麼還要char *類型的傳回值?
為了實作鍊式表達式
int len = strlen(strcpy(strDest, strSrc));
31、寫一個“标準”宏MIN,這個宏輸入兩個參數并傳回較小的一個
#define MIN(a, b) (a) <= (b) ? (a) : (b)
32、關鍵字static的作用是什麼
static用來修飾一個局部的變量的時候,
生命域是全局的
作用域是局部的
static用來修飾一個子產品内的(某一個C的源程式檔案)全局變量的時候
生命域不變
作用域減小,隻在本子產品内有效
static用來修飾一個函數的時候
作用域減小,隻在本子產品内有效
33、說明下面的聲明的含義:
A.
const int a; // a是一個常數
int const a; // a是一個常數
B.
const int *a; // a是一個指向整型常數的指針
int * const a; // a是一個指向整型變量的常指針
int const * a const; // a是一個指向整型常數的常指針
C.
char *strcpy(char *strDest, const char *strSrc);
// 參數在函數内部不會被修改
const int strcmp(char *source, char *dest);
// 函數的傳回值不能被修改
const int a = strcmp(xx, yy);
if(strcmp(xx,yy) != 0)
34、說明關鍵字volatile有什麼含意,并給出例子。
volatile表示被修飾的符号是易變的。告訴編譯器不要随便優化我的代碼!!
*一個硬體寄存器
*中斷中用到的變量
*線程之間共享變量
volatile int a = 10;
while((a & 0x01) == 0);
#define P_UART_STATUS ((const volatile unsigned int *)0x88000000);
// volatile表示硬體會修改這個寄存器的内容
// const表示該寄存器隻讀,寫無意義
35、printf可以接受多個參數,為什麼,請寫出printf的原型。
int printf(const char *fmt, …);
36、什麼是堆棧,簡述為什麼需要堆棧?
堆棧是計算機中最常用的一種資料結構,儲存資料的一塊連續記憶體;比如函數的調用是用堆棧實作的。
37、請列舉常用的串行通信方式(兩種以上),并簡述串行通信和并行通信不同之處、優缺點。
異步通信和同步通信;并行速度快,串行口線間幹擾小
38、列舉一下你熟悉7層OSI協定中的幾層。說說你最熟悉的一層協定的功能。
應用層,表示層,會話層,傳輸層,網絡層,資料鍊路層,實體層。
39、路由協定:網關-網關協定,外部網關協定,内部網關協定(RIP-1、RIP-IGRP、EIGRP、IS-IS和OSPF)
40、位轉換
位 8 7 6 5 4 3 2 1
數 v8 v7 v6 v5 v4 v3 v2 v1
轉換後:
位 8 7 6 5 4 3 2 1
數 v1 v2 v3 v4 v5 v6 v7 v8
unsigned char bit_reverse(unsigned char c)
{
unsigned char buf = 0;
int bit = 8;
while(bit)
{
bit–;
buf |= ((c & 1) << bit);
c >>=1;
}
return buf;
}
41、字元串倒序
1)、inverted_order(char *p)
{
char *s1,*s2,tem;
s1=p;
s2=s1+strlen§-1;
while(s1<s2)
{
tem=*s1;
*s1=*s2;
*s2=tem;
s1++;
s2–;
}
}
2)、inverted_order(char *p)
{
int len = strlen(src);
char *des = (char *)malloc(len + 1);
char *s = &src[len -1];
char *d = des;
while(len-- != 0)
*d++ = *s–;
d = 0;
free(des);
}
42、引用和指針的差別
(1). 指針是一個實體,而引用僅是個别名;
(2). 引用使用時無需解引用(),指針需要解引用;
(3). 引用隻能在定義時被初始化一次,之後不可變;指針可變;
(4). 引用沒有 const,指針有 const,const 的指針不可變;
(5). 引用不能為空,指針可以為空;
(6). “sizeof 引用”得到的是所指向的變量(對象)的大小,
而“sizeof指針”得到的是指針本身(所指向的變量或對象的位址)的大小;
(7). 指針和引用的自增(++)運算意義不一樣;
43、隊列和棧的差別
隊列是先進先出,隻能在一端插入另一端删除,可以從頭或尾進行周遊(但不能同時周遊),
棧是先進後出,隻能在同一端插入和删除,隻能從頭部取資料
44、四層模型?七層模型?TCP/IP協定包括?
這7層是:實體層、資料鍊路層、網路層、傳輸層、話路層、表示層和應用層
這4層分别為:應用層、傳輸層、網絡層、鍊路層。
TCP/IP協定族包括(IP)、(ARP)、(RARP)、(ICMP)、(UDP)、(TCP)、(RIP)、Telnet、(SMTP)、DNS等協定。
45、TCP通信建立和結束的過程?端口的作用
三次握手和四次揮手;端口是一個軟體結構,被客戶程式或服務程序用來發送和接收資訊。一個端口對應一個16比特的數。服務程序通常使用一個固定的端口。
21端口:21端口主要用于FTP(File Transfer Protocol,檔案傳輸協定)服務。 23端口:23端口主要用于Telnet(遠端登入)服務,是Internet上普遍采用的登入和仿真程式。
25端口:25端口為SMTP(Simple Mail TransferProtocol,簡單郵件傳輸協定)伺服器所開放,
主要用于發送郵件,如今絕大多數郵件伺服器都使用該協定。
53端口:53端口為DNS(Domain Name Server,域名伺服器)伺服器所開放,
主要用于域名解析,DNS服務在NT系統中使用的最為廣泛。
67、68端口:67、68端口分别是為Bootp服務的Bootstrap Protocol Server
(引導程式協定服務端)和Bootstrap Protocol Client(引導程式協定用戶端)開放的端口。
69端口:TFTP是Cisco公司開發的一個簡單檔案傳輸協定,類似于FTP。
79端口:79端口是為Finger服務開放的,主要用于查詢遠端主機線上使用者、作業系統類型以及是否緩沖區溢出等使用者的詳細資訊。880端口:80端口是為HTTP(HyperText Transport Protocol,超文本傳輸協定)開放的,
這是上網沖浪使用最多的協定,主要用于在WWW(World Wide Web,網際網路)服務上傳輸資訊的協定。
99端口:99端口是用于一個名為“Metagram Relay”(亞對策延時)的服務,
該服務比較少見,一般是用不到的。
109、110端口:109端口是為POP2(Post Office Protocol Version 2,郵局協定2)服務開放的,
110端口是為POP3(郵件協定3)服務開放的,POP2、POP3都是主要用于接收郵件的。
111端口:111端口是SUN公司的RPC(Remote Procedure Call,遠端過程調用)服務所開放的端口,
主要用于分布式系統中不同計算機的内部程序通信,RPC在多種網絡服務中都是很重要的元件。
113端口:113端口主要用于Windows的“Authentication Service”(驗證服務)。
119端口:119端口是為“Network News Transfer Protocol”(網絡新聞討論區傳輸協定,簡稱NNTP)開放的。
135端口:135端口主要用于使用RPC(Remote Procedure Call,遠端過程調用)協定并提供DCOM(分布式元件對象模型)服務。
137端口:137端口主要用于“NetBIOS Name Service”(NetBIOS名稱服務)。
139端口:139端口是為“NetBIOS Session Service”提供的,主要用于提供Windows檔案和列印機共享以及Unix中的Samba服務。
143端口:143端口主要是用于“Internet Message Access Protocol”v2(Internet消息通路協定,簡稱IMAP)。
161端口:161端口是用于“Simple Network Management Protocol”(簡單網絡管理協定,簡稱SNMP)。
443端口:43端口即網頁浏覽端口,主要是用于HTTPS服務,是提供加密和通過安全端口傳輸的另一種HTTP。
554端口:554端口預設情況下用于“Real Time Streaming Protocol”(實時流協定,簡稱RTSP)。
1024端口:1024端口一般不固定配置設定給某個服務,在英文中的解釋是“Reserved”(保留)。
1080端口:1080端口是Socks代理服務使用的端口,大家平時上網使用的WWW服務使用的是HTTP協定的代理服務。
1755端口:1755端口預設情況下用于“Microsoft Media Server”(微軟媒體伺服器,簡稱MMS)。
4000端口:4000端口是用于大家經常使用的QQ聊天工具的,再細說就是為QQ用戶端開放的端口,QQ服務端使用的端口是8000。
5554端口:在今年4月30日就報道出現了一種針對微軟lsass服務的新蠕蟲病毒——震蕩波(Worm.Sasser),該病毒可以利用TCP 5554端口開啟一個FTP服務,主要被用于病毒的傳播。
5632端口:5632端口是被大家所熟悉的遠端控制軟體pcAnywhere所開啟的端口。
8080端口:8080端口同80端口,是被用于WWW代理服務的,可以實作網頁浏覽。
46、實體位址轉換成IP位址的協定?反之?
位址解析協定(ARP)的作用是将IP位址轉換成實體位址;反位址解析協定(RARP)則負責将實體位址轉換成IP位址。
47、WLAN:無線區域網路絡:利用射頻技術進行資料傳輸的系統。
WLAN是指應用無線通信技術将計算機裝置互聯起來,構成可以互相通信和實作資源共享的網絡體系。無線區域網路本質的特點是不再使用通信電纜将計算機與網絡連接配接起來,而是通過無線的方式連接配接,進而使網絡的建構和終端的移動更加靈活。
48、已知數組table,用宏求元素個數。
COUNT(table) (sizeof(table)/sizeof(table[0]));
49、定義一個兩個參數的标準宏MAX,總是輸出最大值。
#define MAX(a,b) ((a)>(b)?(a):(b))
50、什麼是平衡二叉樹?
當且僅當兩個子樹的高度差不超過1時,這個樹是平衡二叉樹。
51、全局變量和局部變量的差別。
全局變量,儲存在靜态區.進入main函數之前就被建立.生命周期為整個源程式;
局部變量,在棧中配置設定.在函數被調用時才被建立.生命周期為函數内。
52、數組與連結清單的差別。
數組中的資料在記憶體中的按順序存儲的,而連結清單是随機存儲的!
要通路數組中的元素可以按下标索引來通路,速度比較快,如果對他進行插入操作的話, 就得移動很多元素,是以對數組進行插入操作效率很低!由于連表是随機存儲的,
連結清單在插入,删除操作上有很高的效率(相對數組),如果要通路連結清單中的某個元素的話,
那就得從連結清單的頭逐個周遊,直到找到所需要的元素為止,
是以連結清單的随機通路的效率就比數組要低
53、死鎖的四個條件及處理方法。
(1)互斥條件:一個資源每次隻能被一個程序使用。
(2) 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。
(3)不剝奪條件:程序已獲得的資源,在末使用完之前,不能強行剝奪。
(4)循環等待條件:若幹程序之間形成一種頭尾相接的循環等待資源關系。
解決死鎖的方法分為死鎖的預防,避免,檢測與恢複三種
54、程序排程政策。
先進先出算法,最短CPU運作期優先排程算法,輪轉法,多級隊列方法
55、Linux驅動程式流程及功能。
裝置驅動程式的功能:
對裝置初始化和釋放
把資料從核心傳送到硬體和從硬體讀取資料
讀取應用程式傳送給裝置檔案的資料和回送應用程式請求的資料
檢測和處理裝置出現的錯誤
56、時間換空間、空間換時間的例子。
冒泡排序 — 時間換空間
快速排序,堆排序 — 空間換時間
57、MAC層通信協定有哪些?
ISO2110,IEEE802,IEEE802.2
58、關鍵字static的作用是什麼?
*在函數體内,一個被聲明為靜态的變量在這一函數被調用過程中維持其值不變(該變量存放在靜态變量區)。
*在子產品内(但在函數體外),一個被聲明為靜态的變量可以被子產品内所用函數通路,但不能被子產品外其它函數通路。
它是一個本地的全局變量。
*在子產品内,一個被聲明為靜态的函數隻可被這一子產品内的其它函數調用。
那就是,這個函數被限制在聲明它的子產品的本地範圍内使用。
59、參數的傳遞方式有幾種?
1、值傳遞
2、指針傳遞
嚴格來看,隻有一種傳遞,值傳遞,指針傳遞也是按值傳遞的,複制的是位址。
60、局部變量能否和全局變量重名?
答:能,局部會屏蔽全局。要用全局變量,需要使用 ":: " 局部變量可以與全局變量同名,在函數内引用這個變量時,會用到同名的局部變量,而不會用到全局變量。
對于有些編譯器而言,在同一個函數内可以定義多個同名的局部變量,比如在兩個循環體内都定義一個同名的局部變量,
而那個局部變量的作用域就在那個循環體内。
61、如何引用一個已經定義過的全局變量?
答:extern 可以用引用頭檔案的方式,也可以用extern關鍵字,如果用引用頭檔案方式來引用某個在頭檔案中聲明的全局變理,
假定你将那個變量寫錯了,那麼在編譯期間會報錯,如果你用extern方式引用時,假定你犯了同樣的錯誤,
那麼在編譯期間不會報錯,而在連接配接期間報錯。
62、全局變量可不可以定義在可被多個.C檔案包含的頭檔案中?為什麼? 答:可以,在不同的C檔案中以static形式來聲明同名全局變量。 可以在不同的C檔案中聲明同名的全局變量,前提是其中隻能有一個C檔案中對此變量賦初值,此時連接配接不會出錯
63、語句for( ;1 ;)有什麼問題?它是什麼意思? 答:和while(1)相同。
64、static全局變量與普通的全局變量有什麼差別?static局部變量和普通局部變量有什麼差別?static函數與普通函數有什麼差別? 全局變量(外部變量)的說明之前再加static 就構成了靜态的全局變量。全局變量本身就是靜态存儲方式,
靜态全局變量當然也是靜态存儲方式。 這兩者在存儲方式上并無不同。
這兩者的差別雖在于非靜态全局變量的作用域是整個源程式, 當一個源程式由多個源檔案組成時,
非靜态的全局變量在各個源檔案中都是有效的。 而靜态全局變量則限制了其作用域,
即隻在定義該變量的源檔案内有效。 從以上分析可以看出,把局部變量改變為靜态變量後是改變了它的存儲
方式即改變了它的生存期。把全局變量改變為靜态變量後是改變了它的作用域,
限制了它的使用範圍。static函數與普通函數作用域不同。僅在本檔案。隻在目前源檔案中使用的函數應該說明為内部函數(static)
,内部函數應該在目前源檔案中說明和定義。對于可在目前源檔案以外使用的函數,應該在一個頭檔案中說明,
要使用這些函數的源檔案要包含這個頭檔案 static全局變量與普通的全局變量有什麼差別:
static全局變量隻初使化一次,防止在其他檔案單元中被引用static局部變量和普通局部變量有什麼差別:
static局部變量隻被初始化一次,下一次依據上一次結果值; static函數與普通函數有什麼差別:
static函數在記憶體中隻有一份,普通函數在每個被調用中維持一份拷貝
65、程式的局部變量存在于(堆棧)中,全局變量存在于(靜态區 )中,動态申請資料存在于( 堆)中。
66、-1,2,7,28,126請問28和126中間那個數是什麼?為什麼?
答案應該是4^3-1=63 規律是n3-1(當n為偶數0,2,4)n3+1(當n為奇數1,3,5)
67、用兩個棧實作一個隊列的功能?要求給出算法和思路!
設2個棧為A,B, 一開始均為空.
入隊: 将新元素push入棧A;
出隊: (1)判斷棧B是否為空;
(2)如果不為空,則将棧A中所有元素依次pop出并push到棧B;
(3)将棧B的棧頂元素pop出;
68、.軟體測試都有那些種類?
人工測試:個人複查、抽查和會審
機器測試:黑盒測試(針對系統功能的測試 )和白盒測試 (測試函數功能,各函數接口)
69、堆棧溢出一般是由什麼原因導緻的?
沒有回收垃圾資源。
70、寫出float x 與“零值”比較的if語句。
if(x>0.000001&&x<-0.000001)
71、P位址的編碼分為哪倆部分?
IP位址由兩部分組成,網絡号和主機号。不過是要和“子網路遮罩”按位與上之後才能區分哪些是網絡位哪些
是主機位
72、寫一個程式, 要求功能:求出用1,2,5這三個數不同個數組合的和為100的組合個數。
如:100個1是一個組合,5個1加19個5是一個組合。。。。 請用C++語言寫。
答案:最容易想到的算法是:
設x是1的個數,y是2的個數,z是5的個數,number是組合數
注意到0<=x<=100,0<=y<=50,0<=z=20,是以可以程式設計為:
number=0;
for (x=0; x<=100; x++)
for (y=0; y<=50; y++)
for (z=0; z<=20; z++)
if ((x+2y+5z)==100)
number++;
73、記憶體對齊問題的原因?
平台原因(移植原因):不是所有的硬體平台都能通路任意位址上的任意資料;
性能原因:資料結構(尤其是棧)應該盡可能地在自然邊界上對齊,因為為了通路未對齊的記憶體,處理器需要做兩次記憶體通路,
而對齊的記憶體通路僅需要一次。
74、比較一下程序和線程的差別?
(1)、排程:線程是CPU排程和分派的基本機關
(2)、擁有資源:
- 程序是系統中程式執行和資源配置設定的基本機關
-
線程自己一般不擁有資源(除了必不可少的程式計數器,一組寄存器和棧),但他可以去通路其所屬程序的資源,
如程序代碼,資料段以及系統資源(已打開的檔案,I/O裝置等)。
(3)系統開銷:
- 同一程序中的多個線程可以共享同一位址空間,是以它們之間的同步和通信的實作也比較簡單
-
在程序切換的時候,涉及到整個目前程序CPU環境的儲存以及新被排程運作的程序的CPU環境的設定;
而線程切換隻需要儲存和設定少量寄存器的内容,并不涉及存儲器管理方面的操作,進而能更有效地使用系統資源和
提高系統吞吐量。
75、main()
{
int a[5]={1,2,3,4,5};
int ptr=(int )(&a+1);//&a相當于變成了行指針,加1則變成了下一行首位址
printf("%d,%d",(a+1),(ptr-1));
}
(a+1)就是a[1],(ptr-1)就是a[4],執行結果是2,5
76、 void getmemory(char *p)
{
p=(char *) malloc(100);
strcpy(p,“hello world”);
}
int main( )
{
char *str=NULL;
getmemory(str);
printf("%s/n",str);
free(str);
return 0;
}
程式崩潰,getmemory中的malloc 不能傳回動态記憶體, free()對str操作很危險
77、void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, “hello world”);
printf(str);
}
請問運作Test函數會有什麼樣的結果?
答:程式崩潰。因為GetMemory并不能傳遞動态記憶體,Test函數中的 str一直都是 NULL。strcpy(str, “hello world”);将使程式崩潰。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, “hello”);
printf(str);
}
請問運作Test函數會有什麼樣的結果?
答:(1)能夠輸出hello
(2)記憶體洩漏
78、char *GetMemory(void)
{
char p[] = “hello world”;
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
請問運作Test函數會有什麼樣的結果?
答:可能是亂碼。因為GetMemory傳回的是指向“棧記憶體”的指針,該指針的位址不是 NULL,
但其原現的内容已經被清除,新内容不可知。
79、void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
請問運作Test函數會有什麼樣的結果?
答:篡改動态記憶體區的内容,後果難以預料,非常危險。因為free(str);之後,str成為野指針,
if(str != NULL)語句不起作用。
野指針不是NULL指針,是指向被釋放的或者通路受限記憶體指針。
造成原因:指針變量沒有被初始化任何剛建立的指針不會自動成為NULL;
指針被free或delete之後,沒有置NULL;
指針操作超越了變量的作用範圍,比如要傳回指向棧記憶體的指針或引用,因為棧記憶體在函數結束時會被釋放。
80、unsigned char *p=(unsigned char *)0x0801000
unsigned char *q=(unsigned char *)0x0810000
p+5 =? 0x0801005
q+5 =? 0x0810005
81、程序間通信方式:管道、命名管道、消息隊列、共享記憶體、信号、信号量、套接字。
(1)、 管道( pipe ):管道是一種半雙工的通信方式,資料隻能單向流動,而且隻能在具有親緣關系的程序間使用。
程序的親緣關系通常是指父子程序關系。
(2)、有名管道 (named pipe) :有名管道也是半雙工的通信方式,但是它允許無親緣關系程序間的通信。
(3)、信号量( semophore ) :信号量是一個計數器,可以用來控制多個程序對共享資源的通路。
它常作為一種鎖機制,防止某程序正在通路共享資源時,其他程序也通路該資源。
是以,主要作為程序間以及同一程序内不同線程之間的同步手段。
(4)、消息隊列( message queue ) : 消息隊列是由消息的連結清單,存放在核心中并由消息隊列辨別符辨別。
消息隊列克服了信号傳遞資訊少、管道隻能承載無格式位元組流以及緩沖區大小受限等缺點。
(5)、信号 ( sinal ) : 信号是一種比較複雜的通信方式,用于通知接收程序某個事件已經發生。
(6)、共享記憶體( shared memory ) :共享記憶體就是映射一段能被其他程序所通路的記憶體,
這段共享記憶體由一個程序建立,但多個程序都可以通路。共享記憶體是最快的 IPC 方式,它是針對其他程序間通信方式運作效率低而專門設計的。它往往與其他通信機制,
如信号兩,配合使用,來實作程序間的同步和通信。
(7)、套接字( socket ) : 套接字也是一種程序間通信機制,與其他通信機制不同的是,它可用于不同及其間的程序通信。
82、宏和函數的優缺點?
(1)、函數調用時,先求出實參表達式的值,然後帶入形參。而使用帶參數的宏隻是進行簡單的字元替換。
(2)、函數調用是在程式運作時處理的,配置設定臨時的記憶體單元;而宏展開則是在編譯時進行的,在展開時并不配置設定記憶體單元,
不進行值的傳遞處理,也沒有“傳回值”的概念。
(3)、對函數中的實參和形參都要定義類型,二者的類型要求一緻,應進行類型轉換;而宏不存在類型問題,宏名無類型,
它的參數也是無類型,隻是一個符号代表,展開時帶入指定的字元即可。宏定義時,字元串可以是任何類型的資料。
(4)、調用函數隻可得到一個傳回值,而宏定義可以設法得到幾個結果。
(5)、使用宏次數多時,宏展開後源程式長,因為每次展開一次都使程式增長,而函數調用不使源程式變長。
(6)、宏替換不占運作時間,隻占編譯時間;而函數調用則占運作時間(配置設定單元、保留現場、值傳遞、傳回)。
83、C和c++的不同
c和c++的一些不同點(從語言本身的角度):
1)c++源于c,c++最重要的特性就是引入了面向對象機制,class關鍵字。
2)c++中,變量可以再任何地方聲明;c中,局部變量隻能在函數開頭聲明。
3)c++中,const型常量是編譯時常量;c中,const常量隻是隻讀的變量。
4)c++有&引用;c沒有
5)c++的struct聲明自動将結構類型名typedef;c中struct的名字隻在結構标簽名字空間中,不是作為一種類型出現
6)c語言的main函數可以遞歸調用;c++中則不可以
7)c中,void *可以隐式轉換成其他指針類型;c++中要求現實轉換,否則編譯通不過
84、6.大小端格式問題。
方法一:
void checkCpuMode(void)
{
int i = 0x12345678;
char *cp = (char *)&i;
if(*cp == 0x78)
printf(“little endian”);
else
printf(“big endian\n”);
}
方法二:
void checkCpuMode(void)
{
int a = 0x12345678;
if((char)a == 0x12)
printf(“big endian\n”);
else
printf(“little endian\n”);
}
方法三:
void checkCpuMode(void)
{
union
{
short s;
char c[sizeof(short)];
}un;
un.s=0x0102;
if(un.[0]==1&&un.c[1]==2)
printf(“big endian\n”);
else
printf(“little endian\n”);
}
85、由C/C++編譯的程式占用的記憶體分為以下幾個部分
1、棧區(stack): 由編譯器自動配置設定釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于資料結構中的棧。
2、堆區(heap): 一般由程式員配置設定釋放,若程式員不釋放,程式結束時可能由OS回收。
注意它與資料結構中的堆是兩回事,配置設定方式倒是類似于連結清單。
3、全局區(static): 全局變量和靜态變量的存儲是放在一塊的,初始化的全局變量和靜态變量在一塊區域,
未初始化的全局變量和未初始化的靜态變量在相鄰的另一塊區域,程式結束後有系統釋放 。
4、文字常量區: 常量字元串就是放在這裡的, 程式結束後由系統釋放。
5、程式代碼區: 存放函數體的二進制代碼。
87、for(m=5;m–;m<0)
{
printf(“m=%d\n”,m);
}輸出:4、3、2、1、0
88、5[“abcdef”]能夠編譯通過,請問編譯後的結果是什麼?
printf("%d\n",5[“abcdef”]);輸出’f’的ACSII值,如果是4[“abcdef”]則輸出’e’的ACSII的值。
89、線程同步的方法:信号量、條件變量、互斥鎖。
90、for(i=0;i<2,i<3,i<4;i++)
printf("%d \n",i); 輸出:0,1,2,3。
100、實體位址,虛拟位址,邏輯位址和總線位址的差別
邏輯位址(Logical Address)是指由程式産生的與段相關的偏移位址部分。
例如,你在進行C語言指針程式設計中,可以讀取指針變量本身值(&操作),實際上這個值就是邏輯位址,
它是相對于你目前程序資料段的位址,不和絕對實體位址相幹。隻有在Intel實模式下,
邏輯位址才和實體位址相等(因為實模式沒有分段或分頁機制, Cpu不進行自動位址轉換);
邏輯也就是在Intel 保護模式下程式執行代碼段限長内的偏移位址(假定代碼段、資料段如果完全一樣)。
應用程式員僅需與邏輯位址打交道,而分段和分頁機制對您來說是完全透明的,僅由系統程式設計人員涉及。
應用程式員雖然自己可以直接操作記憶體,那也隻能在作業系統給你配置設定的記憶體段操作。
線性位址(Linear Address)是邏輯位址到實體位址變換之間的中間層。程式代碼會産生邏輯位址,
或者說是段中的偏移位址,加上相應段的基位址就生成了一個線性位址。如果啟用了分頁機制,
那麼線性位址可以再經變換以産生一個實體位址。若沒有啟用分頁機制,那麼線性位址直接就是實體位址。
Intel 80386的線性位址空間容量為4G(2的32次方即32根位址總線尋址)。
實體位址(Physical Address) 是指出現在CPU外部位址總線上的尋址實體記憶體的位址信号,是位址變換的最終結果位址。
如果啟用了分頁機制,那麼線性位址會使用頁目錄和頁表中的項變換成實體位址。
如果沒有啟用分頁機制,那麼線性位址就直接成為實體位址了。
在x86下,外設的i/o位址是獨立的,即有專門的指令通路外設i/o,i/o位址就是你所說的“總線位址”。
而“實體位址”就是ram位址。在arm中,i/o和ram統一編址,但linux為了統一各個平台,仍然保留這個概念,其實就是實體位址。
101、編寫核心程式中申請記憶體和編寫應用程式時申請記憶體有什麼差別
應用程式使用C函數庫中的記憶體配置設定函數malloc()申請記憶體核心會為程序使用的代碼和資料空間維護一個目前位置的值brk,
這個值儲存在每個程序的資料結構中。它指出了程序代碼和資料(包括動态配置設定的資料空間)在程序位址空間中的末端位置。
當malloc()函數為程式配置設定記憶體時,它會通過系統調用brk()把程式要求新增的空間長度通知核心,
核心代碼進而可以根據malloc()所提供的資訊來更新brk的值,但此時并不為新申請的空間映射實體記憶體頁面。
隻有當程式尋址到某個不存在對應實體頁面的位址時,核心才會進行相關實體記憶體頁面的映射操作。
當使用者使用記憶體釋放函數free()動态釋放已申請的記憶體塊時,c庫中的記憶體管理函數就會把所釋放的記憶體塊标記為空閑,
以備程式再次申請記憶體時使用。在這個過程中核心為該程序所配置設定的這個實體頁面并不會被釋放掉。
隻有當程序最終結束時核心才會全面收回已配置設定和映射到該程序位址空間範圍内的所有實體記憶體頁面。
102、如何用C語言實作讀寫寄存器變量
#define rBANKCON0 ((volatile unsigned long )0x48000004)
rBankCON0 = 0x12;
103、sscanf(“123456 “, “%4s”, buf); 1234
sscanf(“123456 asdfga”,”%[^ ]”, buf); 123456
sscanf(“123456aafsdfADDEFF”, “%[1-9a-z]”, buf); 123456aafsdf
sscanf(“123afsdfADJKLJ”, “%[^A-Z]”, buf); 123afsdf
104、char s=“AAA”;
s[0]=‘B’;
printf("%s",s);
有什麼錯?
"AAA"是字元串常量。s是指針,指向這個字元串常量,是以聲明s的時候就有問題。
cosnt char s=“AAA”;
然後又因為是常量,是以對是s[0]的指派操作是不合法的。
105、數組a[N],存放了1至N-1個數,其中某個數重複一次。寫一個函數,找出被重複的數字.時間複雜度必須為o(N)函數原型:
int do_dup(int a[],int N) int do_dup(int a[],int N)//a[0]為監視哨
{ {
int i; int temp;
int s; while (a[0]!=a[a[0]])
int num; {
for(i=0;i<N;i++) temp=a[0];
s+=a[i]; a[0]=a[temp];
num=s-N*(N-1)/2;(num即為重複數) a[temp]=temp;
} }
return a[0];
}
106、tcp/udp是屬于哪一層?tcp/udp有何優缺點?
tcp /udp屬于運輸層
TCP 服務提供了資料流傳輸、可靠性、有效流控制、全雙工操作和多路複用技術等。
與 TCP 不同, UDP 并不提供對 IP 協定的可靠機制、流控制以及錯誤恢複功能等。由于 UDP 比較簡單, UDP 頭包含很少的位元組,比 TCP 負載消耗少。
tcp: 提供穩定的傳輸服務,有流量控制,缺點是標頭大,備援性不好
udp: 不提供穩定的服務,標頭小,開銷小
107、 char a = 100;
char b = 150;//10010110//01101010
unsigned char c ;
c = (a < b)? a:b; --》 c=150;
108、應用程式ping發出的是ICMP請求封包
109、在C語言中memcpy和memmove是一樣的嗎?
memmove()與memcpy()一樣都是用來拷貝src所指向記憶體内容前n個位元組到dest所指的位址上,不同是,當src和dest所指的記憶體區域重疊時,
memmove()仍然可以正确處理,不過執行效率上略慢些。
110、C語言程式代碼優化方法
- 選擇合适的算法和資料結構
- 使用盡量小的資料類型
- 使用自加、自減指令
-
減少運算的強度
求餘運算(a=a%8改為a=a&7)
平方運算(a=pow(a,2.0)改為a=a*a)
用移位實作乘除法運算
- 延時函數的自加改為自減
- switch語句中根據發生頻率來進行case排序
111、找出一個字元串中一個最長的連續的數字,并标注出位置和個數。
void main()
{
char input[100];
char output[100] = {0};
int count = 0, maxlen = 0, i = 0;
char *in=input, *out=output,*temp=NULL,*final=NULL;
printf(“Please input string(length under 100):\n”);
scanf("%s", input);
printf(“Input string is %s\n”, input);
while(*in!=’\0’)
{
if(*in>=‘0’&&*in<=‘9’)
{
count=0;
temp=in;
for(?*in>=‘0’)&&(*in<=‘9’);in++)
count++;
if (maxlen<count)
{
maxlen=count;
=temp;
}
}
in++;
}
for(i=0; i<maxlen; i++)
*(out++) = *(final++);
*out=’\0’;
printf(“Maxlen is %d\n”, maxlen);
printf(“Output is %s\n”, output);
}
112、寫出螺旋矩陣
void Matrix(int m,int n) //順時針
{
int i,j,a=1;
int s[100][100];
int small = (m<n)?m:n;
int k=small/2;
for(i=0;i<k;i++)
{
for(j=i;j<n-i-1;j++)
s[i][j]=a++;
for(j=i;j<m-i-1;j++)
s[j][n-i-1]=a++;
for(j=n-i-1;j>i;j–)
s[m-i-1][j]=a++;
for(j=m-i-1;j>i;j–)
s[j][i]=a++;
}
if(small & 1)
{
if(m<n)
for(i=k;i<n-k;++i)
s[k][i]=a++;
else
for(i=k;i<m-k;++i)
s[i][k]=a++;
}
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
printf("=",s[i][j]);
printf("\n");
}
}
113、int *a = (int )2;
printf("%d",a+3); 答案是2+34=14;int類型位址加1 相當于加4個位元組
114、main()
{
char a,b,c,d;
scanf("%c%c",&a,&b);
c=getchar();d=getchar();
printf("%c%c%c%c\n",a,b,c,d);
} 輸出:12
3
115、int a[2] = {1, 2};
//指向常量的指針,指針可以變,指針指向的内容不可以變
const int *p = a;//與int const *p = a;等價
p++;//ok
p = 10;//error
//常指針,指針不可以變,指針指向的内容可以變
int const p2 = a;
p2++;//error
*p2 = 10;//ok
//指向常量的常指針,都不可以改變
p3++;//error
*p3 = 10;//error
116、#error 預處理指令的作用是,編譯程式時,隻要遇到#error 就會生成一個編譯錯誤提
示消息,并停止編譯
117、中斷活動的全過程大緻為:
1、中斷請求:中斷事件一旦發生或者中斷條件一旦構成,中斷源送出“申請報告”,
與請求CPU暫時放下目前的工作而轉為中斷源作為專項服務
2、中斷屏蔽:雖然中斷源送出了“申請報告”,但是,是否得到CPU的響應,
還要取決于“申請報告”是否能夠通過2道或者3道“關卡”(中斷屏蔽)送達CPU
(相應的中斷屏蔽位等于1,為關卡放行;反之相應的中斷屏蔽位等于0,為關卡禁止通行);
3、中斷響應:如果一路放行,則CPU響應中斷後,将被打斷的工作斷點記錄下來
(把斷點位址保護到堆棧),挂起“不再受理其他申請報告牌”
(清除全局中斷标志位GIE=0),跳轉到中斷服務子程式
4、保護現場:在處理新任務時可能破壞原有的工作現場,是以需要對工作現場和工作環境進行适當保護;
5、調查中斷源:檢查“申請報告”是由哪個中斷源送出的,以便作出有針對性的服務;
6、中斷處理:開始對查明的中斷源進行有針對性的中斷服務;
7、清除标志:在處理完畢相應的任務之後,需要進行撤消登記(清除中斷标志),以避免造成重複響應;
8、恢複現場:恢複前面曾經被保護起來的工作現場,以便繼續執行被中斷的工作;
9、中斷傳回:将被打斷的工作斷點找回來(從堆棧中恢複斷點位址),
并摘下“不再受理其他申請報告牌”(GIE=1),繼續執行原先被打斷的工作。
118、linux程序間通訊的幾種方式的特點和優缺點,和适用場合。分類:
#管道( pipe ):管道是一種半雙工的通信方式,資料隻能單向流動,
而且隻能在具有親緣關系的程序間使用。程序的親緣關系通常是指父子程序關系。
#有名管道(named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關系程序間的通信。
#信号量( semophore ) : 信号量是一個計數器,可以用來控制多個程序對共享資源的通路。
它常作為一種鎖機制,防止某程序正在通路共享資源時,其他程序也通路該資源。是以,主要作
為程序間以及同一程序内不同線程之間的同步手段。
#消息隊列( message queue ) : 消息隊列是由消息的連結清單,存放在核心中并由消息隊列辨別
符辨別。消息隊列克服了信号傳遞資訊少、管道隻能承載無格式位元組流以及緩沖區大小受限等缺點。
#信号 ( sinal ) : 信号是一種比較複雜的通信方式,用于通知接收程序某個事件已經發生。
#共享記憶體( shared memory):共享記憶體就是映射一段能被其他程序所通路的記憶體,
這段共享記憶體由一個程序建立,但多個程序都可以通路。共享記憶體是最快的IPC方式,
它是針對其他程序間通信方式運作效率低而專門設計的。它往往與其他通信機制,
如信号量,配合使用,來實作程序間的同步和通信。
#套接字( socket ) : 套解口也是一種程序間通信機制,與其他通信機制不同的是,
它可用于不同及其間的程序通信。
119、關鍵字volatile有什麼含意?并給出三個不同的例子。
一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設
這個變量的值了。精确地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,
而不是使用儲存在寄存器裡的備份。
下面是volatile變量的幾個例子:
1). 并行裝置的硬體寄存器(如:狀态寄存器)
2). 一個中斷服務子程式中會通路到的非自動變量(Non-automatic variables)
3). 多線程應用中被幾個任務共享的變量 回答不出這個問題的人是不會被雇傭的。我認為這是區分C程式員和嵌入式系統程式員的最基本的問題。嵌入式系統程式員經常同硬體、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile内容将會帶來災難。
假設被面試者正确地回答了這是問題(嗯,懷疑這否會是這樣),我将稍微深究一下,
看一下這家夥是不是直正懂得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;
}
Volatile 關鍵字告訴編譯器不要持有變量的臨時性拷貝。一般用在多線程程式中,
以避免在其中一個線程操作該變量時,将其拷貝入寄存器。
請看以下情形: A線程将變量複制入寄存器,然後進入循環,反複檢測寄存器的值是否滿足
一定條件(它期待B線程改變變量的值。在此種情況下,當B線程改變了變量的值時,
已改變的值對其在寄存器的值沒有影響。是以A線程進入死循環。
volatile 就是在此種情況下使用。
120、如何防止同時産生大量的線程,方法是使用線程池,線程池具有可以同時提高排程效率和
限制資源使用的好處,線程池中的線程達到最大數時,其他線程就會排隊等候
121、main()
{
char *p1=“name”;
char p2;
p2=(char)malloc(20);
memset (p2, 0, 20);
while(*p2++ = *p1++);
printf(“%s\n”,p2);
}
答案:Answer:empty string. 因p2++已經指到了’\0’了;
122、作業系統的記憶體配置設定一般有哪幾種方式,各有什麼優缺點?
定長和變長。
變長:記憶體時比較靈活,但是易産生記憶體碎片。
定長:靈活性差,但配置設定效率較高,不會産生記憶體碎片
123、全局變量可不可以定義在可被多個.C檔案包含的頭檔案中?為什麼?
答:可以,在不同的C檔案中以static形式來聲明同名全局變量。可以在不同的C檔案中聲明同名的全局變量,
前提是其中隻能有一個C檔案中對此變量賦初值,此時連接配接不會出錯
124、确定子產品的功能和子產品的接口是在軟體設計的那個隊段完成的?概要設計階段
125、#define N 500
unsigned char count;
for(count=0;count < N;count++)
{
printf("—%d—\n",count);
}死循環,因為unsigned char 最大為255
126、給定結構struct A
{
char t:4; 4位
char k:4; 4位
unsigned short i:8; 8位
unsigned long m; // 偏移2位元組保證4位元組對齊
}; // 共8位元組
127、ICMP(ICMP協定對于網絡安全具有極其重要的意義)功能主要有:
· 偵測遠端主機是否存在。
· 建立及維護路由資料。
· 重導資料傳送路徑。
· 資料流量控制。
單向、雙向連結清單操作、寫一個快速排序算法(原理:找一個基準值,分别将大于和小于基準值的資料放到基準值左右兩邊,即一次劃分。由于處在兩邊的資料也是無序的,是以再用同樣的劃分方法對左右兩邊的序列進行再次劃分,直到劃分元素隻剩1個時結束,)
1、單向連結清單逆序
LONDE *link_reverse(LNODE *head)
{
LNODE *pb,*pt;
if(head == NULL)
return head;
pb = head->next;
head->next=NULL;
while(pb != NULL)
{
pt = pb->next;
pb->next = head;
head = pb;
pb = pt;
}
return head;
}
2、快速排序
void quick_sort(int num,int start_num,int end_num)
{
if(start_num < end_num)
{
int i = start_num;
int j = end_num;
int temp = num[start_num];
while(i < j)
{
while(i < j && num[j] < temp)
j–;
if(i < j)
num[i++] = num[j];//把小于基準值放在左邊
while(i < j && num[i] >= temp)
i++;
if(i < j)
num[j–] = num[i];//把大于基準值放在右邊
}
num[i] = temp;
quick_sort(num,start_num,i-1);
quick_sort(num,i+1,end_num);
}
}
3、//二分擦找
int binary_search(int array[],int value,int size)
{
int low=0,high=size-1,mid;
while(low<=high) //隻要高低不碰頭就繼續二分查找
{
mid=(low+high)/2;
if(value==array[mid]) //比較是不是與中間元素相等
return mid;
else if(value > array[mid]) //每查找一次,就判斷一次所要查找變量所在範圍,并繼續二分
low=mid; //如果大小中間值,下限移到中間的後一個位,上限不變,往高方向二分
else
high=mid; //上限移到中間的前一個位,往低方向二分
}
return -1;
}
/雙向循環連結清單插入函數/
TYPE *insert_link(TYPE *head,TYPE *p_in)
{
TYPE *p_mov = head,p_front = head;
if(head == NULL)
{
head = p_in;
p_in->next = head;
p_perior = head;
}
else
{
while((p_in->[] > p_mov->[]) && (p_mov->next != head))
{
p_front = p_mov;
p_mov = p_mov->next;
}
if(p_in->[] <= p_mov->[])
{
if(head == p_mov)
{
p_in->prior = head->prior;
head->prior->next = p_in;
p_in->next = p_mov;
p_mov->prior = p_in;
head = p_in;
}
else
{
pf->next = p_in;
p_in->prior = p_front;
p_in->next = p_mov;
p_mov->prior = p_in;
}
}
else
{
p_mov->next = p_in;
p_in->prior = p_mov;
p_in->next = head;
head->prior = p_in;
}
}
return head;
}
/雙向連結清單删除函數/
TYPE *delete_link(TYPE *head,int num)
{
TYPE *p_mov = head,p_front = head;
if(head == NULL)
printf(“Not link\n”);
while((p_mov->num != num) && (p_mov->next != head))
{
p_front = p_mov;
p_mov = p_mov->next;
}
if(p_mov->num == num)
{
if(p_mov == head)
{
if(p_mov->next == head && p_mov->prior == head)
{
free(pb);
head =NULL;
return head;
}
head->next->prior = head->prior;
head->prior->next = head->next;
head = head->next;
}
else
{
p_front->next = p_mov->next;
p_mov->next->prior = p_front;
}
free(p_mov);
printf(“The node is delete\n”);
}
else
{
printf(“The node not been found\n”);
}
return head;
}