1. 程式人生 > >嵌入式軟體工程師/linux c程式設計師 面試經驗自我總結

嵌入式軟體工程師/linux c程式設計師 面試經驗自我總結

一、什麼叫可重入?
可重入函式主要用於多工環境中,一個可重入的函式簡單來說就是
可以被中斷的函式,也就是說,可以在這個函式執行的任何時刻中斷
它,轉入OS排程下去執行另外一段程式碼,而返回控制時不會出現什麼
錯誤;而不可重入的函式由於使用了一些系統資源,比如全域性變數區,
中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函式是
不能執行在多工環境下的。


二、tcp/IP三次握手?
1.首先客戶端通過向伺服器端傳送一個SYN來建立一個主動開啟,作為三
路握手的一部分。(同步位為1)
2. 然後伺服器端應當為一個合法的SYN回送一個SYN/ACK。(同步位和確
認位都為1)
3. 最後,客戶端再發送一個ACK。這樣就完成了三路握手,並進入了連線
建立狀態。(確認位位1)


三、TCP/IP通訊阻塞和非阻塞?
阻塞:當socket的接收緩衝區中沒有資料時,read呼叫會一直阻
塞住,直到有資料到來才返回。當socket緩衝區中的資料量小於期望讀取
的資料量時,返回實際讀取的位元組數。當socket的接收緩衝區中的資料大於
期望讀取的位元組數時,讀取期望讀取的位元組數,返回實際讀取的長度。
非阻塞:socket的接收緩衝區中有沒有資料,read呼叫都會立刻返回。接收
緩衝區中有資料時,與阻塞socket有資料的情況是一樣的,如果接收緩衝區
中沒有資料,則返回錯誤號為EWOULDBLOCK,表示該操作本來應該阻塞的,但
是由於本socket為非阻塞的socket,因此立刻返回,遇到這樣的情況,可
以在下次接著去嘗試讀取。如果返回值是其它負值,則表明讀取錯誤。
因此,非阻塞的rea呼叫一般這樣寫:
if ((nread = read(sock_fd, buffer, len)) < 0)
{
if (errno == EWOULDBLOCK)
{
return 0; //表示沒有讀到資料
}
else 
return -1; //表示讀取失敗
}else return nread;  //讀到資料長度


四、TCP/UDP區別?
TCP---傳輸控制協議,提供的是面向連線、可靠的位元組流服務。當客戶和伺服器
彼此交換資料前,必須先在雙方之間建立一個TCP連線,之後才能傳輸資料。TCP
提供超時重發,丟棄重複資料,檢驗資料,流量控制等功能,保證資料能從一端
傳到另一端。


UDP---使用者資料報協議,是一個簡單的面向資料報的運輸層協議。UDP不提供可靠
性,它只是把應用程式傳給IP層的資料報傳送出去,但是並不能保證它們能到達
目的地。由於UDP在傳輸資料報前不用在客戶和伺服器之間建立一個連線,且沒有
超時重發等機制,故而傳輸速度很快


五、林銳記憶體思考?
(1)
void GetMemory(char *p) 
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
答:程式崩潰因為GetMemory並不能
傳遞動態記憶體,Test函式中的 str一
直都是NULL
strcpy(str, "hello world");
將使程式崩潰 


(2)
char *GetMemory(void) 
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
答:可能是亂碼 因為GetMemory返回的
是指向棧記憶體的指標,該指標的地址
不是 NULL,但其原現的內容已經被清
除,新內容不可知


(3)
void GetMemory2(char **p, int num) 
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
答: (1)能夠輸出hello (2)記憶體洩漏沒有free


(4)
void Test(void) 
{
char *str = (char *) malloc(100);
strcpy(str, hello);
free(str);
if(str != NULL)
{
strcpy(str, world);
printf(str);
}
}
答:篡改動態記憶體區的內容,後果難以
預料,非常危險 因為free(str);之後
str成為野指標,if(str != NULL)語句
不起作用


六、編寫各種str函式?
strcpy函式
char *my_strcpy(char *dest, const char *src)
{
char *temp;

assert(dest!=NULL && str != NULL);
while((*dest++ = *src++) != '\0');

return temp;
}


char *my_strcat(char *dest, const char *stc)
{
char *temp;

assert(dest!=NULL && str != NULL);
while(*dest)
dest++;
while((*dest++ = *stc++) != '\0');

return temp;
}


char my_strstr(const char *str, char c)
{
for(; *str != c; str++)
if(*str == '\0')
return NULL;
else
return str;
}


int my_strlen(const char *str)
{
char *temp = str;
for(; *temp != '\0'; temp++);

return (int)(temp - str);
}


