# 面試考古 相關連結: [作業系統及考古](https://hackmd.io/Q7dkl9LpRpuvx3h83Liceg?both)、[計算機結構及考古](https://)、[linux kernel學習筆記](https://)、[STM32學習筆記](https://) ## C語言的0x10問題 ### 預處理器(Preprocessor) 前置處理器或稱預處理器,會在程式編譯開始前先行作用,其目的是為了使程式碼更簡潔、可讀性更佳,參考Jserv老師的[你所不知道的 C 語言:前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor#%E5%89%8D%E7%BD%AE%E8%99%95%E7%90%86%E5%99%A8%E6%98%AF%E5%BE%8C%E4%BE%86%E6%89%8D%E7%B4%8D%E5%85%A5-C-%E8%AA%9E%E8%A8%80%E7%9A%84%E7%89%B9%E5%BE%B5)中的一段話: > 原來 C preprocessor 以獨立程式的形式存在,所以當我們用 gcc 或 cl (Microsoft 開發工具裡頭的 C 編譯器) 編譯給定的 C 程式時,會呼叫 cpp (伴隨在 gcc 專案的 C preprocessor) 一類的程式,先行展開巨集 (macro) 或施加條件編譯等操作,再來 才會出動真正的 C 語言編譯器 (在 gcc 中叫做 cc1) 可以讓我們更加了解一些其中的原理,主要的C語言程式執行過程的流程圖如下:![image](https://hackmd.io/_uploads/Bkt05VXn0.png) 由上可看出`.c` 和 `header file`會先經過`preprocessor`展開巨集後再經由`compiler`生成`obj file`再透過`linker`把多個`obj file`打包成我們熟悉的可執行檔`(.exe)`但`GNU gcc`預設輸出檔名為`a.out`。 常見的一些預處理器有:`#define (macro)、#include`等並且預處理器有分為兩種形式: * `#` :[Stringification/Stringizing (字串化)](https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html):讓一個表示式變成字串,在 assert 巨集用到。 * `##`:[concatenation (連結,接續)](https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html) 有了上述一些基本概念我們開始實作第一個問題: #### 1. Using the #define statement, how would you declare a manifest constant that returns the number of seconds in a year? Disregard leap years in your answer. 使用 #define 定義一個常數來計算一年有幾秒? ```clike #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL ``` 透過上述巨集可釐清一些關於預處理器的一些基本概念: * (1) Basic knowledge of the #define syntax (i.e. no semi-colon at the end, the need to parenthesize etc.). >(1) 關於前置處理器語法上需注意的點(不能用分號約束,對括號的需求) * (2) A good choice of name, with capitalization and underscores. >(2)好的名子的選擇,有大寫以及底線(這樣命名較可與程式碼內建變數做區隔) * (3) An understanding that the pre-processor will evaluate constant expressions for you. Thus, it is clearer, and penalty free to spell out how you are calculating the number of seconds in a year, rather than actually doing the calculation yourself. > (3)了解到使用預處理去計算常數運算式。相較自己去運算一年有多少秒來說簡潔許多 * (4) A realization that the expression will overflow an integer argument on a 16 bit machine – hence the need for the L, telling the compiler to treat the expression as a Long. > (4) 透過上述可了解到對16位元的變數型態進行計算可能會造成溢位(Overflow)所以改使用Long型態的變數進行計算 * (5)As a bonus, if you modified the expression with a UL (indicating unsigned long), then you are off to a great start because you are showing that you are mindful of the perils of signed and unsigned types — and remember, first impressions count! > (5)當一個bonus,如果你更改運算式為UL(意指unsigned long),你會有一個好的開始,因為你注意到了signd與unsigned型態的危險(可能會造成位元表示型態不同)。 以下為其實作考量 使用 `WSL Ubuntu 22.04 LTS`僅參考他人實作方式 ```clike #include<stdio.h> #define SECONDS_PER_YEAR (60UL* 60UL* 24UL * 365UL) int main() { printf("%lu\n", SECONDS_PER_YEAR); return 0; } ``` 輸出結果為:`31536000` 若將LU這個型態改為用int這個型態去實作為: ```clike #include<stdio.h> #define SECONDS_PER_YEAR (60* 60* 24 * 365) int main() { printf("%lu\n", SECONDS_PER_YEAR); return 0; } ``` `gcc`會提醒你`print`的資料型態跟`#define`計算的資料型態不同: ```clike define.c: In function ‘main’: define.c:7:19: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘int’ [-Wformat=] 7 | printf("%lu\n", SECONDS_PER_YEAR); | ~~^ | | | long unsigned int | %u ``` 故我們將`%lu`改為原本內建計算的`%d`來進行計算可得正常輸出為:`31536000` #### 2.Write the ‘standard’ MIN macro. That is, a macro that takes two arguments and returns the smaller of the two arguments. 寫一個"標準的" MIN macro。也就是說,一個macro接收兩個參數並且回傳這兩個參數中較小的那個。 ```clike #define MIN(A,B) ((A) <= (B) ? (A) : (B)) ``` 透過上述巨集我們可釐清一些觀念: * (1)Basic knowledge of the #define directive as used in macros. This is important, because until the inline operator becomes part of standard C, macros are the only portable way of generating inline code. Inline code is often necessary in embedded systems in order to achieve the required performance level. > 在C99之前還沒有inline這個關鍵字可以將某些程式碼嵌入於程式本體在此時只有#define可以將程式碼內嵌至程式本體中,對於嵌入式系統來說內嵌程式碼提升運行速率及效能是必須的(因為嵌入式系統相較個人電腦其硬體資源很小) > inline code是使用同一種語言或是其他語言嵌入於程式碼本體使其效能提升 EX:使用Assembly嵌入在C中提升效能,但現代編譯器內建優化已經很好了,其提升效能沒這麼多。(可參考:RISC-V GUN tool Chain) * (2) Knowledge of the ternary conditional operator. This exists in C because it allows the compiler to potentially produce more optimal code than an if-then-else sequence. Given that performance is normally an issue in embedded systems, knowledge and use of this construct is important. > 三元運算子,這個在C語言中的存在因為它允許編譯器有潛力的製作比if-then-eles更佳的程式。因為嵌入式系統十分要求性能,使用三元運算子提升性能是非常重要的。 * (3) Understanding of the need to very carefully parenthesize arguments to macros. > 括號的影響對於巨集十分重要。 * (4) I also use this question to start a discussion on the side effects of macros, e.g. what happens when you write code such as : > 討論巨集的副作用 ```clike least = MIN(*p++, b); ``` 先參考本文章的標準答案: ```clike #include<stdio.h> #define MIN(A,B) ((A) <= (B) ? (A):(B)) int main(){ printf("%d\n", MIN(4,7)); } ``` 輸出為:`4` 若把括號移除為: ```clike #define MIN(A,B) (A) <= (B) ? (A):(B) ``` ```clike #define MIN(A,B) A <= B ? A:B ``` 其實並不影響輸出仍然為:`4` 但根據作者之提問`least = MIN(*p++, b);`有何影響? 在此參考他人文章,上述程式碼會被替換為`least=( (*p++) <= (b) ?(*p++):(b) )`,因此我參考他人程式碼進行實作: ```clike #include<stdio.h> #define MIN(A,B) ((A) <= (B) ? (A) : (B)) int main() { int a = 4; int *p = &a; printf("least value: %d\n", MIN(*p++, 7)); return 0; } ``` 連續執行五次發現每次指標指出的記憶體位址不同:![image](https://hackmd.io/_uploads/rJQ618mhC.png) 很像原本指向`p++`位址的指標指向到其他位置去然後再與`7`這個常數去比較?故在此修改原本測試用的程式碼,在此將`p`指向一個陣列,這個陣列為`4、5、6`這些元素所組成: ```clike #include<stdio.h> #define MIN(A,B) ((A) <= (B) ? (A) : (B)) int main() { int a[3] = {4, 5, 6}; int *p = a; printf("least value: %d\n", MIN(*p++, 7)); printf("current pos value of array:%d \n", *p); return 0; } ``` 然後輸出為: ``` least value: 5 current pos value of array:6 ``` 可以發現原本為指向4的位置的指標偏移成5來去與7這個數字進行比較,然後透過這行程式碼來去觀察` printf("current pos value of array:%d \n", *p);`目前執行位置,可發現偏移兩個位置,綜合上面的觀察,當我們使用: ```clike #define MIN(A,B) ((A) <= (B) ? (A) : (B)) least = MIN(*p++, b); least=( (*p++) <= (b) ?(*p++):(b) ) ``` 會替換成下方的程式碼執行造成我們程式執行結果與我們設計程式的預期不同,如果是使用變數而不是陣列的話,會產生不可預期的錯誤,因為我們並沒有對偏移一個整數與兩個整數位置的地方初始化。 #### 3.What is the purpose of the preprocessor directive #error? 預處理指令`#error`的目的是什麼? * `#error`就是生成編譯錯誤的訊息,然後會停止編譯,可以用在檢查程式是否是照自己所預想的執行。其語法格式為: ```clike #error error-message ``` 例如說我們可以可以我們可以在程式碼加些`#ifndef`,如果偵測到沒有被`define`,我們就可以出現使用`#error`訊息中止程式,參考他人實作: ```clike #include<stdio.h> int main() { #ifndef NUM1 #error No define NUM1 #endif printf("%d\n", NUM1); return 0; } ``` 可得輸出為: ```clike error.c: In function ‘main’: error.c:5:2: error: #error No define NUM1 5 | #error No define NUM1 | ^~~~~ error.c:7:24: error: ‘NUM1’ undeclared (first use in this function) 7 | printf("%d\n", NUM1); | ^~~~ error.c:7:24: note: each undeclared identifier is reported only once for each function it appears in ``` 我們可以透過`#error`確認`NUM1`並沒有被`define`並列印出來,接著我們把NUM1定義`#define NUM1 6`可得輸出為: ![image](https://hackmd.io/_uploads/ByFfr87nR.png) ### 無窮迴圈(Infinite loops) 無窮迴圈常見於韌體及嵌入式系統領域,故了解其性質及背後原因對於韌體工程師是非常重要的 #### 4. Infinite loops often arise in embedded systems. How does one code an infinite loop in C? 無窮迴圈在嵌入式系統是很常見的,如何使用`C`語言實作無窮迴圈? 有下列的實作方式: * 1. ```clike while(1) { … } ``` > 因為`while loo`p是檢查是否為`true or false`來執行迴圈,若寫成這樣,程式永遠為`true`會持續執行迴圈。 * 2. ```clike for(;;) { … } ``` >這寫法的`for loop`沒有初始化也沒有檢查條件也沒有更新其實相等於`while(1)`可以提及為`C`語言的老爸們`K&R`推薦的無窮迴圈的寫法。 * 3. ```clike … goto Loop; ``` > 剛學習`C`的時候常常被說`goto`不要常用,但其實此方法在`linux kernel`是很常用的,寫法跟`Assembly function call`是類似的。 #### 5.Using the variable a, write down definitions for the following: (a) An integer (b) A pointer to an integer (c) A pointer to a pointer to an integer (d) An array of ten integers (e) An array of ten pointers to integers (f) A pointer to an array of ten integers (g) A pointer to a function that takes an integer as an argument and returns an integer (h) An array of ten pointers to functions that take an integer argument and return an integer. Solution: ```clike (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 ``` 在此提及一下`function pointer`的基本語法為:`返回類型 (*指針名稱)(參數類型列表);`這代表它指向的為函數而不是單純記憶體位址,這是我一直困惑的地方或許未來要看一下`jserv`老師的C語言講座。 實作以下指標操作 `int *a[10]、int (*a)[10]、int (*a)(int)、int (*a[10])(int)`: * 1.`int *a[10]`在此簡化成四個指標的`int *a[4]`的陣列,另外宣告了b=20、c=30、d=40、e=50這五個變數讓我們將a[0]指向b,a[1]指向c,a[2]指向d,a[3]指向e。 ```clike #include<stdio.h> int main(){ int *a[4]; int b=20,c=30,d=40,e=50; a[0] = &b; a[1] = &c; a[2] = &d; a[3] = &e; printf("*a[0]:%d\n", *a[0]); printf("*a[1]:%d\n", *a[1]); printf("*a[2]:%d\n", *a[2]); printf("*a[3]:%d\n", *a[3]); return 0; } ``` 可看出輸出為: ```clike *a[0]:20 *a[1]:30 *a[2]:40 *a[3]:50 ``` 嘗試將每個指標+1觀察記憶體位址是否有聯動 ```clike *a[0]:20 *a[1]:30 *a[2]:40 *a[3]:50 *a[0]:21 and b is 21 *a[1]:31 and c is 31 *a[2]:41 and d is 41 *a[3]:51 and e is 51 ``` 觀察出指標及變數是有聯動的,確實有指向該變數的位址。 * 2. `int(*a)[10]` 簡化為 `int(*a)[3]`然後使其指向二維陣列`b[2][3]={{2,2,3},{3,5,6}}`;,並且讓`a`指到`b` ```clike #include<stdio.h> int main(){ int (*a)[3]; int b[2][3]={{2,2,3},{3,5,6}}; a=b; printf("%d\n", *(*a)); printf("%d\n", *(*a+1)); printf("%d\n", *(*a+2)); printf("%d\n", *(*(a+1))); printf("%d\n", *(*(a+1)+1)); printf("%d\n", *(*(a+1)+2)); return 0; } ``` 輸出為: ```clike 2 2 3 3 5 6 ``` * 3.`函式指標(function pointer)`:`int (*a)(int)、int (*a[10])(int)->int(*a[3])(int)` ```clike #include <stdio.h> int func(int num) { return num; }; int main(int argc, char* argv[]) { int (*a)(int) = func; printf("%d\n", a(10)); // use function pointer return 0; } ``` ```c #include <stdio.h> int b(int num) { return 2*num; } int c(int num) { return 3*num; } int d(int num) { return 4*num; } int main(int argc, char* argv[]) { int (*a[3])(int); a[0] = b; a[1] = c; a[2] = d; printf("%d\n", a[0](1));//也可以寫成(*a[0])(1) printf("%d\n", a[1](1)); printf("%d\n", a[2](1)); return 0; // a => &a function ptr } ``` #### 6. What are the uses of the keyword static? static有三種不同的用法:(好處) * 1. A variable declared static within the body of a function maintains its value between function invocations. >在函數區間內(in funtion block),一個被宣告為靜態的變數,在函數被呼叫的過程中其值維持不變 * 2.A variable declared static within a module [1], (but outside the body of a function) is accessible by all functions within that module. It is not accessible by functions within any other module. That is, it is a localized global. > 在一個Block(ie. {…} )內 (但在函數外),一個被宣告為靜態的變數可以被Block內所有的函數存取,但不能被其他Block中的函數存取。它是一個本地的全局變數。(local的global變數) * 3.Functions declared static within a module may only be called by other functions within that module. That is, the scope of the function is localized to the module within which it is declared. > 在一個block內宣告為static的函數只可以被其他同一個block內的其他函數呼叫。也就是說,這個函數的範圍對它所宣告在的block而言是區域性的。 ##### 另一種答案 * 1.function內做靜態變數宣告,在這一函數被呼叫的過程中其值維持不變 * 2.function外做靜態變數宣告或靜態函數宣告,代表此變數或此函數只能被同file的函數做存取 > 補充:在C語言中,static的三種作用: > * 1.隱藏功能,利用這一特性可以在不同的檔案中定義同名函式和同名變數,而不必擔心命名衝突 > * 2.保持變數內容的持久,儲存在靜態資料區的變數會在程式剛開始執行時就完成初始化,也是唯一的一次初始化。 > * 3.預設初始化為0,其實全域性變數也具備這一屬性,因為全域性變數也儲存在靜態資料區。在靜態資料區,記憶體中所有的位元組預設值都是0x00。 - [ ] 待補實作 #### 7.What does the keyword const mean? 當應試者回答說 ‘const就是常數’,我知道我會認為他們是業餘的。Dan Saks去年已經辛苦的概括const,因此每一個ESP(Embedded System Programming)的讀者應該要很熟悉 const 對你而言可以做什麼以及不能做什麼。`如果你還沒有讀到這個專欄,只要說 const 代表 “read-only” 就夠了。雖然這個答案並不是完全,但我接受它是一個正確的答案。(如果你想要知道更詳細的答案,那仔細的就去讀Saks的專欄)` ##### What do the following incomplete [2] declarations mean? ```clike const int a; int const a; const int *a; int * const a; int const * a const; ``` 含意:前兩個代表同一件事情,也就是說 a 是個 const (read-only) 整數。第三個代表 a 是個指向 const int 的指標(意即,整數無法修改,但是指標可以)。第四個宣告 a 是個指向整數的 const 指標(意即,被指到的整數可以修改,但是指標沒辦法)。最後一個宣告 a 是代表 const 指標指到 const integer(意即,被 a 指到的整數,或者是指標本身都無法修改)。 * 1.The use of const conveys some very useful information to someone reading your code. In effect, declaring a parameter const tells the user about its intended usage. If you spend a lot of time cleaning up the mess left by other people, then you’ll quickly learn to appreciate this extra piece of information. (Of course, programmers that use const, rarely leave a mess for others to clean up…) > 1. 使用 const 概括一些非常有用的資訊對那些正在讀你程式的人。實際上,宣告一個 parameter const 會告訴使用者關於它的預期使用。如果你付了很多時間在清理其他人留下的混亂,你將會更快的學到感謝這個多餘的訊息。(當然,程式設計師使用 const ,很少會留下混亂給其他人清理) * 2.const has the potential for generating tighter code by giving the optimizer some additional information. > Const有潛力產生更緊湊的程式碼藉由給予優化器一些附加資訊 * 3.Code that uses const liberally is inherently protected by the compiler against inadvertent coding constructs that result in parameters being changed that should not be. In short, they tend to have fewer bugs. > 使用 const 的程式碼自然的是固有的被編譯器保護來對抗不注意的程式結構導致不該改變的參數被改變。簡而言之,它們傾向擁有更少的bug - [ ] 待補實作 #### 8.What does the keyword volatile mean? Give three different examples of its use. 一個 volatile 變數會被不可預期的改變。因此,編譯器可以使這個變數沒有假設。具體來說,編譯器會小心的重載這個變數當這個變數每次被使用時,而不是保存一份拷貝在編譯器中。Volatile變數的範例如下: ( a ) 周邊設備的硬體暫存器,如狀態暫存器(`state Register`) ( b ) 中斷服務函式(ISR)中會訪問到的非自動變數(`Non-automatic variables`) ( c ) 多執行緒應用中,被多個任務共享的變數 > volatile變數代表其所儲存的內容會不定時地被改變,宣告volatile變數用來告訴編譯器 (Compiler) 不要對該變數做任何最佳化操作,凡牽涉讀取該volatile變數的操作,保證會到該變數的實體位址讀取,而不會讀取CPU暫存器的內容 (提升效能) 。舉個例子,某一硬體狀態暫存器 (Status Register)就必須宣告volatile關鍵字,因為該狀態暫存器會隨時改變,宣告volatile便可確保每次讀取的內容都是最新的。 ##### 進階問題 ( a )一個參數可以同時是const也是volatile嗎?解釋為什麼。 ( b )一個指標可以是volatile 嗎?解釋為什麼。 ( c )下面的函數有什麼錯誤︰ ```c int square(volatile int *ptr){ return *ptr * *ptr; } ``` 答案如下: ( a )是的,舉例說明像是"read only的狀態暫存器"。它是volatile,因為它可能會被非預期的改變;它是const,因為程式不應該試圖修改它。 ( b )是的,儘管這並不常見。一個例子是中斷服務函式修改一個指向buffer的指標時。 ( c )這段程式碼的目的是用來返指標***ptr指向值的平方**,但是,由於 *ptr指向一個volatile型參數,編譯器將產生類似下面的程式碼︰ ```c int square(volatile int *ptr) { int a,b; a = *ptr; b = *ptr; return a * b; } ``` 因為*ptr的值可能會被不預期的改變,因此a和b可能是不同的。所以,這段程式碼可能返回不是你所期望的平方值!正確寫法的程式碼如下︰ ```c int square(volatile int *ptr){ int a; a = *ptr; return a*a; } ``` * `Question`:如果沒有用`volatile keyword`用在重複執行的變數上會發生什麼事?對於哪種`instruction`會有影響? > Ans:在重複運用變數時,`complier`會將重複執行pop/push等操作的變數優化掉使得程式不符合預期行為,在`load/store instructions`會被其影響。 - [ ] 待補實作 ### 位元操作(Bit Manipulation) 在 C 中提供的位元運算子,分別是`AND`、`OR`、`NOT`、`XOR`與補數等`bitwise`或`bytewise`的操作。 #### 9.Embedded systems always require the user to manipulate bits in registers or variables. Given an integer variable a, write two code fragments. The first should set bit 3 of a. The second should clear bit 3 of a. In both cases, the remaining bits should be unmodified. 用 `#defines` 和 `bit masks` 操作。解決方法如下: ```clike //寫法一 #define BIT3(0x1<<3) static int a; void set_bit3(void){ a |= BIT3; } void clear_bit3(void){ a &= ~BIT3; } -------------- //寫法二 #define BIT3 (1U << 3) int main() { int a = 0; /* set bit 3 */ a |= BIT3; /* clear bit 3 */ a &= ~BIT3; } ``` 或使用巨集(macro)的寫法 ```c #define SET_BIT(p,n) ((p) |= (1 << (n))) #define CLEAR_BIT(p,n) ((p) &= ~(1 << (n))) #define FLIP_BIT(p,n) ((p) ^= (1 << (n))) #define CHECK_BIT(p,n) ((p) & (1 << (n)) ``` 重點是要看到明白的常數,以及使用 |= 和 &= ~結構。在韌體常用的位元操作通常都是bitwise。 - [ ] 待補實作 #### 10.Embedded systems are often characterized by requiring the programmer to access a specific memory location. On a certain project it is required to set an integer variable at the absolute address 0x67a9 to the value 0xaa55. The compiler is a pure ANSI compiler. Write code to accomplish this task. 嵌入式系統常有一個特點是要求程式設計失去存取特定的記憶體位置。在某個專案中被要求設定一個絕對位址在0x67a9的整數變數為數值0xaa55。編譯器是一個純ANSI編譯器。寫下程式碼來完成這個任務。 這個問題測試你是否知道為了存取一個絕對位置,去型別轉換一個整數成一個指標這是合法的。確切的語法根據每個人的風格因人而異。典型的程式碼如下: ```c int main(){ int *ptr = (int *)0x67a9; *ptr = 0xaa55; } ``` - [ ] 待補實作 #### 中斷(interrupt) 當CPU在執行程式時,遇到外部或內部的緊急事件須優先處理,因此暫停執行當前的程式,轉而服務突發的事件。直到服務完畢,再回到原先的暫停處(記憶體地址)繼續執行原本尚未完成的程式。 #### 11. Interrupts are an important part of embedded systems. Consequently, many compiler vendors offer an extension to standard C to support interrupts. Typically, this new key word is __interrupt. The following code uses __interrupt to define an interrupt service routine. Comment on the code. ```c __interrupt double compute_area(double radius) { double area = PI*radius*radius; printf("\nArea=%f", area); return area; } ``` 這個函數有太多錯誤了,問題如下: 1.ISR不能返回一個值 2.ISR不能傳遞參數 3.在許多編譯器/處理器中,浮點數操作是不可重入的(re-entrant)。有些處理器/編譯器需要讓多餘的暫存器入棧(PUSH入堆疊),有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。 4.與第三點類似,printf通常會有可重入和效能的問題。 - [ ] 待補實作 #### 12.What does the following code output and why? ```c void foo(void) { unsigned int a = 6; int b = -20; (a + b > 6) ? puts(">6"):puts("<=6"); } ``` 這個問題測試你是否懂得C語言中的整數自動轉型原則。 這題的答案會輸出“> 6”。因為當表達式中存在singed與unsinged型態的時候,所有的運算元都會自動轉換為無符號類型(unsigned)。 因此–20變成了一個非常大的正整數,並且這個表達式計算出的結果大於6。這是個在嵌入式系統非常重要的點,因為unsigned的資料型態應該會被頻繁的使用。 - [ ] 待補實作 #### 13.Comment on the following code fragment? ```c unsigned int zero = 0; unsigned int compzero = 0xFFFF; /* 1's complement of zero */ ``` 對於一個int型不是16位元的機器來說,它將會導致錯誤。 正確的程式碼如下: ```c unsigned int compzero = ~0; ``` 這個問題真的可以知道應試者是否了解字長在處理器的重要性。好的嵌入式程式設計師會清楚的知道硬體的細節與它的限制,然後電腦程式設計師傾向忽視硬體,並把它視為一個無法避免的煩惱。 - [ ] 待補實作 ### 動態記憶體配置(Dynamic Memory Allocation) #### 14.Although not as common as in non-embedded computers, embedded systems still do dynamically allocate memory from the heap. What are the problems with dynamic memory allocation in embedded systems? 會發生的問題像是記憶體碎片,碎片收集(垃圾回收)的問題,變量的生命週期(變數的執行時間)等等。 - [ ] 待補充 #### 15.Typedef is frequently used in C to declare synonyms for pre-existing data types. It is also possible to use the preprocessor to do something similar. For instance, consider the following code fragment: ```c #define dPS struct s * typedef struct s * tPS; ``` 以上兩種情況都是要定義dPS和tPS為一個指向結構s的指標。哪個方法比較好,並解釋為什麼? typedef更好。思考下面的例子: ```c dPS p1, p2; tPS p3, p4; ``` 第一個式子會被擴展成`struct s * p1, p2`; 上面程式碼定義p1為一個指向結構的指標,p2為一個實際的結構變數,這並不是我們原本想要的。 第二個式子正確定義了p3和p4兩個指標。 - [ ] 待補實作 #### 16.C allows some appalling constructs. Is this construct legal, and if so what does this code do? ```c int a = 5, b = 7, c; c = a+++b; ``` 根據“maximum munch”原則,編譯器應當能儘可能處理所有合法的用法。因此,上面的程式碼被處理成︰ `c = a++ + b;` 因此,在這個程式碼執行之後,`a = 6, b = 7 & c = 12;` (其實a++ 就是後做,先運算完之後再++) - [ ] 待補實作 ### 其餘常見考題 #### 1.struct 與 union之差異 兩者的差異主要在於記憶體空間的佔用,struct佔用的記憶體空間至少為成員的總和,union佔用的記憶體空間為所有成員裡佔用最大空間的資料型態的size (union變數裡面的成員會共用一個記憶體位址) struct: 自定義的一種型別, 可以包含多個不同型別的變數, 每個成員都會配置一塊空間 union: 跟struct有點像, 主要差別是裡面的成員共用一塊記憶體, 所需記憶體由型別最大的成員決定 enum: 可以用來定義常數, 主要是可以提升程式可讀性, 裡面的值從值指定的值開始遞增, 預設為0 >1.在儲存多個成員訊息時,編譯器會自動給struct的成員分配儲存空間,struct可以儲存多個成員訊息。而union每個成員會共用同一個儲存空間,只能儲存最後一個成員訊息。 2.都是由多個不同數據類型的成員組成,但在任何同一時刻,union只存放一個被先選中的成員,而struct的所有成員都存在。 3.對於union的不同成員賦值,將會對其他成員重寫,原來成員的值就不存在了;而對於struct的不同成員賦值,是互不影響的。 #### 2.Static函數的生命週期、使用時間點 靜態區域變數:生命週期貫穿整個運行期間,直到程式結束。 靜態全域變數:生命週期貫穿整個運行期間,直到程式結束。 補充:一般宣告的區域變數,都是自動變數,即隨著宣告區域決定生命週期的變數。 #### 3.extern和static的差異 1.extern:不同文件中想要互相使用的變量。當我們在某一個文件中定義了一個global variable,使用extern修飾變數即可在多個檔案中使用該變數。 2.static:包含同一個include檔的文件間想要互相使用的變量,但又不希望其他文件的操作改變本文件的變量。static 的意義就是 “被修飾的東西,會從程式一開始執行就存在,且不會因為離開 scope 就消失,會一直存在到程式結束”。 #### 4.lvalue & rvalue lvalue 定義為 "locator value",亦即 lvalue 是個物件的表示式 (an expression referring to an object),該物件的型態可以是一般的 object type 或 incomplete type,但不可為 void。換句話說,運算式的結果會是個有名稱的物件。 #### 5.解釋封裝,繼承,多型 1.封裝(Encapsulation)的概念就是在程式碼中設置權限,讓不同的物件之間有不同的存取限制,而不是把所有資料都攤在陽光下讓大家使用,「封裝」可防止程式的原始碼被竄改,保障了資料的隱密性,並提高了程式的穩定性和安全性,最常用的三種:public、private和protected。 2.繼承性(Inheritance)的概念很簡單,可用日常生活的比喻來理解。例如,兒子繼承了爸爸的家業(子類別會繼承父類別的屬性和方法),所以兒子會有父親已經做過的東西,而不必再重新做一次。而在程式語言中,繼承最大的好處是可以不必一再撰寫重複的程式碼,不只節省心力和時間,更重要的是可以提高程式的可讀性,增加程式的結構化程度,並讓維護和新增功能時更加容易方便、減少錯誤。 3.多型性(Polymorphism)的概念,又可分為多載(Overloading)和複寫(Overriding),以下分成兩個子項目來解說。 一、多載(overloading): 多載的概念簡單來說,就是相同名稱的方法(Method),藉由傳給它不同的參數(函數的輸入值),它就會執行不同的敘述,以產生不同的輸出。就像是同一台果汁機,丟進去蘿蔔就會輸出蘿蔔汁,丟進去蘋果就會輸出蘋果汁。而且,由於蘋果比較硬,所以這台優秀的果汁機會自動把刀片旋轉的力道和轉速調強一點。 以上比喻,用程式語言的術語表達就是:「同一個方法(Method)會依據它的參數值(輸入值)的「型態」、「數量」,甚至「順序」的不同,自動選擇對應的定義,執行不同的敘述,輸出不同的結果」。 二、上面計算矩形面積的例子已經示範過何謂「多載」,而複寫(Overriding)是指子類別對其父類別的方法(method)做改寫、並取而代之。複寫的觀念也很容易理解,打個比方,兒子繼承了父親的公司,並且對公司制度做了多項改革,例如行政流程全面電腦化、導入ERP系統管理庫存、汰換過時的設備、甚至於人事變動…等,這就是複寫。 ## Predict the output of below programs. (GeeksforGeek) ### 1. ```c #include<stdio.h> int main() { int n; for(n = 7; n!=0; n--) printf("n = %d", n--); getchar(); return 0; } ``` ::: spoiler 解答 上述輸出為:`infinite loop`因為在迴圈迭代時,`n--`不會等於0 ::: ### 2. ```c #include<stdio.h> int main() { printf("%x", -1<<1);// 2's complement -1 = 0xfffc for 16 bits machine getchar(); return 0; } ``` ::: spoiler 解答 `-1`為`1`的二補數在`16bits machine`中為` 0xfffc`,`left shift`後為`0xfffe`,`32bits machine`為 `0xfffffffe` ::: ### 3. ```c # include <stdio.h> # define scanf "%s Geeks For Geeks " main() { printf(scanf, scanf); getchar(); return 0; } ``` ::: spoiler 解答 `preprocessor`的展開,會直接印出 `%s Geeks For Geeks ` ::: ### 4-1. ```c #include <stdlib.h> #include <stdio.h> enum {false, true}; int main() { int i = 1; do { printf("%d\n", i); i++; if (i < 15) continue; } while (false); getchar(); return 0; } ``` ### 4-2. ```c #include <stdlib.h> #include <stdio.h> enum {false, true}; int main() { int i = 1; do { printf("%d\n", i); i++; if (i < 15) break; } while (true); getchar(); return 0; } ``` ::: spoiler 解答 1.`continue`會直接進入`do-while loop`的檢查條件中,但因為`while`永遠都是`false`導致迴圈不會繼續執行下去,故只會印出`1`。 2.`break`會直接導致跳出迴圈不會執行`do-while loop`的檢查條件,故也只會印出`1` ::: ### 5. ```c #include <stdio.h> char *getString() { char *str = "Nice test for strings"; return str; } int main() { printf("%s", getString()); getchar(); return 0; } ``` ::: spoiler 解答 `string constant`(字串字面常數)會被儲存於`data section`中的`Read-Only data section`中而不是儲存在`stack`中(函數呼叫完會自動釋放記憶體空間),所以`*str`這個指標可以正確指向記憶體位址得到預期的輸出。 ::: ### 6. ```c #include<stdio.h> char *getString() { char str[] = "Will I be printed?"; //str[] => *(str+0) 自動轉型成指標 return str; } int main() { printf("%s", getString()); getchar(); } ``` ::: spoiler 解答 `str`為陣列也是`getString`這個`function`的區域變數被儲存於`stack`裡,故當函式執行後`return`的`pointer` str因stack會自動釋放已執行完函式的記憶體空間會讓`str`成為`dangling pointer`,且在`printf`裡呼叫`dangling pointer`會導致`undefined behavior`。 `dangling pointer`: A pointer pointing to a memory location that has been deleted (or freed) is called a dangling pointer. Such a situation can lead to unexpected behavior in the program and also serve as a source of bugs in C programs. There are three different ways where a pointer acts as a dangling pointer: ::: ### 7. ```c #include<stdio.h> int main() { static int i=5; if(--i){ main(); printf("%d ",i); } } ``` ::: spoiler 解答 有初始化的`static`變數為放入`data section`裡,在函數進入至`main();`後開始遞迴呼叫每次呼叫都會建議一個專屬於當時遞迴的`stack frame`直至其遞迴條件結束,本程式碼總共會遞迴四次,當`if(--i)`在第四次遞迴會從`true->false`,進而執行`printf`然後從第四次遞迴依序印出輸出至第一次遞迴。 ::: ### 8. ```c #include<stdio.h> int main() { static int var = 5; printf("%d ",var--); if(var) main(); } ``` ::: spoiler 解答 `output`:54321,先初始化`static var`,然後先`print`之後再進入遞迴,直至`if statement`不成立。 ::: ### 9. ```c #include<stdio.h> int main() { int x; printf("%d",scanf("%d",&x)); /* Suppose that input value given for above scanf is 20 */ return 1; } ``` ::: spoiler 解答 `scanf`的`return value`不是輸入本身,而是它成功讀取並轉換的輸入項目數量且`printf("%d",scanf("%d",&x))`會印出`scanf`的`return value`而不是其輸入數值。 ::: ### 10. ```c # include <stdio.h> int main() { int i=0; for(i=0; i<20; i++) { switch(i) { case 0: i+=5; case 1: i+=2; case 5: i+=5; default: i+=4; break; } printf("%d ", i); } getchar(); return 0; } ``` ::: spoiler 解答 進入`switch`敘述時,因為沒有`break`所以會從`case0`一路走到`case5`,數值變化為:`5(0)->7(1)->12(5)->16(default)`,上述的原理為[casefall thorugh](https://ivan7645.github.io/2016/05/25/c-break/), 第二次迭代為`17->21`(因為沒有上述`case`直接執行`default`)。 ::: ### 11. ```c #include <stdio.h> int main() { printf("%p", main); getchar(); return 0; } ``` ::: spoiler 解答 在`C`語言中,函式名稱也是函式指示符會被`complier`認為是`pointer to function`,在`compiler`處理會隱性轉型為` address of pointer to function`(也就是函式本身的位址)。 [function designator](https://hackmd.io/@sysprog/c-pointer?type=view) ::: ### 12. ```c #include <stdio.h> int main() { printf("\new_c_question\by"); printf("\rgeeksforgeeks"); getchar(); return 0; } ``` ::: spoiler 解答 `/n、/r、\b`都是`printf`跳脫字元故第一個`output`為:`ew_c qusetiony`,第二個為:`geekforgeeks` ::: ### 13. ```c # include<stdio.h> # include<stdlib.h> void fun(int *a) { a = (int*)malloc(sizeof(int)); } int main() { int *p; fun(p); *p = 6; printf("%d\n",*p); getchar(); return(0); } ``` ::: spoiler 解答 上述程式碼不會有預期的`output`出來,在`C`中函式傳遞都是`call by value`也就是將原本變數的副本的值給予至被呼叫的函式中,當在`main`函式`dereference pointer`時其實只是改變指標指向的`address`位址,如果要達到更改`pointer`的位址應該使用`pointer to pointer`來去更改才能得到預期的結果,故上述執行結果在`dereference`時會有不如預期的結果指向的記憶體位址為未知使用`undefined behavior`且有可能會有`segmentation fault` 參考閱讀 [How can I allocate memory and return it (via a pointer-parameter) to the calling function?](https://stackoverflow.com/questions/1398307/how-can-i-allocate-memory-and-return-it-via-a-pointer-parameter-to-the-calling) ::: ### 14. ```c #include <stdio.h> int main() { int i; i = 1, 2, 3; printf("i = %d\n", i); getchar(); return 0; } ``` ::: spoiler 解答 輸出為:1,因為在`complie`過程中會被解釋成` (i = 1),2,3` ,這個是因為 assign operator 的運算順序大於 comma operator,故會有上面的輸出。 ::: ### 15. ```c #include <stdio.h> int main() { int first = 50, second = 60, third; third = first /* Will this comment work? */ + second; printf("%d /* And this? */ \n", third); getchar(); return 0; } ``` ::: spoiler 解答 `110`,`/**/`會被`complier`略過不去進行編譯。 ::: ### 16. ```c #include‹stdio.h› int main() { struct site { char name[] = "GeeksforGeeks"; int no_of_pages = 200; }; struct site *ptr; printf("%d",ptr->no_of_pages); printf("%s",ptr->name); getchar(); return 0; } ``` ::: spoiler 解答 `struct`是一種資料型別宣告,代表它是創建一個新的資料型別而不能在此進行初始化,進行資料型別宣告時並進行初始化會導致`complier`報錯,初始化應該是要在定義結構變數後再進行初始化,可以採用逐個初始化或是用列表初始化。 ::: ### 17. ```c int main() { char a[2][3][3] = {'g','e','e','k','s','f','o', 'r','g','e','e','k','s'}; printf("%s ", **a); getchar(); return 0; } ``` ::: spoiler 解答 三維陣列有`18`個`element`的位址,上述程式碼初始化了其中`13`個,在`char`型別尚未被初始化的位址裡面會初始化成為`/0`,`**a`為指向陣列起始位址的指標。 ::: ### 18. ```c int main() { char str[]= "geeks\nforgeeks"; char *ptr1, *ptr2; ptr1 = &str[3]; ptr2 = str + 5; printf("%c", ++*str - --*ptr1 + *ptr2 + 2); printf("%s", str); getchar(); return 0; } ``` ::: spoiler 解答 `ptr1`指向陣列第四個元素的位址,`ptr2`指向陣列第六個元素的位址,在這邊主要是要注意`printf("%c", ++*str - --*ptr1 + *ptr2 + 2);`,上述的`pointer arithmetic`都是透過`ASCII encode`去進行運算故` ++*str`為`g->k(g+1=k)`,` --*ptr1`為 `k->j(k-1=j)`、` *ptr2` 為 `s`、 `++*str - --*ptr1 + *ptr2 + 2` 為 `s(ASCII:115)`故第一個`printf`會將字串更改為`heejs\ngeeks`第二個`printf`會印出`heejs\ngeeks`。 ::: ### 19. ```c #include <stdio.h> int fun(int n) { int i, j, sum = 0; for(i = 1;i<=n;i++) for(j=i;j<=i;j++) sum=sum+j; return(sum); } int main() { printf("%d", fun(15)); getchar(); return 0; } ``` ::: spoiler 解答 單純就是迴圈條件判斷和函式的理解,`fun`這個函式會有下列結果`n(n+1)/2`上述輸出為`120` ::: ### 20. ```c #include <stdio.h> int main() { int c = 5, no = 1000; do { no /= c; } while(c--); printf ("%d\n", no); return 0; } ``` ::: spoiler 解答 上述程式碼會這樣執行`do-while loop`會先做再檢查`while`的成立條件, ```c (1000/5 = 200)->(200/4=50)->(50/3 = 16(round off))->(16/2 = 8)->(8/1 = 8) ->(8/0 -> complier error) ``` 因為有除以`0`所以`compiler`會報錯 ::: ### 21. ```c int main() { while(1){ if(printf("%d",printf("%d"))) break; else continue; } return 0; } ``` ::: spoiler 解答 答案無法被預測,當 printf 函數使用格式化字串,但缺少對應的參數時,為`undefined behavior`這種情況下`printf`會在`register`或者`stack`隨便找一個值代入,外層`printf`獲取內層`printf`的返回值並打印出來,然後進入`break`讓看似無窮迴圈的地方變成只打印一次。 `print`f 函數返回的是 成功印出的字元數量。 即使印出的是垃圾值,printf 仍然會返回印出的字元數量,只要沒有發生錯誤。 ::: ### 22. ```c int main() { unsigned int i=10; while(i-- >= 0) printf("%u ",i); return 0; } ``` ::: spoiler 解答 在上述程式碼中會被執行打印為`9->8->7->6->5->4->3->2->1->0(while(0)仍成立)->(-1)(原本是)`但因為`unsigned int`沒有負數會導致`underflow`至`unsigned int`的最大值開始打印。 ::: ### 23. ```c int main() { int x, y = 2, z, a; if (x = y % 2) z = 2; a = 2; printf("%d %d ", z, x); return 0; } ``` ::: spoiler 解答 在程式執行過程中未被初始化的變數會被自動初始化為0,所以 `x、z、a` 一開始會初始化為 `0`,後續`branch`判斷也不成立所以`printf`打印出值為`0 0`。 ::: ### 24. ```c int main() { int a[10]; printf("%d",*a+1-*a+3); return 0; } ``` ::: spoiler 解答 因為`dereference`的運算符相較`+-`來說優先順序更高,所以應該會被更改成 `(*a)+1 -(*a)+3`且`*a`會被隱性轉型成指向陣列第一個元素的`ptr`但因為不知道陣列第一個元素為何?故用`x`來代替上述運算式會被轉換成`(*a)+1 -(*a)+3->x+1-x+3->4`,所以輸出為`4` ::: ### 25. ```c #define prod(a,b) a*b int main() { int x=3,y=4; printf("%d",prod(x+2,y-1)); return 0; } ``` ::: spoiler 解答 預期程式的結果會是`5 * 3 = 15`,但因為巨集會直接展開成`3+2*4-1=10`,這是巨集的副作用,如果要讓程式輸出符合預期應該會在marco上寫成 `#define prod(a,b) (a)* (b)` 以避免上述不符合預期的結果。 ::: ### 26. ```c int main() { unsigned int i=65000; while ( i++ != 0 ); printf("%d",i); return 0; } ``` ::: spoiler 解答 迴圈進入點為 `i = 65000;while loop` 判斷會一直`increment`然後直到 `while overflow `至 `0`時`while loop`會跳出並且` i` 會 `increment` 為 `1 ` ::: ### 27. ```c int main() { int i=0; while ( +(+i--) != 0) i-=i++; printf("%d",i); return 0; } ``` ::: spoiler 解答 while loop 中的判斷式可以視為 `+(+(i--)) != 0` ::: ### 28. ```c int main() { float f=5,g=10; enum{i=10,j=20,k=50}; printf("%d\n",++k); printf("%f\n",f<<2); printf("%lf\n",f%g); printf("%lf\n",fmod(f,g)); return 0; } ``` ::: spoiler 解答 第一個錯誤是因為在第一個`printf`中嘗試更改`k`的值,`k`是`enum`,其實就類似整數常數,其值不容許更改, 第二個錯誤為`f<<2`,因為`f`為`floating number`,在`c`語言中`<<`運算子限用於`int`型別表示的`number`,故不符合語法設定,第三個錯誤為在`c`語言中`%`僅使用於int但`f g`為`float`故不符合語法設定 ::: ### 29. ```c int main() { int i=10; void pascal f(int,int,int); f(i++, i++, i++); printf(" %d",i); return 0; } void pascal f(integer :i,integer:j,integer :k) { write(i,j,k); } ``` ::: spoiler 解答 在`c`語言中沒辦法使用`pascal`語言的語法及函數,因為可能現代`c`語言編譯器不支援此功能 ::: ### 30. ```c void pascal f(int i,int j,int k) { printf("%d %d %d",i, j, k); } void cdecl f(int i,int j,int k) { printf("%d %d %d",i, j, k); } main() { int i=10; f(i++,i++,i++); printf(" %d\n",i); i=10; f(i++,i++,i++); printf(" %d",i); } ``` ::: spoiler 解答 標準`c`語言並無`pascal`和`cdecl`兩個函數,並且兩個函數為分別為由左至右遞增及有右至左遞增,兩個函式對於相同變數進行重複操作且沒有明確的順序點導致結果不重合,這會導致未定義行為,且函數於定義時`c`語言不允同名同參數的`overloading`。 ::: ### 31. ```c int main() { int i = 0; while (i <= 4) { printf("%d", i); if (i > 3) goto inside_foo; i++; } getchar(); return 0; } void foo() { inside_foo: printf("PP"); } ``` ::: spoiler 解答 呼叫的`inside_foo`這個`label`在這段程式碼並main函式中沒有定義,因為`label`的`scope`於函式中,故不能在其他函式中呼叫`inside_foo`這個`label` ::: ### 32. ```c #define a 10 int main() { #define a 50 printf("%d",a); getchar(); return 0; } ``` ```c #define a 10 int main() { printf("%d ",a); #define a 50 printf("%d ",a); getchar(); return 0; } ``` ::: spoiler 解答 在兩段程式碼第一段程式碼,僅會印出`50`,但第二段會印出`10、50`。這是因為重新定義`preprocessor`時編譯器不會發出警告且會以最新數值做為基礎進行後續操作 ::: ### 33. ```c int main() { char str[] = "geeksforgeeks"; char *s1 = str, *s2 = str; int i; for(i = 0; i < 7; i++) { printf(" %c ", *str); ++s1; } for(i = 0; i < 6; i++) { printf(" %c ", *s2); ++s2; } getchar(); return 0; } ``` ::: spoiler 解答 在上述程式碼兩種字元指標皆指向字元陣列`str`的第一個元素也就是`g`,在第一個`loop`中,`printf`函式會`dereference str`這個陣列取得其記憶體位址,但後續片段為`++s1`指`s1`在`increment`,故迴圈結束仍是指向`str`第一個元素會印出 `ggggggg`, 第二個迴圈d`erefernece s2`並且`increment by s2`,所以會依序印出 `geeksf`。 ::: ### 34. ```c int main() { char str[] = "geeksforgeeks"; int i; for(i=0; str[i]; i++) printf("\n%c%c%c%c", str[i], *(str+i), *(i+str), i[str]); getchar(); return 0; } ``` ::: spoiler 解答 上述其實都是 `*(arr+i)`的一種形式 對於c語言中`arr[i]`其實就是`*(arr+i)`的語法糖故會印出四次`gggg eeee eeee`以此類推至印出整個陣列。 ::: ### 35. ```c int main() { char *p; printf("%d %d ", sizeof(*p), sizeof(p)); getchar(); return 0; } ``` ::: spoiler 解答 計算指標`*p`為 `1 (char pointer)`,計算指標變數`p`在常見編譯器實作為 `4 or 8`。 ::: ### 36. ```c #include<stdio.h> int main() { int x = 5, p = 10; printf("%*d", x, p); getchar(); return 0; } ``` ::: spoiler 解答 `%*d`為`printf`函式標準輸出的字元,會規定打印字串最小寬度以這裡為例為5故其值會是 `10` (`10`只有兩個字元剩餘為空白字元) ::: ### 37. ```c int main() { char arr[] = "geeksforgeeks"; char *ptr = arr; while(*ptr != '\0') ++*ptr++; printf("%s %s", arr, ptr); getchar(); return 0; } ``` ::: spoiler 解答 在`c`語言中`postfix(left to right)`優先順序大於`prefix`和`dereference(right to left)`故其程式碼行為會是 `ptr++->*(ptr++)->++(*(ptr)++)`故行為是`ASCII`值加`1`並指向下一個字元直至`terminal character` ::: ### 38. ```c int main() { signed char i=0; for(; i >= 0; i++); printf("%d\n", i); getchar(); return 0; } ``` ::: spoiler 解答 因為是`signed int`故其範圍會是在`-128~127`以`4 byte`為編譯器實作定義的話,故迴圈結束後印出值為`-128` ::: ### 39. ```c #include <stdio.h> void fun(const char **p) { } int main(int argc, char **argv) { fun(argv); getchar(); return 0; } ``` ```c #include <stdio.h> void fun(const char **p) { } int main(int argc, char **argv) { const char **temp fun(argv); getchar(); return 0; } ``` ::: spoiler 解答 函式傳遞參數時,`fun`需要的參數為`const char`但在`main`函式中傳遞值為`char`會造成`compiler error`,改為下列程式碼即可符合預期結果。 ::: ### 40. ```c int main() { int c=5; printf("%d\n%d\n%d", c, c <<= 2, c >>= 2); getchar(); } ``` ## Basic C concept ### Q: Memory allocation ```c int global_var = 0; //global初始化區(data) int *global_ptr; //global未初始化區(bss) int main() { int local_var; //stack char str[] = "1234"; //stack char *p1; //stack static int static_var = 0; //global初始化區(data) p1 = (char *)malloc(sizeof(char *)); //heap return 0; } ``` ### Q: Compare Compile error, linker error, run time error | Feature | Explain | | -------- | -------- | | `Complie error` | 通常是syntax錯誤 | | `linker error` | 找不到外部include的function或是程式碼 | | `run time error` | 常見為錯誤使用指標導致OS或程式被kill掉 例如:Segmentation falut | ### Q: Explain define, const, volatile, inline, extern | Feature | Explain | | -------- | ---------------------------------------------------------------------------------------------- | | define | 巨集的一種預編譯會直接展開不會做型別檢查,因為是展開所以要注意一些side effect | | const | Read-only變數,通常在編譯階段使用 | | volatile | 告知complier不要對其進行最佳化,通常使用在變數進行重複執行記憶體讀寫的操作常用於ISR和 Register | | inline | 可以拿來修飾function, 主要用來加速執行速度, 參數類型會檢查, inline後編譯器不一定會實作 | | extern | 可以引用外部的全域變數 | ### Q: What is the output #### define ```c #define A 2+3 #define B (2+3) printf("%d\n", A*A); //2+3*2+3=11 printf("%d\n", B*B); //(2+3)*(2+3)=25 ``` #### static v.s local ```c /* Call this function 10 times, a=?, b=? */ void fun() { static int a = 0; int b = 0; a++; b++; printf("%d\n", a); //10 printf("%d\n", b); //1 } ``` #### const ```c const int c = -5; c++; //Compile error printf("%d\n", c); ``` #### switch ```c switch(2) { case 0: printf("0\n"); //won't print break; case 1: printf("1\n"); //won't print case 2: printf("2\n"); //will print case 3: printf("3\n"); //will print } ``` ### linux #### Q: 以下哪個Linux command可以列出當前目錄中的檔案? (A) free (B) top \(C) cd (D) ls ```c free: 查看記憶體 top: 效能分析工具, 類似工作管理員 cd: 切換目錄 ls: 列出當前目錄的檔案 ``` #### Q: 請說明tar的功能 Linux常用的壓縮或解壓縮command 參數有很多種組合, 都是用來壓縮或解壓縮不同的壓縮格式 #### Compare user space(mode) and kernel space(mode) ![image](https://hackmd.io/_uploads/rJ8L1jRa1e.png) sudo 還是在 user space #### Q: 用chmod更改file.txt的權限為 -rwxrwxrwx- ```shell $ chmod 770 file.txt or $ chmod ug+rwx file.txt ``` #### Q: 在linux中, kernel space如何傳遞資料給user space ```c 有寫過kernel module的話應該會大概知道怎麼做 主要就是copy_to_user和copy_from_user這兩個function ``` > https://www.jollen.org/blog/2006/12/linux_device_driver_io_3.html #### Q: spin lock, mutex, semaphore在linux的差異 ``` mutex同一時間只能讓一個process進入critical section, 有鑰匙的人可以進去 semaphore允許多個process進入critical section, 滿了就要等process出來才能進去, 沒有鑰匙 spin lock為busy waiting的機制, 比較適合在需要保護某一小段data時, 因為等待時間較長 ``` ## Pointer - `*` : pointer, dereference (解指標) - `&` : address-of - `**`: pointer to pointer(指標的指標) - `call-by-value`: caller and callee各自有自己的記憶體, C的function call都屬於這種 - `call-by-reference`: caller and callee使用相同的記憶體, C++才有 ### Basic ```c int a = 5; int *p; // assign an pointer to int p = &a; // assign the address of variable "a" to pointer p printf("%d\n", *p); //dereferenec p:5 printf("%p\n",&a); //0xXXXXX printf("%p\n", p) // same as address of variable a ``` ```c int a[] = {1,2,3,4,5}; int *p1 = a ; // pointer p will point to the address of head of a char c[] = "54321"; int *p2 = c; /* array of pointer to int */ printf("%d\n", *p1); //a[0]=1 printf("%d\n", *p1+1);//a[0]+1=2 printf("%d\n", *p1++);//1, and the pointer move to next element printf("%d\n", *++p1); // 3, the pointer moves first then dereference printf("%d\n", ++*p1); // 4,dereference then plus one /* array of pointer to char*/ printf("%c\n",*p2); c[0] = 5; printf("%c\n",*(p2+1)); c[0+1] = 4; printf("%zu\n",sizeof(c)); // 6 byte, 5+1(end char) ``` ```c *p++ = *(p++); *++p = *(++p); ++*p = ++(*p); ``` ### Function Pointer 很少遇到, qsort函數的callback參數就有需要 ```c // function define void fun(char *str, int len); // funtion pointer define void(*fun_ptr)(char *, int )= &fun ``` ### Advanced usage #### Q: 給一句話, 把那句話表達出來 ```c // 1. A ptr to a ptr to an integer // 2. an array of 10 integer // 3. An array of 10 ptr to integer // 4. A ptr to an array of 10 integer // 5. A ptr to a function that take an integer as an argument and return an integer // 6. An array of the pointer to function that take an integer argument and return an integer int **p; int a[10]; int *p[10]; int (*p)[10]; int (*p)(int); int (*p[10])(int); ``` #### Q: What is the output (64-bits machine) ```c int main(void) { char *a[] = {"abc","def","ghijk","lmnop"} char *b = a[2]; printf("%d\n", sizeof(a)); // 8 * 4 =32 printf("%d\n", sizeof(b)); // 8 printf("%d\n",sizeof(a[2])); // 8 printf("%d\n",sizeof(b[2])); //1 } ``` #### Q: Write two functions that are able to access a fixed memory location ```c /* 64-bits machine */ uint64_t *read_mem(uint64_t addr) { uint64_t *p = addr; return *p; } void write_mem(uint64_t addr, uint64_t data) { uint64_t *p = addr; *p = data; } ``` ### Struct,union,enum ``` struct: 自定義的一種型別, 可以包含多個不同型別的變數, 每個成員都會配置一塊空間 union: 跟struct有點像, 主要差別是裡面的成員共用一塊記憶體, 所需記憶體由型別最大的成員決定 enum: 可以用來定義常數, 主要是可以提升程式可讀性, 裡面的值從值指定的值開始遞增, 預設為0 ``` ```c /* struct */ struct GPIO { char name[12]; unsigned char direction; unsigned int value; struct GPIO *gpio_ptr; //struct不能含有自己, 但可以有自己的pointer }; int main(void) { /* normal */ /* struct GPIO g = { "GPIO_IO1", 0, 1, NULL }; */ /* designated initializers(recommend) */ struct GPIO g = { .name = "GPIO_IO1", .direction = 0, .value = 1, .gpio_ptr = NULL }; printf("%s\n", g.name); //GPIO_IO1 printf("%d\n", g.direction); //0 printf("%d\n", g.value); //1 printf("%p\n", g.gpio_ptr); //nil return 0; } ``` ```c /* union */ union data { unsigned char c; //1 byte int val; //4 bytes long int li; //8 bytes }; int main(void) { union data d; d.c = 255; printf("%d\n", d.c); //255 return 0; } ``` ```c /* enum */ enum GPIO_NUM { GPIO_IO0 = 0, GPIO_IO1, GPIO_IO2, GPIO_IO3, GPIO_IO4 }; int main(void) { enum GPIO_NUM G = GPIO_IO3; printf("%d\n", G); //3 return 0; } ``` **struct pointer** ```c typedef struct _GPIO { char *name; //8 bytes unsigned char direction; //1 byte unsigned int value; //4 byte } GPIO; int main(void) { GPIO *ptr = (GPIO *)malloc(sizeof(GPIO)); ptr->name = "GPIO_IO20"; ptr->direction = 0; ptr->value = 1; printf("%s\n", ptr->name); //GPIO_IO20 printf("%d\n", ptr->direction); //0 printf("%d\n", ptr->value); //1 return 0; } ``` ```c typedef struct _GPIO1 { char *name; //8 bytes unsigned char direction; //1 byte unsigned int value; //4 byte } GPIO1; typedef struct _GPIO2 { unsigned char direction; //1 byte char *name; //8 bytes unsigned int value; //4 byte } GPIO2; int main(void) { printf("%lu\n", sizeof(GPIO1)); //16 //因為struct會被編譯器拿去做對齊最佳化, //所以"direction"和"value"這兩個變數會被合在一起然後補滿(padding)8 bytes printf("%lu\n", sizeof(GPIO2)); //24 //"direction"會補滿8 bytes, "value"也會補滿8 bytes, 所以加起來是24 bytes return 0; } ``` #### union and CPU endian 一般CPU都是Little-Endian, 也就是lowest bytes會放在最低的記憶體位子上 Big-Endian則相反 ```c union data { unsigned char c; //1 byte unsigned int val; //4 bytes }; int main(void) { union data d; d.val = 0x12345678; printf("0x%x\n", d.c); //Little-Endial: 0x78 d.c = 256; printf("%d\n", d.c); //0 return 0; } ``` **判斷程式碼** ```c #include <stdio.h> typedef union { unsigned long l; unsigned char c[4]; } EndianTest; int main() { EndianTest et; et.l = 0x12345678; printf("本系統位元組順序為:"); if (et.c[0] == 0x78 && et.c[1] == 0x56 && et.c[2] == 0x34 && et.c[3] == 0x12) { printf("Little Endiann"); } else if (et.c[0] == 0x12 && et.c[1] == 0x34 && et.c[2] == 0x56 && et.c[3] == 0x78) { printf("Big Endiann"); } else { printf("Unknown Endiann"); } printf("0x%lX 在記憶體中的儲存順序:n", et.l); for (int i = 0; i < 4; i++) { printf("%p : 0x%02Xn", &et.c[i], et.c[i]); } return 0; } ``` **網路位元組順序** ```c #include <stdio.h> #include <stdint.h> #include <arpa/inet.h> typedef union { uint32_t l; unsigned char c[4]; } EndianTest; // 輸出位元組順序 void printBytes(uint32_t x) { EndianTest et; et.l = x; for (int i = 0; i < 4; i++) { printf("0x%02X ", et.c[i]); } printf("n"); } int main() { uint32_t x = 0x12345678; printf("0x%X 在記憶體中的儲存順序:", x); printBytes(x); uint32_t n = htonl(x); printf("0x%X 在網路中的傳輸順序:", x); printBytes(n); } 0x12345678 在記憶體中的儲存順 ``` #### Q: sizeof(struct data)? (64-bits machine) ```c struct __attribute__((__packed__)) data { char c; //1 short s1; //2 short s2; //2 int h; //4 }; printf("%lu\n", sizeof(struct data)); //9 ``` ### bitwise operator **Q: What is the output** ```c unsigned int x = 0xf; x << = 4; x |= 0x03; printf("0x%x",x) // 0xf3 ``` **Q: Complete the program** ```c void write_reg(unsinged int *x, unsigned int loc,unsigned char en) { if(loc<0) return; if(en == '0'){ *x &= ~(1<<loc); //clear bit }else if(en == '1'){ *x |=(1<<loc) // set bit }else if(en == '2'){ *x ^= (1<<loc); // toggle bit }else{ printf("intput error\n"); } } ``` **Q: Write a program to check is power of 2** ```c int is_pow_2(unsigned int x){ return (x&-x) == x; } ``` **Q: Write a program to calculate number of 1** ```c int number_of_one(unsigned int x){ int c = 0; while(x != 0){ x = x &(x-1); c++; } return c; } ``` **Q: Find the MSB and LSB location** ```c void MSB(unsinged int x){ unsigned int test = 0x80000000; int count = 31; if(x<=0){ return; } while((x & test)>>count !=1){ count --; test>>=1; } printf("MSB bit is at %d\n",count); } ``` ```c void LSB(unsigned int x){ unsigned int test = 0x1; int count = 0; while((x & test)<<count == 0){ test <<=1; count++; } printf("LSB is at %d\n",count); } ``` **Write a program to check the hex is equal** ```c int is_hex_equal(unsigned short input) { unsigned short hex[4]={0}; hex[0] = (input & 0xF000)>>12; hex[1] = (input & 0x0F00)>>8; hex[2] = (input & 0x00F0)>>4; hex[3] = (input & 0x000F); if(hex[0]==hex[1]&&hex[0]==hex[2]&&hex[0]==hex[3]){ return 1; }else{ return 0; } } ``` ## Programming ### Basic **Q: Write a program to print fibonacci number** ```c /* recursive */ int fib_recursive(int n){ if(n<=1) return n; return fib_recursive(n-2)+fib_recursive(n-1); } /* Dp */ int fib_dp(int n) { if (n <= 1) return n; int dp[n + 1]; dp[0] = 0; dp[1] = 1; for (int i = 2; i <= n; i++) dp[i] = dp[i - 1] + dp[i - 2]; return dp[n]; } ``` **Q: Write a swap function** ```c // normal void swap(int *a, int *b){ int tmp = 0; tmp = *a; *a = b; *b = tmp; } // bitwise void swap_bitwise(int *a, int *b){ *a = (*a)^(*b); *b = (*a)^(*b); *a = (*a)^(*b); } ``` **PrintStar** ```c void print_star(int n) { for(int i =1;i<=n;i++){ for(int j = 0;j<i;j++){ printf("*"); } printf("\n"); } } ``` **calculate GCD** ```c // recursive int gcd_recursive(int a,int b){ if(b == 0) return a; return gcd(b,a%b); } // while - int gcd(int a,int b){ while(a != b){ if(a>b) a -=b; if(b>a) b -= a; } return a; } // while % int gcd(int a, int b) { while (b != 0) { int temp = a % b; a = b; b = temp; } return a; } // find lcm int lcm(int a,int b){ return a*b /gcd(a,b); } ``` **Find 1~100 prime** ```c void find_prime(int n){ int flag = 1; int prime = 0; for(int i = 2;i<=n;i++){ prime = i; flag = 1; for(int j = 2;j<i;j++){ if(i%j==0){ flag = 0; break; } } if(flag) printf("%d\n", prime); } } ``` **roundoff program** ```c int roundoff(float x){ return (int)(x+0.5) } ``` **reversestring** ```c void swap(char *c1,char *c2){ *c1 = (*c1)^(*c2); *c2 = (*c1)^(*c2); *c1 = (*c1)^(*c2); } char *reverse_str(char *str){ const int len = strlen(str); for(int i = 0;i<len/2;i++){ swap(&str[i],&str[len-1-i]); } return str; } ``` **Complete strcpy、strlen、strcmp、strcat** ```c // strcpy void strcpy(char *dest,const char *src){ if(dest == NULL|| src == NULL) return; while(*src !='\0'){ *dest = *src; dest++; src++; } } //strcat void strcat(char *dest, const char *src) { if (dest == NULL || src == NULL) return; // 找到 dest 結尾 while (*dest != '\0') dest++; // 複製 src while (*src != '\0') { *dest = *src; dest++; src++; } // 加上結尾 '\0' *dest = '\0'; } // strlen void strlen(const char *str){ int len = 0; while(*str++ !='\0') len++; return len; } //strcmp int strcmp(const char *s1, const char *s2) { while (*s1 && (*s1 == *s2)) { s1++; s2++; } return (unsigned char)(*s1) - (unsigned char)(*s2); } ``` **binary search** ```c void binary_search(int *arr,int len,int target){ int right = len-1,left = 0,mid=0; while(right>=left){ mid=(right+left)/2; if(arr[mid]<target){ left = mid +1; }else if(arr[mid]>target){ right = mid - 1; }else if(arr[mid]== target){ printf("Found it\n"); return; } } printf("Not found it\n"); } ``` **Marco** ```c //max #define MAX(a,b) ((a)>(b))?(a):(b) // array element #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) // setbit #define set_bit(b,n) ((b) |= (1<<(n))) // clearbit #define clear_bit(b,n) ((b) &= ~(1<<(n))) // togglebit #define toggle_bit(b,n) ((b) ^= (1<<(n))) ``` **大小寫轉換** ```c char convert(char c){ if(c>='A'&&c<='Z') return c+32; else if(c>='a'&&c<='z') return c-32 else return 0; } ``` **輸入三個數找出最大和最小值** ```c struct ret_val{ int max; int min; } struct ret_val find(int a,int b,int c){ struct ret_val ret; ret.max = 0; ret.min = 0; if(a>b){ if(a>c) {ret.max =a;} else{ ret.max = c; } }else{ if(b>c){ ret.max = b; }else{ ret.max = c; } } if(a<b){ if(a<c){ ret.min= a; }else{ ret.min = c; } }else{ if(b<c){ ret.min = b; }else{ ret.min = c; } } return ret; } ``` ### Sorting ### linkedlist **Append node** 把node加在list最後面 最簡單實作的方法為traverse整個list然後再加上去, 如果list是空的就直接加上去 ```c void append(struct listNode **head,int data){ struct listNode *new_node = NULL, *curr = *head; new_node = (struct listNode *)malloc(sizeof(struct listNode)); if(new_node == NULL) return; new_node -> next = NULL; new_node -> data = data; if(curr == NULL){ *head = new_node; return ; } while(curr->next) curr = curr->next; curr->next = new_node; } ``` **Push node** node加在最前面 ```c void push(struct listNode **head,int data){ struct listNode *new_node = NULL; new_node = (struct listNode *)malloc(sizeof(struct listNode)); if(new_node == NULL) return; new_node->next = *head; new_node->data = data; *head = new_node; } ``` **print list** ```c void print_list(struct listNode *head){ struct listNode *ptr = head; while(ptr){ printf("%d->",ptr->data); ptr = ptr->next; } printf("%d\n",ptr->data); } ``` **Delete Node** ```c void delete_node_pos(listNode **head, int pos){ struct ListNode *prev = NULL; struct ListNode *curr = *head; int counter = 0; if(pos<0) return; // Not allow -1,-2 while(cur && counter != pos){ prev = curr; curr = curr->next; counter++; } if(curr==NULL){ printf("Node doesn't exits\n") }else if(*head == curr){ *head = curr->next; free(curr); curr = NULL; }else{ prev->next = curr->next; free(curr); curr = NULL; } } ``` **Insert node after a give postion** ```c void insert_node_after(struct listNode **head,int pos,int data){ struct listNode *new_node = NULL; struct listNode *curr = *head; int i = 0; new_node = (struct listNode *)malloc(sizeof(struct listNode)); if(new_node == NULL) return; new_node->next = NULL; new_node->data = data; if(*head == NULL || pos <0)return; while(curr->next && pos !=i){ curr = curr->next; i++; } new_node->next = curr->next; curr->next = new_node; } ``` ## Reference [C Multiple Choice Questions](https://www.geeksforgeeks.org/c-multiple-choice-questions/)