C語言相關知識點
預處理器(Processor)
用預處理指令#define 聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在這想看到幾件事情:
1) #define 語法的基本知識(例如:不能以分號結束,括號的使用,等等)
2)懂得預處理器將爲你計算常數表達式的值,因此,直接寫出你是如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。
3) 意識到這個表達式將使一個16位機的整型數溢出-因此要用到長整型符號L,告訴編譯器這個常數是的長整型數。
4) 如果你在你的表達式中用到UL(表示無符號長整型),那麼你有了一個好的起點。記住,第一印象很重要。
寫一個"標準"宏MIN ,這個宏輸入兩個參數並返回較小的一個。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
這個測試是爲下面的目的而設的:
1) 標識#define在宏中應用的基本知識。這是很重要的。因爲在嵌入(inline)操作符變爲標準C的一部分之前,宏是方便產生嵌入代碼的唯一方法,對於嵌入式系統來說,爲了能達到要求的性能,嵌入代碼經常是必須的方法。
2)三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能產生比if-then-else更優化的代碼,瞭解這個用法是很重要的。
3) 懂得在宏中小心地把參數用括號括起來
4) 我也用這個問題開始討論宏的副作用,例如:當你寫下面的代碼時會發生什麼事?
least = MIN(*p++, b);
死循環(Infinite loops)
嵌入式系統中經常要用到無限循環,你怎麼樣用C編寫死循環呢?
這個問題用幾個解決方案。我首選的方案是:
while(1)
{
}
一些程序員更喜歡如下方案:
for(;;)
{
}
這個實現方式讓我爲難,因爲這個語法沒有確切表達到底怎麼回事。如果一個應試者給出這個作爲方案,我將用這個作爲一個機會去探究他們這樣做的基本原理。如果他們的基本答案是:"我被教着這樣做,但從沒有想到過爲什麼。"這會給我留下一個壞印象。
第三個方案是用 goto
Loop:
...
goto Loop;
應試者如給出上面的方案,這說明或者他是一個彙編語言程序員(這也許是好事)或者他是一個想進入新領域的BASIC/FORTRAN程序員。
sizeof與strlen
區別一:sizeof是運算符,在編譯時即計算好了;而strlen是函數,要在運行時才能計算。
區別二:strlen 是一個函數,它用來計算指定字符串 str 的長度,但不包括結束字符(即 null 字符)。
sizeof 是一個單目運算符,而不是一個函數。與函數 strlen 不同,它的參數可以是數組、指針、類型、對象、函數等
char sArr[] = "ILOVEC";
printf("sArr的長度=%d\n", strlen(sArr)); strlen不包括結束字符(即 null 字符)
printf("sArr的長度=%d\n", sizeof(sArr)); sizeof 包括結束字符 null
C函數相關問題
在ARM系統中,函數調用的時候,參數是通過哪種方式傳遞的?
參數<=4時候,通過R0~R3傳遞,>4的通過壓棧方式傳遞
中斷(interrupt,如鍵盤中斷)與異常(exception,如除零異常)有何區別?
異常:在產生時必須考慮與處理器的時鐘同步,實踐上,異常也稱爲同步中斷。在處理器執行到由於編程失誤而導致的錯誤指令時,或者在執行期間出現特殊情況(如缺頁),必須靠內核處理的時候,處理器就會產生一個異常。
所謂中斷應該是指外部硬件產生的一個電信號,從cpu的中斷引腳進入,打斷cpu當前的運行;
所謂異常,是指軟件運行中發生了一些必須作出處理的事件,cpu自動產生一個陷入來打斷當前運行,轉入異常處理流程。
優先級反轉問題在嵌入式系統中的問題
a) 首先請解釋優先級反轉問題
b) 很多RTOS提供優先級繼承策略(Priority inheritance)和優先級天花板策略(Priority ceilings)用來解決優先級反轉問題,請討論這兩種策略。
優先級翻轉是指高優先級任務通過信號量機制訪問共享資源時,該信號量被一個低優先級任務佔有,因此造成高優先級任務被阻塞,實時性難以保證。
優先級繼承策略(Priority inheritance):繼承現有被阻塞任務的最高優先級作爲其優先級,任務退出臨界區,恢復初始優先級。
優先級天花板策略(Priority ceilings):控制訪問臨界資源的信號量的優先級天花板。
優先級繼承策略對任務執行流程的影響相對教小,因爲只有當高優先級任務申請已被低優先級任務佔有的臨界資源
這一事實發生時,才擡升低優先級任務的優先級。
數據聲明(Data declarations)
用變量a給出下面的定義
a) 一個整型數(An integer)
b)一個指向整型數的指針(A pointer to an integer)
c)一個指向指針的的指針,它指向的指針是指向一個整型數(A pointer to a pointer to an integer)
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 )
答案是:
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的作用是什麼?
static函數的三個作用
- 函數內部static變量
- 函數外部static變量
- static函數
1、函數內部的static變量,關鍵在於生命週期持久,他的值不會隨着函數調用的結束而消失,下一次調用時,static變量的值,還保留着上次調用後的內容。
2/3、函數外部的static變量,以及static函數,關鍵在於私有性,它們只屬於當前文件,其它文件看不到他們。
函數外部static關鍵字
全局變量定義在函數體外部,在全局數據區分配存儲空間,且編譯器會自動對其初始化。
普通全局變量對整個工程可見,其他文件可以使用extern外部聲明後直接使用。也就是說其他文件不能再定義一個與其相同名字的變量了(否則編譯器會認爲它們是同一個變量)。
靜態全局變量僅對當前文件可見,其他文件不可訪問,其他文件可以定義與其同名的變量,兩者互不影響。
函數用static修飾
函數的使用方式與全局變量類似,在函數的返回類型前加上static,就是靜態函數。其特性如下:
靜態函數只能在聲明它的文件中可見,其他文件不能引用該函數
不同的文件可以使用相同名字的靜態函數,互不影響
非靜態函數可以在另一個文件中直接引用,甚至不必使用extern聲明
const修飾符
7、關鍵字const有什麼作用?
1)const修飾普通變量
const int a = 10;
int const a = 10;
這兩種寫法是等價的
用 const 修飾的變量,無論是全局變量還是局部變量,生存週期都是程序運行的整個過程。
2)const修飾指針變量
const int *p //p本身不是const的,而p指向的變量是const的
int const *p //p本身不是const的,而p指向的變量是const的
int * const p; //p 本身是 const 的,而 p 指向的變量不是 const 的
const int * const p; //p 本身是 const 的,而 p 指向的變量也是 const 的
volatile關鍵字
1). 並行設備的硬件寄存器(如:狀態寄存器)
2). 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
3). 多線程應用中被幾個任務共享的變量
short flag;
void test()
{
do1();
while(flag==1);
{
do2();
}
}
變量flag的值由別的程序更改,這個程序可能是某個硬件中斷服務程序。
產生中斷時,在中斷服務程序中改變flag的值,但是,編譯器並不知道flag的值會被別的程序修改,因爲在它進行優化的時候,可能會把flag的值先讀入某個寄存器,然後等待那個寄存器變爲1。如果不幸進行了這樣的優化,那麼while循環就變成了死循環,因爲寄存器的內容不可能被中斷服務程序修改。爲了讓程序每次都讀取真正flag變量的值,就需要定義爲如下形式:
volatile short flag;
一個參數既可以是const還可以是volatile嗎?解釋爲什麼?
答:可以。一個例子是隻讀的狀態寄存器。它是volatile 因爲它可能被意想不到地改變。它是const 因爲程序不應該試圖去修改它。
一個指針可以是volatile嗎?解釋爲什麼?
答:可以。儘管這並不常見。一個例子是當一箇中斷服務子程序修改一個指向buffer的指針時。
加了volatile之後, 直接從內存讀值,而不是從寄存器讀值。
Union與struct的區別。
1.在存儲多個成員信息時,編譯器會自動給struct每個成員分配存儲空間,struct 可以存儲多個成員信息,而Union每個成員會用同一個存儲空間,只能存儲最後一個成員的信息。
2.都是由多個不同的數據類型成員組成,但在任何同一時刻,Union只存放了一個被先選中的成員,而結構體的所有成員都存在。
3.對於Union的不同成員賦值,將會對其他成員重寫,原來成員的值就不存在了,而對於struct 的不同成員賦值 是互不影響的。
堆與棧的區別
Heap是堆,Stack是棧。
(1)棧的空間由操作系統自動分配和回收,而堆上的空間由程序員申請和釋放。
(2)棧的空間大小較小,而堆的空間較大。
(3)棧的地址空間往低地址方向生長,而堆向高地址方向生長。
(4)棧的存取效率更高。程序在編譯期間對變量和函數的內存分配都在棧上,
且程序運行過程中對函數調用中參數的內存分配也是在棧上。
位操作
1、位與 &與&& 邏輯與
位與時,兩個操作數是按照二進制位彼此對應位相位與的
邏輯與,兩個操作數爲整體來進行相與的
eg. 0xAA & 0xF0 = 0xA0
0xAA && 0xF0 = 1
2、位或 |與|| 邏輯或
“位或”與“邏輯或”的區別,同“位與”和“邏輯與”的區別
3、位異或 ^
位異或真值表:1^1= 0、0^0=0、1^0=1、0^1=1
位與、位或、位異或特點總結:
位與:與1位與無變化、與0位與變成0
位或:與1位或變成1、與0位或無變化
位異或:與1位或會取反、與0位或無變化
根據上面的三個特點:
特定位清零用&、特定位置1用|、特定位取反用^
回顧:要置 1 用|, 要清零用&,要取反用^, ~和<< >>用來構建特定二進制數。
1、給定一個整型數 a,設置 a 的 bit3,保證其他位不變。
a |= (1<<3)
2、給定一個整形數 a,設置 a 的 bit3~bit7,保持其他位不變。
a |= (0x1f<<3);
3、給定一個整型數 a,清除 a 的 bit15,保證其他位不變。
a = a & (~(1<<15)); 或者 a &= (~(1<<15));
4、給定一個整形數 a,清除 a 的 bit15~bit23,保持其他位不變。
a = a & (~(0x1ff<<15)); 或者 a &= (~(0x1ff<<15));
5、給定一個整形數 a,取出 a 的 bit3~bit8。
思路:
第一步:先將這個數 bit3~bit8 不變,其餘位全部清零。
第二步,再將其右移 3 位得到結果。
第三步,想明白了上面的 2 步算法,再將其轉爲 C 語言實現即可。
a &= (0x3f<<3);
a >>= 3;26
6、用 C 語言給一個寄存器的 bit7~bit17 賦值 937(其餘位不受影響)。
關鍵點:第一,不能影響其他位;第二,你並不知道原來 bit7~bit17 中裝的值。
思路:第一步,先將 bit7~bit17 全部清零,當然不能影響其他位。
第二步,再將 937 寫入 bit7~bit17 即可,當然不能影響其他位。
a &= ~(0x7ff<<7);
a |= (937<<7);
4.2.5.位運算實戰演練 2
7、用 C 語言將一個寄存器的 bit7~bit17 中的值加 17(其餘位不受影響)。
關鍵點:不知道原來的值是多少
思路:第一步,先讀出原來 bit7~bit17 的值
第二步,給這個值加 17
第三步,將 bit7~bit17 清零
第四步,將第二步算出來的值寫入 bit7~bit17
8、用 C 語言給一個寄存器的 bit7~bit17 賦值 937,同時給 bit21~bit25 賦值 17.
思路: 4.2.4.6 的升級版,兩倍的 4.2.4.6 中的代碼即可解決。
分析:這樣做也可以,但是效果不夠高,我們有更優的解法就是合兩步爲一步。
中斷(Interrupts)
中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴展—讓標準C支持中斷。具代表事實是,產生了一個新的關鍵字 __interrupt。下面的代碼就使用了__interrupt關鍵字去定義了一箇中斷服務子程序(ISR),請評論一下這段代碼。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
這個函數有太多的錯誤了,以至讓人不知從何說起了:
1)ISR 不能返回一個值。如果你不懂這個,那麼你不會被僱用的。
2) ISR 不能傳遞參數。如果你沒有看到這一點,你被僱用的機會等同第一項。
3) 在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。
4) 與第三點一脈相承,printf()經常有重入和性能上的問題。如果你丟掉了第三和第四點,我不會太爲難你的。不用說,如果你能得到後兩點,那麼你的被僱用前景越來越光明瞭。
代碼例子(Code examples)
下面的代碼輸出是什麼,爲什麼?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
這個問題測試你是否懂得C語言中的整數自動轉換原則,我發現有些開發者懂得極少這些東西。不管如何,這無符號整型問題的答案是輸出是 ">6"。原因是當表達式中存在有符號類型和無符號類型時所有的操作數都自動轉換爲無符號類型。因此-20變成了一個非常大的正整數,所以該表達式 計算出的結果大於6。這一點對於應當頻繁用到無符號數據類型的嵌入式系統來說是豐常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。
注意:當表達式中存在有符號類型和無符號類型時所有的操作數都將自動轉換爲無符號類型。
Typedef
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 兩個指針。
晦澀的語法
C語言同意一些令人震驚的結構,下面的結構是合法的嗎,如果是它做些什麼?
int a = 5, b = 7, c;
c = a+++b;
這個問題將做爲這個測驗的一個愉快的結尾。不管你相不相信,上面的例子是完全合乎語法的。問題是編譯器如何處理它?水平不高的編譯作者實際上會爭論這個問題,根據最處理原則,編譯器應當能處理儘可能所有合法的用法。因此,上面的代碼被處理成:
c = a++ + b;
因此, 這段代碼持行後a = 6, b = 7, c = 12。
如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個當作問題。我發現這個問題的最大好處是這是一個關於代碼編寫風格,代碼的可讀性,代碼的可修改性的好的話題。
大端模式與小端模式(big-endial & little endian)
小端模式:低位字節放在內存的低地址中;高位字節放在內存的高地址中
所以在小端模式下:0x12345678在內存中的存放位置爲:
0x80000000 78
0x80000001 56
0x80000002 34
0x80000003 12
大端模式與小端模式相反
container_of宏與offset_of宏
offset_of宏作用:計算結構體中每個成員的偏移量
// TYPE是結構體類型,MEMBER是結構體中一個元素的元素名
// 這個宏返回的是member元素相對於整個結構體變量的首地址的偏移量,類型是int
#define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)
container_of宏
// ptr是指向結構體元素member的指針,type是結構體類型,member是結構體中一個元素的元素名
// 這個宏返回的就是指向整個結構體變量的指針,類型是(type *)
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
bzero與memset
1、bzero()
函數原型:extern void bzero(void *s, int n)
頭文件:<string.h>
功能:置字節字符串s的前n個字節爲零且包括‘\0’
說明:無返回值
eg.
char a[10];
bzero(a, sizeof(char)*10)
memset(a, 0, sizeof(char)*10)
2、memset()
函數原型:extern void *memset(void *buffer, int c, int count)
頭文件:<string.h>
功能:把buffer所指內存區域的前count個字節設置成c的值。