void *my_memcpy(void * dest, const void *src, size_t count)
{
char *temp_dest = (char *)dest;
char *temp_src = (char *)src;

assert(dest != NULL && src != NULL);

while(count--)
*temp_dest++ = *temp_src++;
return dest;
}


int my_strcmp(char *str1, char *str2)
{
int ret = 0;

while((ret = *(unsigned char*)str1 - *(unsigned char*)str2)&&*str1&&*str2)
{
str1++;
str2++;
}
if(ret < 0)
ret = -1;
else if(ret > 0)
ret = 1;
return ret;
}




七、連結串列操作
(1)逆序?
node *reverse_node(node *head)
{
node *record, *current;

if(head == NULL || head->next == NULL)
return head;

current = head->next;
head->next = NULL;
while(current != NULL)
{
record = current->next;
current->next = head->next;
head->next = current;
current = record;
}
return head;
}
(2)插入
node *add_node(node *head, node *data)
{
node *current = head;
while(current->next != NULL)
current = current->next;
current->next = data;
data->next = NULL;
return head;
}
(3)刪除
node *del_node(node *head, node *data)
{
node *pf,*pb;
pf = head->next;

while(pf != NULL && pf->data != data->data)
{
pb = pf;
pf = pf->next;
}
if(pf->data == data->data)
{
pb->next = pf->next;
free(pf);
}
else
printf("NO node!\n");
}
(4)單鏈表(非迴圈)倒數第4個元素?
思路:讓第一個元素先走四步,然後兩個遊標指標一起走。
node *Get_Node(node *head)
{
int i;
node *first = head;
node *back  = head;

for(i=0; i<4; i++)
{
if(first->next == NULL)
printf("Node less than four!\n");
first = first->next;
}
while(first != NULL)
{
first = first->next;
back = back->next;
}
return back;
}
(5)如何找出連結串列中間元素?
思路:讓前一個指標每次走兩步,後一個指標每次走一步。
node *Get_middle_node(node *head)
{
node *first = head;
node *back = head;

while(first != NULL)
{
first = first->next->next;
back = back->next;
}
return back;
}
(6)刪除一個無頭單鏈表的一個節點(連結串列很長10萬以上)?
思路:把當前節點的下個節點資料拷貝到當前節點,然後刪除下一個節點。
void del_node(node *del_node)
{
node *record = del_node->next;
node *current = del_node;

current->data = record->data;  //資料拷貝,假設結構體資料為data
current->next = record->next;  //指標跳轉
free(record);
}
(7)如何判斷一個單鏈表是否有環?
思路:我們用一個步長1和一個步長2的遊標指標遍歷連結串列,觀察是否有兩個遊標相遇的時刻。
int judge_list_circle(node *head)
{
node *first = head;
node *back = head;

while(first->next != NULL && back->next->next != NULL)
{
first = first->next;
back = back->next->next;
if(first == back)
return true;
}
return false;
}


八、兩種排序法(氣泡排序、選擇排序)
void bubble(int a[], int n)
{
int i,j,temp;

for(i=0; i<n-1; i++)
for(j=i+1; j<n; j++)
{
if(a[i] > a[j])
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
return 0;
}


void choise(int a[], int n)
{
int i, j, temp, min;

for(i=0; i<n-1; i++)
{
min=i;
if(j=i+1; j<n; j++)
{
if(a[i] > a[j])
min = j;
}
if(min != i)
{
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
return 0;
}


九、字串反轉?
char str_rev(char *str, size_t len)
{
char *start = str;
char *end = str+len-1;
char ch;

if(str != NULL)
{
while(start < end)
{
ch = *start;
*start++ = *end;
*end-- = ch;
}
}
return str;
}


十、關鍵字volatile有什麼含意 並給出三個不同的例子。
一個定義為volatile的變數是說這變數可能會被意想不到地改變,這樣,編譯器就不會
去假設這個變數的值了。精確地說就是,優化器在用到這個變數時必須每次都小心地重
新讀取這個變數的值,而不是使用儲存在暫存器裡的備份。下面是volatile變數的幾個例子:
1) 並行裝置的硬體暫存器(如:狀態暫存器);
2) 一箇中斷服務子程式中會訪問到的非自動變數;
3) 多執行緒應用中被幾個任務共享的變數;


1) 一個引數既可以是const還可以是volatile嗎?解釋為什麼。
2) 一個指標可以是volatile 嗎?解釋為什麼。
3) 下面的函式有什麼錯誤:
int square(volatile int *ptr)
{
  return *ptr  *  *ptr;
}
答案:
1) 是的。一個例子是隻讀的狀態暫存器。它是volatile因為它可能被意想
不到地改變。它是const因為程式不應該試圖去修改它。
2) 是的。儘管這並不很常見。一個例子是當一箇中斷服務子程式修該一個指向一個buffer的指標時。
3)這段程式碼的目的是用來返指標*ptr指向值的平方,但是,由於*ptr指向
一個volatile型引數,編譯器將產生類似下面的程式碼:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由於*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段程式碼可能返不是你所期望的平方值!正確的程式碼如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}


