# 一些我夢到的常見面試問題
這邊紀錄一些我夢到或是想到的面試常見問題
如果對題目或是解答有問題, 或是想改東西加東西, 歡迎email給我
email: benson40111@gmail.com
先放參考連結
>[你所不知道的C語言:指標篇](https://hackmd.io/@sysprog/c-pointer)
>[C/C++ - 常見 C 語言觀念題目總整理(適合考試和面試)](https://mropengate.blogspot.com/2017/08/cc-c.html)
>[考古題相關](https://hackmd.io/@jQSlhiN3QaGkugpbD4NDAA/rkvNkyQfi)
>[面試整理](/KlnPOwLlS6uQ_uYK-uCtMQ)
>[發哥(聯發科)上機考題目整理](@Rance/SkSJL_5gX)
>[C語言 : 超好懂的指標,初學者請進~](https://kopu.chat/c%e8%aa%9e%e8%a8%80-%e8%b6%85%e5%a5%bd%e6%87%82%e7%9a%84%e6%8c%87%e6%a8%99%ef%bc%8c%e5%88%9d%e5%ad%b8%e8%80%85%e8%ab%8b%e9%80%b2%ef%bd%9e/)
>[ISO/IEC 9899](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf)
## Content
- Basic concepts
- Data structure
- Basic concepts of C language
- Linux
- Pointer
- Basic usage
- Function pointer
- Advanced usage
- struct, union and enum
- bitwise operator
- Programming
- Basic
- Sort
- Linked list
- MCU
- GPIO
- ADC
- I2C
- SPI
- undefined behavior (TODO)
- Network (TODO)
## Basic concepts
### Data structure
#### Q: Compare stack, queue, heap
>[Stack vs. Heap](https://medium.com/joe-tsai/stack-vs-heap-b4bd500667cd)
>[Stack vs Heap Memory – Difference Between Them](https://www.guru99.com/stack-vs-heap.html)
| Features | Explain |
|:-------- |:--------------------------------------------------------------------------------------- |
| stack | **LIFO** (Last In First Out) 大部分情況用來存local variable的地方, 可以用來實作四則運算 |
| queue | **FIFO** (First In First Out) stack的相反, 有些CPU的排班會利用queue |
| heap | 二元樹的一種, 特性是root一定是最大或最小, 父節點必定大於子節點 |
---
#### Q: Compare sort alogorithm
直接畫表格比較快
這邊只討論平均
| Algorithms | Time complexity | Space complexity | Stability |
|:-------------- |:--------------- |:-------------------- |:--------- |
| Selection sort | $O(n^2)$ | $O(1)$ | unstable |
| Insertion sort | $O(n^2)$ | $O(1)$ | stable |
| Bubble sort | $O(n^2)$ | $O(1)$ | stable |
| Quick sort | $O(n\log n)$ | $O(\log n)$ ~ $O(n)$ | unstable |
---
#### Q: Binary Search Tree (BST) Traversal
一般都是給BST然後要你traverse三或四種方法
網路上很多, 懶得寫出來了
>[基礎演算法系列 — Tree 樹狀資料結構](https://medium.com/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/%E5%9F%BA%E7%A4%8E%E6%BC%94%E7%AE%97%E6%B3%95%E7%B3%BB%E5%88%97-tree-%E6%A8%B9%E7%8B%80%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B-d10fe8ac1ce2)
---
### Basic concepts of C language
#### Q: Memory allocation
給一段C的程式片段, 問變數配置在哪些地方
>寫得很好, 建議看完
>[C 語言程式的記憶體配置概念教學](https://blog.gtwang.org/programming/memory-layout-of-c-program/)
```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
比較三種差異, 比較常在寫直譯語言的話可能會比較不熟悉
建議用個簡單的程式然後用gcc分段跑一次會比較熟
>[Compiler error vs linker error?](https://stackoverflow.com/questions/14947050/compiler-error-vs-linker-error)
| Features | Explain |
|:-------------- |:--------------------------------------------------------- |
| Compile error | 通常都是語法錯誤,warning不算錯誤, 不過也是要注意 |
| linker error | 找不到某些include進來的東西或是function |
| run time error | 最常見就是指標亂指導致OS把程式殺掉 ex: Segmentation fault |
---
#### Q: Explain define, const, volatile, inline, extern
解釋這四種關鍵字在幹嘛
比較需要注意的是volatile,inline,extern
| Features | Explain |
|:-------- |:------------------------------------------------------------------------------------------- |
| define | 巨集的一種, 預編譯時會展開且**不會做型別檢查**, 因為是展開, 所以要注意一些陷阱題 |
| const | read-only變數, 一般用來宣告常數, 編譯階段使用 |
| volatile | 告知編譯器不要對它做最佳化, 每次都從記憶體撈值, 而不是從暫存器. 常用於ISR或是一些硬體暫存器 |
| inline | 可以拿來修飾function, 主要用來加速執行速度, 參數類型會檢查, inline後編譯器不一定會實作 |
| extern | 可以引用外部的全域變數 |
---
#### Q: data types
基本觀念, 建議背一些常用的就好
以下是常見情況, 占用多少memory還是要看機器而定
| Data Type | Bytes |
|:----------------- |:----- |
| short | 2 |
| long | 8 |
| float | 4 |
| double | 8 |
| **int** | 4 |
| **char** | 1 |
| unsigned | 4 |
| **unsigned int** | 4 |
| **unsigned char** | 1 |
| **pointer** | 8 |
---
#### 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
>[Words Commonly Mispronounced by Chinese Programmers](https://github.com/shimohq/chinese-programmer-wrong-pronunciation)
Linux真正的唸法參考連結內有, 裡面還有很多其他常見單字的唸法
Linux相關的題目大部份都會出現在考卷上, 一般都是問command可以幹嘛
基本command不列在這邊, 這邊列出我工作遇過或夢到的相關題目
#### Q: 以下哪個Linux command可以列出當前目錄中的檔案?
(A) free
(B) top
\(C\) cd
(D) ls
```
free: 查看記憶體
top: 效能分析工具, 類似工作管理員
cd: 切換目錄
ls: 列出當前目錄的檔案
```
---
#### Q: 請說明tar的功能
Linux常用的壓縮或解壓縮command
參數有很多種組合, 都是用來壓縮或解壓縮不同的壓縮格式
>[ GNU / Linux 各種壓縮與解壓縮指令](http://note.drx.tw/2008/04/command.html)
---
#### Q: Compare user space(mode) and kernel space(mode)
作業系統(OS)的基本觀念
基本上Linux系統很多設計上的東西跟恐龍書差不多

>[圖片來源](https://www.redhat.com/rhdc/managed-files/2015/07/user-space-vs-kernel-space-simple-user-space.png)
***注意 sudo還是run在user mode***
---
#### Q: 用chmod更改file.txt的權限為 -rwxrwx\-\-\-
要先懂文件權限在幹嘛
>[Linux chmod命令](https://www.runoob.com/linux/linux-comm-chmod.html)
然後我們就可以開始作答了
```bash
$ chmod 770 file.txt
or
$ chmod ug+rwx file.txt
```
---
#### Q: 如何將程式放置到後台執行
任何的command加上 ```&```就好了, 雖然我不確定是不是所有常用command都可以這樣
```bash
$ ./test.sh &
```
#### Q: 承上題, 進入後台執行後的程式如何回到前台執行
```bash
$ fg
```
如果有超過一個以上的後台執行, 可以先用```jobs```確認編號, 然後加上編號就可以指定要把哪個後台程式放回前台
---
#### Q: 在linux中, kernel space如何傳遞資料給user space
>[Linux 驅動程式的 I/O, #3: kernel-space 與 user-space 的「I/O」](https://www.jollen.org/blog/2006/12/linux_device_driver_io_3.html)
>[開發 driver 需要的基礎知識](https://www.cntofu.com/book/46/linux_device_driver_programming/05.md)
有寫過kernel module的話應該會大概知道怎麼做
主要就是copy_to_user和copy_from_user這兩個function
---
#### Q: spin lock, mutex, semaphore在linux的差異
>[Mutex v/s Semaphore v/s Spinlock](https://medium.com/freethreads/mutex-v-s-semaphore-v-s-spinlock-98c6884356b9)
>[淺談同步機制](https://rickhw.github.io/2020/09/12/ComputerScience/Synchronization/)
```
mutex同一時間只能讓一個process進入critical section, 有鑰匙的人可以進去
semaphore允許多個process進入critical section, 滿了就要等process出來才能進去, 沒有鑰匙
spin lock為busy waiting的機制, 比較適合在需要保護某一小段data時, 因為等待時間較長
```
---
## Pointer
應該算是很多人都不熟的東西了(包括我), 所以需要花點時間念
>[c語言有沒有call by reference(or call by address)?](http://eportfolio.lib.ksu.edu.tw/~T093000170/blog?node=000000119)
- ```*``` : pointer, dereference (解指標)
- ```&``` : address-of
- ```**```: pointer to pointer(指標的指標)
- ```call-by-value```: caller and callee各自有自己的記憶體, C的function call都屬於這種
- ```call-by-reference```: caller and callee使用相同的記憶體, C++才有
---
### Basic usage
一些基本的用法
```C=
int a = 5;
int *p; //assign a pointer to int
p = &a; //assign the address of variable "a" to pointer p
printf("%d\n", *p); //dereference 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 moves 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("%lu\n", sizeof(c)); //6 bytes, 5 + 1(end char)
```
```C=
*p++ = *(p++)
*++p = *(++p)
++*p = ++(*p)
```
---
### Function pointer
很少遇到, qsort函數的callback參數就有需要
```C=
/* function define */
void fun(char *str, int len);
/* function pointer define */
void (*fun_ptr)(char *, int) = &fun;
```
---
### Advanced usage
我認為比較進階的題目
#### Q: 給一句話, 把那句話表達出來
```C=
/* define */
//1. A pointer to a pointer to an integer
//2. An array of 10 integers
//3. An array of 10 pointers to integers
//4. A pointer to an array of 10 integers
//5. A pointer to a function that takes an integer as an argument and returns an integer
//6. An array of ten pointers to functions 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
return 0;
}
```
---
#### Q: Write two functions that are able to access a fixed memory location
>[Interview Guide for Embedded C Programmers](https://binaryupdates.com/interview-guide-for-embedded-c-programmers/2/)
寫兩個function, 一個可以read memory一個可以write memory
答案很多種, 主要是要看pointer操作懂不懂
```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 and enum
算是C/C++的必考題之一, 實務上也很常用到
```
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;
}
```
structure pointer
Linked-list很常用到
```C=
typedef struct _GPIO {
char *name; //8 bytes
unsigned char direction; //1 byte
unsigned int value; //4 byte
} GPIO;
int main(void)
{
GPIO *ptr = (struct GPIO *)malloc(sizeof(struct 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;
}
```
struct size計算
寫得滿仔細的
>[C 語言:關於 sizeof 及結構及同位的記憶體對齊](https://magicjackting.pixnet.net/blog/post/221968938)
```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則相反
>[Big-Endian 與 Little-Endian 的差異與判斷程式碼](https://blog.gtwang.org/programming/difference-between-big-endian-and-little-endian-implementation-in-c/)
>[2018q3 Homework3 (rewiew)](https://hackmd.io/@cheese/r1OKM-yo7?type=view)
```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;
}
```
---
#### Q: sizeof(struct data)? (64-bits machine)
>[【C++ Primer】 神秘的 sizeof(union) 、sizeof(struct) 和内存对齐技术](https://blog.csdn.net/tianshuai1111/article/details/7576279)
```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
基本上都是考一個數字, 經過一些運算後輸出是什麼
set bit, clear bit建議背起來, firmware必考題
>[[C 語言] Bitwise operation note](https://m033010041.github.io/2020/02/28/bitwise-operation-in-C/)
>[awesome-bits](https://github.com/keon/awesome-bits)
#### Q: What is the output
```C=
unsigned int x = 0xf;
x <<= 4;
x |= 0x03;
printf("0x%x\n", x); //0xf3
```
---
#### Q: Complete the program
```C=
/*
* en is '0': clear bit, en is '1': set bit, en is '2': inverse bit
* loc is bit location
*/
void write_register(unsigned int *x, unsigned int loc, unsigned char en)
{
//Complete the code below
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); //inverse bit
} else {
printf("input error\n");
}
}
```
---
#### Q: Write a program to check is power of 2
如果不給用if來判斷的話, 可以用bitwise來判斷
如果是2的次方的話, 在2進位中只會有1個1
```C=
/* 0: is not power of 2, 1: is power of 2 */
int is_pow_2(unsigned int x)
{
return (x & -x) == x;
}
```
---
#### Q: Write a program to calculate number of 1
數一個值轉二進位後有幾個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 highest_bit(unsigned int x)
{
unsigned int test = 0x80000000;
int count = 31;
if (x <= 0) {
return;
}
while ((x & test) >> count != 1) {
count--;
test >>= 1;
}
printf("highest bit is at %d\n", count);
}
```
另一種寫法, 如果型別比較大的話就不太適合這樣寫
```C=
int highest_bit(unsigned char x)
{
int n = 7;
if (x == 0) return -1;
if ((x >> 4) == 0) {
x = x << 4;
n -= 4;
}
if ((x >> 6) == 0) {
x = x << 2;
n -= 2;
}
if ((x >> 7) == 0) {
n -= 1;
}
return n;
}
```
找最低位元的位置
```C=
void lowest_bit(unsigned int x)
{
unsigned int test = 0x1;
int count = 0;
while ((x & test) << count == 0) {
test <<= 1;
count++;
}
printf("lowest bit is at %d\n", count);
}
```
---
#### Q: Write a program to check the hex is equal
看16進位數字有沒有都一樣
ex: 0xAAAA 有, 0xAABB 沒有
```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
這邊就放程式題目了, 難度頂多到leetcode的medium
如果前面懶得看, 也可以只看這邊就好
先從基礎開始吧
### Basic
#### Q: Write a program to print fibonacci number
基本題目, 一般用遞迴解, 因為比較直覺
不過這邊建議用DP(Dynamic Programming)
```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)
{
int dp[1001] = {0};
dp[0] = 0;
dp[1] = 1;
if (n <= 1) return dp[0];
if (n == 2) return dp[1];
for (int i = 2; i < n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n-1] + dp[n-2];
}
```
---
#### Q: Write a swap function
交換兩個數值
有兩種寫法, 看題目有沒有限制
```C=
/* normal */
void swap(int *a, int *b)
{
int tmp = 0;
tmp = *a;
*a = *b;
*b = tmp;
}
/* without tmp */
void swap(int *a, int *b)
{
*a = (*a) ^ (*b);
*b = (*a) ^ (*b);
*a = (*a) ^ (*b);
}
```
---
#### Q: Print stars
\*
\**
\***
\****
標準印星星題目
```C=
void print_step()
{
for (int i = 1; i <= 4; i++) {
for (int j = 0; j < i; j++) {
printf("*");
}
printf("\n");
}
}
```
---
#### Q: Calculate the GCD
計算最大公因數, 可以用遞迴或是迴圈來解
概念就是輾轉相除法
順便也把最小公倍數放這邊
```C=
/*
* recursive
* Notice: b must > a
*/
int gcd_recursive(int a, int b)
{
if (b == 0) return a;
return gcd(b, a%b);
}
/* loop */
int gcd(int a, int b)
{
while(a != b) {
if (a > b) a -= b;
if (b > a) b -= a;
}
return a;
}
/* Least Common Multiple */
int lcm(int a, int b)
{
return a * b / gcd(a, b);
}
```
---
#### Q: 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);
}
}
```
---
#### Q: Write a program can round off
寫一個簡單的四捨五入程式, input只會到小數點第一位, 所以要進位到個位數
ex: 5.5=>6, 5.4=>5
一般都會限制不給用if-else, 所以要善用C的型別轉換
```C=
int round(float x)
{
return (int)(x + 0.5);
}
```
---
#### Q: Write a program to reverse string(char array)
手刻反轉字串
```C=
void swap(char *c1, char *c2)
{
char tmp = ' ';
tmp = *c1;
*c1 = *c2;
*c2 = tmp;
}
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;
}
```
---
#### Q: Complete strcpy, strcat, strlen, strcmp
手刻這幾個程式, 算挺常見的題目
```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)
{
char *p = dest;
int loc = 0;
if (dest == NULL || src == NULL) return;
while(*(p++) != '\0') loc++;
while(*src != '\0') {
dest[loc] = *src;
src++;
loc++;
}
}
/* strlen */
int strlen(const char *str)
{
int len = 0;
while (*str++ != '\0') len++;
return len;
}
/* strcmp */
int strcmp(const char *s1, const char *s2)
{
char *ptr = s1 + strlen(s1);
while (*s2 != '\0') {
*ptr++ = *s2++;
}
*ptr = '\0';
return s1;
}
```
---
#### Q: Complete 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\n");
}
```
---
#### Q: Macro
寫一個define可以回傳兩個數字中的最大值
寫一個define可以回傳陣列長度
set bit, clear bit and toggle bit use macro
兩題都是考條件運算式(conditional expressions)和巨集而已
需要注意的是要記得把變數刮起來
計算陣列這題一般而言這樣寫就可以交卷了
不過因為巨集不做型別檢查, 所以在Linux kernel裡面會多一個檢查的機制
Linux kernel定義路徑: include/linux/kernel.h
>[Linux Kernel: ARRAY_SIZE()](http://frankchang0125.blogspot.com/2012/10/linux-kernel-arraysize.html)
```C=
/* MAX */
#define MAX(a, b) ((a)>(b))?(a):(b)
/* numbers of array element */
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
/* set bit */
#define set_bit(b, n) ((b) |= ((n)<<1))
/* clear bit */
#define clear_bit(b, n) ((b) &= ~((n)<<1))
/* toggle bit */
#define toggle_bit(b, n) ((b) ^= ((n)<<1))
```
---
#### Q: 大小寫轉換
大寫轉小寫, 小寫轉大寫
大寫'A'在ascii是65
小寫'a'在ascii是97
兩個差32, 所以也就是說大寫加32就會變小寫, 反之
```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;
}
```
---
#### Q: 輸入3個數, 找出最大和最小值
```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;
}
```
---
### Sort
算是常見題目了, 有些是填空題, 有些是直接手寫
>[【Day23】[演算法]-插入排序法Insertion Sort](https://ithelp.ithome.com.tw/articles/10277360)
>[【Day22】[演算法]-選擇排序法Selection Sort](https://ithelp.ithome.com.tw/articles/10276719)
>[[演算法] 快速排序法 (Quick Sort)](https://ithelp.ithome.com.tw/articles/10202330)
#### Bubble sort
初學者會接觸到的第一個排序法
概念就是兩兩互換, 最大或最小的值讓它跑到最右邊
```C=
void bubble_sort(int *arr, int len)
{
int tmp = 0;
for (int i = 0; i < len; i++) {
for (int j = i; j < len; j++) {
if (arr[j] < arr[i]) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}//for
}//
```
---
#### Insertion sort
```C=
void insertion_sort(int *arr, int len)
{
int key = 0, j = 0;
for (int i = 1; i < len; i++) {
key = arr[i];
j = i - 1;
while (j >= 0 && key < arr[j]) {
arr[j+1] = arr[j];
j -= 1;
}
arr[j+1] = key;
}
}
```
#### Selection sort
```C=
void selection_sort(int *arr, int len)
{
int min = 0, tmp = 0;
for (int i = 0; i < len; i++) {
min = i;
for (int j = i + 1; j < len; j++) {
if (arr[min] > arr[j])
min = j;
}
/* 如果本身就是最小, 多這個if可以避免自己跟自己交換 */
if (i != min) {
tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}//for
}//
```
---
#### Quick sort
可以選擇直接背起來比較快
把第1個當成pivot
```C=
void quick_sort(int *arr, int left, int right)
{
int i = left, j = right, p = arr[left], tmp = 0;
if (left >= right) return;
while (i != j) {
while (p < arr[j] && i < j) j--;
while (p >= arr[i] && i < j) i++;
if (i < j) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
arr[left] = arr[i];
arr[i] = p;
quick_sort(arr, left, i-1);
quick_sort(arr, i+1, right);
}
```
把最後一個當成pivot, 要多一個partition
```C=
int partition(int *arr, int left, int right)
{
int p = arr[right], i = left - 1, tmp = 0;
for (int j = left; j < right; j++) {
if (arr[j] <= p) {
i++;
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
tmp = arr[i+1];
arr[i+1] = arr[right];
arr[right] = tmp;
return i + 1;
}
void quick_sort(int *arr, int left, int right)
{
int mid = 0;
if (right > left) {
mid = partition(arr, left, right);
quick_sort(arr, left, mid-1);
quick_sort(arr, mid+1, right);
}
}
```
---
### Linked-list
必考題, 不管怎樣一定要加減念一下
考卷都是考題組或是考程式填空, 當然也有要重頭寫的
不管是面試軟體還是韌體都算是一定要準備的題目
#### Data structure
基本結構
Linux kernel是採用雙向連結
>[Linux内核链表——看这一篇文章就够了](https://zhuanlan.zhihu.com/p/546794612)
>[linux/tools/include/linux/types.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/types.h#L84)
```C
struct list_head {
struct list_head *next, *prev;
};
```
會發現list_head裡面沒有data相關的member
這種結構稱為Intrusive Linked List
這樣做的好處是可以處理泛型
但是一般面試都是考單向鏈結, 所以寫底下這個就夠了
```C=
struct listNode {
int data;
struct listNode *next;
};
```
---
#### Append node
把node加在list最後面
簡單的做法就是走完一整個list, 然後加上去
如果list是空的, 就直接加上去
>[Insertion in Linked List](https://www.geeksforgeeks.org/insertion-in-linked-list/)
底下的寫法是不回傳的方法, 所以傳入head的時候需要用pointer to pointer來確保資料有在function裡面更動
>[Linux Kernel 中的数据结构——泛型 LinkedList](https://studymakesmehappy.club/posts/linux-kernel-%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%B3%9B%E5%9E%8B-linkedlist/)
```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
也算是最基本的題目了, 就是把所有node走過一次, 不過如果node有cycle的話就需要額外處理
底下這段是假設沒有cycle
```C=+
void print_list(struct listNode *head) {
struct listNode *ptr = head;
while (ptr->next) {
printf("%d->", ptr->data);
ptr = ptr->next;
}
printf("%d\n", ptr->data);
}
```
---
#### Delete node
刪除給定的節點, 我這邊是實作給一個數值, 然後刪除該數值
這樣寫會導致只能刪除遇到的第一個, 如果後面有重複的會刪不到
所以又寫了一個刪除指定位置的
>[Linked List: 新增資料、刪除資料、反轉](http://alrightchiu.github.io/SecondRound/linked-list-xin-zeng-zi-liao-shan-chu-zi-liao-fan-zhuan.html)
```C=+
/* delete node by value */
void delete_node_value(struct listNode **head, int data) {
struct listNode *prev = NULL;
struct listNode *curr = *head;
while (curr && curr->data != data) {
prev = curr;
curr = curr->next;
}
if (curr == NULL) {
printf("Node doesn't exits\n");
} else if (*head == curr) {
prev = curr;
free(curr);
curr = NULL;
} else {
prev->next = curr->next;
free(curr);
curr = NULL;
}
}
/* delete node by position */
void delete_node_position(listNode **head, int pos)
{
struct ListNode *prev = NULL;
struct ListNode *curr = *head;
int i = 0;
if (pos < 0) return; //Not allowed -1,-2...
while (curr && i != pos) {
prev = curr;
curr = curr->next;
i++;
}
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;
}
}
```
>[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)
>indirect pointer操作, 有點複雜, 但是很值得一看
---
#### Insert node after a given position
插入node在給定的position之後
沒特別檢查輸入進來的pos, 所以如果輸入進來的pos大於list長度, 那就會直接接在最後一個後面
```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;
}
```
---
#### Q: Remove Duplicates from Sorted List (leetcode 083)
>[題目網址](https://leetcode.com/problems/remove-duplicates-from-sorted-list/)
>給一個list, 移除重複的
>input: 1->1->2->3, output: 1->2->3
概念大概就是反正他是排序過的list, 所以重複的一定連在一起
所以就是把指標挪到下下一個, 跳過去就好
然後大多數linked-list的題目第一步通常都是把head空的情況先回傳回去
```C=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* deleteDuplicates(struct ListNode* head){
struct ListNode *curr = head;
if (head == NULL) return head;
while (curr) {
if (curr->next != NULL && curr->next->val == curr->val) {
curr->next = curr->next->next;
} else {
curr = curr->next;
}
}
return head;
}
```
---
#### Q: Linked List Cycle (leetcode 141)
>[題目網址](https://leetcode.com/problems/linked-list-cycle/)
>給一個list, 回傳是否有cycle
>有的話回傳true, 沒有回傳false
>pos是cycle開始的位置
>input: 3->2->0->-4, pos = 1, output: 1
用[快慢法](https://zh.wikipedia.org/zh-tw/Floyd%E5%88%A4%E5%9C%88%E7%AE%97%E6%B3%95)來解,
用兩個pointer, 一個跑比較快, 一個跑比較慢, 當相遇時就代表有cycle
```C=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode *slow = head, *fast = head;
int cycle_loc = 0;
if (head == NULL) return false;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
return true;
}
cycle_loc++;
}
return false;
}
```
---
#### Q: Reverse Linked List (leetcode 206)
>[題目網址](https://leetcode.com/problems/reverse-linked-list/)
>給一個list, 把list反轉
>input: 3->2->0->-4, output: -4->0->2->3
改變指標的指向就好, 用3個指標去存前中後
```C=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode *next = NULL;
struct ListNode *curr = head;
struct ListNode *prev = NULL;
while (curr) {
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
head = prev;
return prev;
}
```
---
#### Q: Merge Two Sorted Lists (leetcode 21)
>[題目網址](https://leetcode.com/problems/merge-two-sorted-lists/)
>給兩個已經排序好的list, 合併成一個list並且也是要排序好的
>input: list1: 1->2->4, list2: 1->3->4, output: 1->1->2->3->4->4
一般做法就是開一個空的list, 然後比較小的先放進去
最後假如其中一個list還有東西的話, 必定都是比較大的值, 直接接上去就好
最後要回傳的是新的list的next, 因為第一個node是NULL
```C
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode *ans = malloc(sizeof(struct ListNode));
struct ListNode *ptr = ans;
if (list1 == NULL) return list1;
if (list2 == NULL) return list2;
while (list1 && list2) {
if (list1->val < list2->val) {
ptr->next = list1;
list1 = list1->next;
} else {
ptr->next = list2;
list2 = list2->next;
}
ptr = ptr->next;
}
if (list1) {
ptr->next = list1;
} else {
ptr->next = list2;
}
return ans->next;
}
```
>這樣寫最直觀最好理解, 不過會需要開一個新的空間
>[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#%E5%BE%9E-Linux-%E6%A0%B8%E5%BF%83%E7%9A%84%E8%97%9D%E8%A1%93%E8%AB%87%E8%B5%B7)
>這篇的 "案例探討: LeetCode 21. Merge Two Sorted Lists"裡面有很詳細的介紹其他解法
>(神跟人看到的東西就是不一樣)
---
#### Q: Implement a Queue and Stack by linked-list
>[你所不知道的 C 語言:物件導向程式設計篇](https://hackmd.io/@sysprog/c-oop)
用linked-list實作queue和stack
方法很多, 就看資料結構要怎麼設計
Queue
```C=
struct linked_list {
struct linked_list *next;
uint8_t data;
};
struct Queue {
struct linked_list *head, *tail;
void (*qpush)(uint8_t);
void (*qpop)();
};
struct Queue queue = NULL;
void qpush(uint8_t val)
{
struct linked_list *tmp = malloc(sizeof(struct linked_list));
tmp->next = NULL;
tmp->val = val;
if (queue->head == NULL)
queue->head = tmp;
else
queue->tail->next = tmp;
queue->tail = tmp;
}
void qpop()
{
struct ListNode *tmp = malloc(sizeof(struct ListNode));
queue->tail->next = NULL;
queue->head = queue->head->next;
if (queue->head == NULL)
queue->tail = NULL;
free(tmp);
tmp = NULL;
}
void init_queue(struct Queue **q)
{
*q = (struct Queue *)malloc(sizeof(struct Queue));
if (*q == NULL) return;
(*q)->head = NULL;
(*q)->tail = NULL;
(*q)->qpush = qpush;
(*q)->qpop = qpop;
}
int main(void)
{
init_queue(&queue);
list.push(1);
list.push(2);
list.push(3);
list.pop();
list.push(4);
list.pop();
}
```
Stack (TODO)
```C=
struct linked_list {
struct linked_list *next;
uint8_t data;
};
struct Stack {
struct linked_list *top;
void (*spush)(uint8_t);
void (*spop)();
};
struct Stack stack = NULL;
void spush(uint8_t val)
{
struct linked_list *tmp = malloc(sizeof(struct linked_list));
tmp->next = NULL;
tmp->val = val;
if (stack->head == NULL)
stack->head = tmp;
else
stack->top->next = tmp;
queue->tail = tmp;
}
void spop()
{
struct ListNode *tmp = malloc(sizeof(struct ListNode));
queue->tail->next = NULL;
queue->head = queue->head->next;
if (queue->head == NULL)
queue->tail = NULL;
free(tmp);
tmp = NULL;
}
void init_stack(struct Stack **s)
{
*q = (struct Queue *)malloc(sizeof(struct Queue));
if (*q == NULL) return;
(*q)->head = NULL;
(*q)->tail = NULL;
(*q)->qpush = qpush;
(*q)->qpop = qpop;
}
int main(void)
{
init_stack(&stack);
list.push(1);
list.push(2);
list.push(3);
list.pop();
list.push(4);
list.pop();
}
```
---
#### Q: 移除值為偶數的node
```C=
void delete_node_even(struct ListNode **head)
{
struct ListNode *prev = NULL, *next = NULL, *tmp = NULL;
struct ListNode *curr = *head;
if (curr == NULL) return;
while (curr) {
if (!(curr->val & 1)) {
tmp = curr;
if (prev == NULL) {
curr = curr->next;
*head = curr;
} else {
next = curr->next;
prev->next = next;
curr = next;
}
free(tmp);
tmp = NULL;
} else {
prev = curr;
curr = curr->next;
}
}
}
```
---
#### Q: Copy list
copy一模一樣的list
```C=
struct ListNode *copy_list(struct ListNode *head)
{
struct ListNode *tail = NULL;
struct ListNode *new_list = NULL;
struct ListNode *curr = head;
if (head == NULL) return NULL;
new_list = (struct ListNode *)malloc(sizeof(struct ListNode));
new_list->val = curr->val;
new_list->next = NULL;
tail = new_list;
curr = curr->next;
while (curr) {
tail->next = (struct ListNode *)malloc(sizeof(struct ListNode));
tail = tail->next;
tail->val = curr->val;
tail->next = NULL;
curr = curr->next;
}
return new_list;
}
```
---
## MCU
系統場比較愛問的東西(個人經驗), 新鮮人的話大部分都是問在學校實驗過那些protocol
做過哪些照實回答即可, 如果可以的話建議回答的越詳細越好
有使用過示波器或邏輯分析儀(Logic Analyzer LA)量測過訊號的話, 加分很大, 可以特別提出來
其實幾乎所有底層操作都是去操作register, STM32裡面的HAL lib也不例外
所以如果有直接對底層操作過的經驗而不是都用別人寫好的lib加分很大
---
### Interrupts
中斷這個機制在MCU的開發中很常使用, 為了要做到一些低功耗的開發, 中斷是一定會用到的, 所以也算是必考之一
#### Q: 請說明底下程式哪裡需要改
>題目來源
>[中斷 (Interrupts)](https://blog.xuite.net/tzeng015/twblog/113273155#)
```C=
__interrupt double compute_area(double radius)
{
double area = PI * radius * radius;
printf("nArea = %f", area);
return area;
}
```
---
### GPIO
>[STM32-3 GPIO初探 ](https://ithelp.ithome.com.tw/articles/10284264?sc=rss.qu)
>stm32的gpio簡介, 要看的話記得去看留言區
GPIO(General-Purpose Input/output)
算是所有protocol的基本類型了, 通常學校實習課一定會用到
```
GPIO大致分成input和output, 這個稱為direction
output可以設定要輸出High or Low, input則是可以讀取外部的電壓資訊是High or Low
input讀回來的電壓值通常都是讀到超過3V就會判定High, 反之就是Low
所以在做電路設計時, 要避免讓電壓處於臨界值, 這樣很容易誤判
詳細的判定值要看datasheet來決定
```
最後補充一點, 如果gpio是input的話最好預設要pull-high or pull-low
不要floating, 不然很容易誤判
>[[問題] 為什麼會有floating 這種狀態?](https://www.ptt.cc/bbs/Electronics/M.1515253667.A.26D.html)
>[為什麼 GPIO input 要用 pull-up/pull-down,output 要用 push-pull 或 open-drain?](https://tfing.blogspot.com/2019/10/gpio-input-pull-uppull-downoutput-push.html)
---
### ADC
>[ADC](http://wiki.csie.ncku.edu.tw/embedded/STM32F429-ADC)
>[[Day 19] STM32 ADC 類比數位轉換器](https://ithelp.ithome.com.tw/articles/10300851)
>[单端(Single-Ended)模式与差分(Differential)模式的区别](https://blog.csdn.net/qq_34447192/article/details/101208745)
ADC(Analog-to-digital Coverter)
也算是很常遇到的應用
主要功能就是把類比連續的訊號轉成數位離散的訊號
既然扯到轉換, 那當然就要考慮到解析度以及取樣率
進來電壓的計算方式需要去看datasheet
使用ADC前一定要記得校正, 不然值很容易出錯
---
#### Q: 請簡單介紹ADC的檢測方式
有被某間做面板的問過, 所以還是把它放上來
| Single-ended | Differential |
| ------------------------ |:-------------------------- |
| **量測點與ground的比較** | **會有兩個量測點互相比較** |
---
### I2C
>[【Day21】I2C的介紹](https://ithelp.ithome.com.tw/articles/10278308)
>google搜尋I2C的第一個搜尋結果, 沒接觸過的話一定要看完
I2C (Inter-Interated Circuit)
很常見的protocol, 幾乎所有場合都用的到
尤其是做IoT相關的, 一堆device都是用I2C在傳輸的
```
傳輸方式大概就是靠device上的address
master先用start condition加上address告訴device要溝通了
device再回傳1個ACK告訴master說我有收到, 然後接下來每8個bit會回傳1個ACK給master
所以一般的device如果是7-bit address的話, master存取一次device通常要送18個bits
去掉start condition, 7-bit address + R/W bit + ACK + data(8bit) + ACK
```
大致上都是這樣, 主要還是要看datasheet怎麼寫
我就遇過藉著I2C的名義, 但是跟平常用的完全不一樣
---
### SPI
>[SPI](http://wiki.csie.ncku.edu.tw/embedded/SPI)
SPI(Serial Peripheral Interface)
也算常見, 不過我遇到的device還是I2C居多
通常都是一組SPI接一個device
也有一組SPI接多個device, 不過這樣的話通常要看driver有沒有支援
如果沒有driver處理, 可能就要自己去控制CS(chip select) pin
不然常見的做法是會接一顆MUX去做切換, 同樣的方法也會用在I2C
```
主要由4根pin組成:
MISO(Master Input Salve Output)
MOSI(Master Output Salve Input)
SCLK(Serial Clock)
CS(Chip Select)
其中傳輸模式有分同步以及非同步
同步傳輸就是master和slave clock共用clock, 非同步則是各自有自己的clock
```
#### Q: 簡介clock phase(CPHA) and clock polarity(CPOL)
以新鮮人來說算偏難的問題, 因為通常來說MCU會幫你處理好很多問題
通常只要去call MCU提供的lib就可以傳輸了
```
CPHA:
表示要在哪個edge取值, 可以是第一個或是第二個
CPOL:
clock閒置時的電位, 通常都是High
```
---