天天看點

嵌入式C語言面試題

1、  解釋c語言關鍵字extern、static的含義。

1、  概念基礎:

局部變量:函數内部定義的變量(包括定義在函數内部複合語句中的變量)。

全局變量:定義在函數外部的變量。

作用域:任何辨別符(包括變量、函數名、符号常量及新的資料名)都有它的作用範圍,此範圍稱為該辨別符的作用域。比如符号常量的作用域是從定義符号 常量的地方開始到包含這個#define指令(作用于該符号常量)的檔案末尾或者遇到#undef指令(作用于該符号常量)為止。

2、  對于變量:

static關鍵字:static可以用于修飾局部變量以擴充局部變量的生存期 ,被static關鍵字修飾的局部變量的生存期為:在調用該變量所在的函數前已生成,直到程式退出才消亡,是以在調用該局部變量所在的函數後該變量仍然存在并保持最後使用的值。雖然static關鍵字可以改變局部變量的生存期,但是不能改變局部變量的作用域,該局部變量仍然隻能在定義它的函數中使用。

       static關鍵字也可可用于修飾全局變量,此時它的作用在于限制該全局變量的作用域,隻能在定義該全局變量的檔案中才能使用。

extern關鍵字:隻能用于擴充沒有被static關鍵字修飾的全局變量 。 預設情況下全局變量隻能在定義它的檔案中使用(從定義該全局變量開始到所在檔案的檔案尾),但如果在另一個檔案中将這個變量聲明為外部變量,那麼這個變量 的作用域将被擴充到另外一個檔案中。也可以在定義全局變量之前聲明該變量,進而在檔案中可以在定義該全局變量前使用該全局變量。

3、  對于函數:

static關鍵字:在定義函數時在函數首部的最左邊加上static可以把該函數聲明為内部函數(又叫靜态函數),這樣該函數就隻能在其定義所在的檔案中使用,如果在不同的檔案中有同名的内部函數,則互不幹擾。

extern關鍵字:在定義函數時如果在函數首部的最左端冠以關鍵字extern,則表示此函數是外部函數,可供其他檔案調用。c語言規定,如果在定義函數時省略extern,則隐含為外部函數。

       在檔案中要調用其他檔案中的外部函數,則需要在檔案中用extern聲明該外部函數,然後就可以使用。

2、  解釋c語言關鍵字volatile、const的含義。

volatile 影響編譯器編譯的結果,指出,volatile 變量是随時可能發生變化的,與volatile變量有關的運算,不要進行編譯優化,以免出錯,(vc++ 在産生release版可執行碼時會進行編譯優化,加volatile關鍵字的變量有關的運算,将不進行編譯優化。)。

例如:

volatile int i=10;

int j = i;

...

int k = i;

volatile 告訴編譯器i是随時可能發生變化的,每次使用它的時候必須從i的位址中讀取,因而編譯器生成的可執行碼會重新從i的位址讀取資料放在k中。

而優化做法是,由于編譯器發現兩次從i讀資料的代碼之間的代碼沒有對i進行過操作,它會自動把上次讀的資料放在k中。而不是重新從i裡面讀。這樣以來,如果i是一個寄存器變量或者表示一個端口資料就容易出錯,是以說volatile可以保證對特殊位址的穩定通路,不會出錯。

1、什麼是const?

常類型是指使用類型修飾符const說明的類型,常類型的變量或對象的值是不能被更新的。(當然,我們可以偷梁換柱進行更新:)

2、為什麼引入const?

const 推出的初始目的,正是為了取代預編譯指令,消除它的缺點,同時繼承它的優點。

3、cons有什麼主要的作用?

(1)可以定義const常量,具有不可變性。 例如:

const int max=100; int array[max];

(2)便于進行類型檢查,使編譯器對處理内容有更多了解,消除了一些隐患。例如: void f(const int i) { .........} 編譯器就會知道i是一個常量,不允許修改;

(3)可以避免意義模糊的數字出現,同樣可以很友善地進行參數的調整和修改。 同宏定義一樣,可以做到不變則已,一變都變!如(1)中,如果想修改max的内容,隻需要:const int max=you want;即可!

(4)可以保護被修飾的東西,防止意外的修改,增強程式的健壯性。 還是上面的例子,如果在函數體内修改了i,編譯器就會報錯; 例如:

void f(const int i) { i=10;//error! }

(5) 為函數重載提供了一個參考。

class a { ......

void f(int i) {......} //一個函數

void f(int i) const {......} //上一個函數的重載 ......

};

(6) 可以節省空間,避免不必要的記憶體配置設定。例如:

#define pi 3.14159 //常量宏

const doulbe pi=3.14159; //此時并未将pi放入rom中 ......

double i=pi; //此時為pi配置設定記憶體,以後不再配置設定!

double i=pi; //編譯期間進行宏替換,配置設定記憶體

double j=pi; //沒有記憶體配置設定

double j=pi; //再進行宏替換,又一次配置設定記憶體!

const定義常量從彙編的角度來看,隻是給出了對應的記憶體位址,而不是象#define一樣給出的是立即數,是以,const定義的常量在程式運作過程中隻有一份拷貝,而#define定義的常量在記憶體中有若幹個拷貝。

(7) 提高了效率。 編譯器通常不為普通const常量配置設定存儲空間,而是将它們儲存在符号表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀記憶體的操作,使得它的效率也很高。

3、舉例說明typedef和define的用法。

typedef和#define的用法與差別

一、typedef的用法

在c/c++語言中,typedef常用來定義一個辨別符及關鍵字的别名,它是語言編譯過程的一部分,但它并不實際配置設定記憶體空間,執行個體像:

typedef    int       int;

typedef    int       array[10];

typedef   (int*)   pint;

typedef可以增強程式的可讀性,以及辨別符的靈活性,但它也有“非直覺性”等缺點。

二、#define的用法

#define為一宏定義語句,通常用它來定義常量(包括無參量與帶參量),以及用來實作那些“表面似和善、背後一長串”的宏,它本身并不在編

譯過程中進行,而是在這之前(預處理過程)就已經完成了,但也是以難以發現潛在的錯誤及其它代碼維護問題,它的執行個體像:

#define   int             int

#define   true         1

#define   add(a,b)     ((a)+(b));

#define   loop_10    for (int i=0; i<10; i++)

在scott meyer的effective c++一書的條款1中有關于#define語句弊端的分析,以及好的替代方法,大家可參看。

三、typedef與#define的差別

從以上的概念便也能基本清楚,typedef隻是為了增加可讀性而為辨別符另起的新名稱(僅僅隻是個别名),而#define原本在c中是為了定義常量

,到了c++,const、enum、inline的出現使它也漸漸成為了起别名的工具。有時很容易搞不清楚與typedef兩者到底該用哪個好,如#define

int int這樣的語句,用typedef一樣可以完成,用哪個好呢?我主張用typedef,因為在早期的許多c編譯器中這條語句是非法的,隻是現今的

編譯器又做了擴充。為了盡可能地相容,一般都遵循#define定義“可讀”的常量以及一些宏語句的任務,而typedef則常用來定義關鍵字、冗

長的類型的别名。

宏定義隻是簡單的字元串代換(原地擴充),而typedef則不是原地擴充,它的新名字具有一定的封裝性,以緻于新命名的辨別符具有更易定義變

量的功能。請看上面第一大點代碼的第三行:

typedef    (int*)      pint;

以及下面這行:

#define    pint2    int*

效果相同?實則不同!實踐中見差别:

pint a,b;的效果同int *a; int *b;表示定義了兩個整型指針變量。

而pint2 a,b;的效果同int *a, b;表示定義了一個整型指針變量a和整型變量b。

typedef的四個用途和兩個陷阱

用途一:

定義一種類型的别名,而不隻是簡單的宏替換。可以用作同時聲明指針型的多個對象。比如:

char*   pa,   pb;     //   這多數不符合我們的意圖,它隻聲明了一個指向字元變量的指針,  

//   和一個字元變量;

以下則可行:

typedef   char*   pchar;     //   一般用大寫

pchar   pa,   pb;                 //   可行,同時聲明了兩個指向字元變量的指針

雖然:

char   *pa,   *pb;

也可行,但相對來說沒有用typedef的形式直覺,尤其在需要大量指針的地方,typedef的方式更省事。

用途二:

用在舊的c代碼中(具體多舊沒有查),幫助struct。以前的代碼中,聲明struct新對象時,必須要帶上struct,即形式為:   struct   結構名   對象名,如:

struct   tagpoint1

{

       int   x;

       int   y;

struct   tagpoint1   p1;  

而在c++中,則可以直接寫:結構名   對象名,即:

tagpoint1   p1;

估計某人覺得經常多寫一個struct太麻煩了,于是就發明了:

typedef   struct   tagpoint

}point;

point   p1;   //   這樣就比原來的方式少寫了一個struct,比較省事,尤其在大量使用的時候

或許,在c++中,typedef的這種用途二不是很大,但是了解了它,對掌握以前的舊代碼還是有幫助的,畢竟我們在項目中有可能會遇到較早些年代遺留下來的代碼。

用途三:

用typedef來定義與平台無關的類型。

比如定義一個叫   real   的浮點類型,在目标平台一上,讓它表示最高精度的類型為:

typedef   long   double   real;  

在不支援   long   double   的平台二上,改為:

typedef   double   real;  

在連   double   都不支援的平台三上,改為:

typedef   float   real;  

也就是說,當跨平台時,隻要改下   typedef   本身就行,不用對其他源碼做任何修改。

标準庫就廣泛使用了這個技巧,比如size_t。

另外,因為typedef是定義了一種類型的新别名,不是簡單的字元串替換,是以它比宏來得穩健(雖然用宏有時也可以完成以上的用途)。

用途四:

為複雜的聲明定義一個新的簡單的别名。方法是:在原來的聲明裡逐漸用别名替換一部分複雜聲明,如此循環,把帶變量名的部分留到最後替換,得到的就是原聲明的最簡化版。舉例:

1.   原聲明:int   *(*a[5])(int,   char*);

變量名為a,直接用一個新别名pfun替換a就可以了:

typedef   int   *(*pfun)(int,   char*);  

原聲明的最簡化版:

pfun   a[5];  

2.   原聲明:void   (*b[10])   (void   (*)());

變量名為b,先替換右邊部分括号裡的,pfunparam為别名一:

typedef   void   (*pfunparam)();

再替換左邊的變量b,pfunx為别名二:

typedef   void   (*pfunx)(pfunparam);

pfunx   b[10];

3.   原聲明:doube(*)()   (*e)[9];  

變量名為e,先替換左邊部分,pfuny為别名一:

typedef   double(*pfuny)();

再替換右邊的變量e,pfunparamy為别名二

typedef   pfuny   (*pfunparamy)[9];

pfunparamy   e;  

了解複雜聲明可用的“右左法則”:從變量名看起,先往右,再往左,碰到一個圓括号就調轉閱讀的方向;括号内分析完就跳出括号,還是按先右後左的順序,如此循環,直到整個聲明分析完。舉例:

int   (*func)(int   *p);

首先找到變量名func,外面有一對圓括号,而且左邊是一個*号,這說明func是一個指針;然後跳出這個圓括号,先看右邊,又遇到圓括号,這說明(*func)是一個函數,是以func是一個指向這類函數的指針,即函數指針,這類函數具有int*類型的形參,傳回值類型是int。

int   (*func[5])(int   *);

func右邊是一個[]運算符,說明func是具有5個元素的數組;func的左邊有一個*,說明func的元素是指針(注意這裡的*不是修飾func,而是修飾func[5]的,原因是[]運算符優先級比*高,func先跟[]結合)。跳出這個括号,看右邊,又遇到圓括号,說明func數組的元素是函數類型的指針,它指向的函數具有int*類型的形參,傳回值類型為int。

也可以記住2個模式:

type   (*)(....)函數指針  

type   (*)[]數組指針  

---------------------------------

陷阱一:

記住,typedef是定義了一種類型的新别名,不同于宏,它不是簡單的字元串替換。比如:

先定義:

typedef   char*   pstr;

然後:

int   mystrcmp(const   pstr,   const   pstr);

const   pstr實際上相當于const   char*嗎?不是的,它實際上相當于char*   const。

原因在于const給予了整個指針本身以常量性,也就是形成了常量指針char*   const。

簡單來說,記住當const和typedef一起出現時,typedef不會是簡單的字元串替換就行。

陷阱二:

typedef在文法上是一個存儲類的關鍵字(如auto、extern、mutable、static、register等一樣),雖然它并不真正影響對象的存儲特性,如:

typedef   static   int   int2;   //不可行

編譯将失敗,會提示“指定了一個以上的存儲類”。

  4、語句for( ;1 ;)有什麼問題?它是什麼意思?

  5、do……while和while……do有什麼差別?

  6、請寫出下列代碼的輸出内容

  #include

  main()

  {

   int a,b,c,d;

   a=10;

   b=a++;

   c=++a;

   d=10*a++;

   printf(“b,c,d:%d,%d,%d”,b,c,d);

   return 0;

  }

答:13, 10, 12, 120

  7、設有以下說明和定義:(32位編譯器情況下)

  typedef union {long i; int k[5]; char c;} date;

  struct data { int cat; date cow; double dog;} too;

  date max;

  則語句 printf(“%d”,sizeof(struct date)+sizeof(max));的執行結果是:__52_____

8、下面是51單片機最小系統電路圖,試分析該電路結構以及實作原理?

9、談談彙編語言、c和c++三種語言在嵌入式開發中的差別和特點?簡述你對嵌入式概念的了解?

10、列舉常見的嵌入式作業系統和單片機?簡要介紹你以前做過的項目,在項目中用過什麼處理器和作業系統,實作什麼功能以及關鍵技術。

繼續閱讀