十一、記憶體分配應該注意什麼問題?
1、檢查記憶體分配是否成功。
2、記憶體生命週期,程式結束時記得free,避免記憶體洩露。
3、使用過程中避免指標越界訪問,會導致不必要的錯誤。


十二、字串迴文判斷?
int str_is_resv(char *str)
{
int i;
int  len = strlen(str);
char *start = str;
char *end   = str+len-1;

for(i=0; i<len/2; i++)
{
if(*(start+i) != *(end-i))
return -1;
}
return 0;
}


十三、字串迴圈左移?
//迴圈左移,庫函式實現 
char *str_rev1(char *str, int bit)
{
int len = strlen(str);
int n = bit%len;
char temp[128] = "";

strcpy(temp, str);
strncpy(str, str+n, len-n);
str[len-n] = '\0';
strncat(str, temp, n);

return str;
}


//迴圈左移,不用庫函式
char *str_rev2(char *str, int bit)
{
char temp_str[128];
int i,j;
int str_len = strlen(str);
bit = bit%str_len;

for(i=0; i<bit; i++)
{
*(temp_str+i) = *(str+i);
}


for(i=0; i<str_len-bit; i++)
{
*(str+i) = *(str+bit+i);
}


for(j=0; j<bit; j++)
{
*(str+i+j) = *(temp_str+j);
}
return str;
}


十四、如何程式設計判斷大小端?
/*返回1表示小端,返回0表示大端*/
int check_cpu_endian()
{
union 
{
unsigned int a;
unsigned char b;
}s;
s.a = 1;      //0x01 
return (s.b == 1);
}


十五、如何判斷一個位元組(char)裡面多少bit被置1?
int count_bit(int value)
{
int i, count=0;

for(i=0; i<8; i++)
{
if(value&0x01 == 0x01)
count++;
value  = value >> 0x01;
}
return count;
}


十六、判斷一個整數裡面有多少個1?
int count_bit(int value)
{
int count=0; 

while(value)
{
count++;
value &= value-1;
}
return count;
}


十七、二分法查詢?
int mid_serach(int arry[], int key, int size)
{
int low, high, mid;

low = 0;
high = size-1;

while(low < high)
{
mid = (low+high)/2;
if(key == arry[mid])
return mid;
else if(key < arry[mid])
high = mid-1;
else if(key > arry[mid])
low = mid+1;
}
return -1;
}


十八、字串轉成十進位制數?
int str_to_int(char *str)
{
int temp = 0;


while(*str != '\0')
{
temp = temp*10 + *str - '0';
str++;
}
//temp = atol(str);    //庫函式一步完成
return temp;
}


十九、OSI模型分哪幾層?
物理層、資料鏈路層、網路層、傳輸層、應用層、表示層、會話層
集線器hub工作在OSI參考模型的(物理)層;
網絡卡工作在OSI參考模型的(物理)層;
路由器router工作在OSI參考模型的(網路)層;
交換機Switch工作在OSI參考模型的(資料鏈路)層。


二十、給定某個整數n(0-10000),求小於等於n的整數中與7有關的整數
(被7整除或者數字中含有7,只要求求出個數,不用列覺出每一個數字)
int fun(int data)
{
int count=0;
int i;
int first,second,third,four;

for(i=1; i<=data; i++)
{
first  = i%10;
second = i/10%10;
third  = i/100%10;
four   = i/1000;
if ((i%7 == 0) || (first == 7) || (second == 7) || (third == 7) || (four == 7)) 
{
//printf("qian bai shi ge is:%d %d %d %d\n", four, third, second, first);
count++;
}
}
return count;
}


二十一、將字串中每個單詞的第一個字元變為大寫?
char *fun(char *str)
{
int i;
int flag = -1;
int len = strlen(str);

if(islower(str[0]))
{
str[0] = toupper(str[0]);
}

for(i=1; i<len; i++)
{
if(str[i]==' ' && islower(str[i+1]))
{
str[i+1] = toupper(str[i+1]);
}

}
return str;
}


二十二、統計出具有n個元素的一維整數陣列中大於等於
所有元素平均值的元素個數。驗證該函式?


int fun(int a[], int size)
{
int i=0;
float sum=0, aver=0;
int count=0; 

for(i=0; i<size; i++)
sum +=a[i];
aver = sum/size;

for(i=0; i<size; i++)
{
if(a[i] > aver)
count++;
}
printf("The aver is %f\n", aver);

return count;
}