1. 程式人生 > >騰訊安全技術崗初試

騰訊安全技術崗初試

1.前言

2016.4.11日廣州參加了騰訊的CC++後臺技術一面,安全技術類的面試。面試官人很溫和,經歷了大概70分鐘的問答,特將遇到的面試問題彙總如下,自己總結學習,亦供網友參考。

2.問題彙總

問題一:
不好意思,我有事,先處理一下,你先寫個非遞迴二分查詢。
答:
之前遇到過這個問題,有所瞭解。感覺很多面試的第一個問題都是先寫段程式碼。因此,手寫程式碼感覺很重要,因為這是給面試官的第一印象。除了二分查詢快排連結串列反轉,實現atoi()函式等等,在面試中也常被用來作為手寫程式碼的考題。

//陣列遞增有序
int  binarySearch(int* array,int len,int value
){ int mid=0; int low=0; int high=len-1; while(low<=high){ mid=(low+high)/2; if(array[mid]==value) //找到 return mid; if(value>array[mid]) //在右半區 low=mid+1; else //在左半區 high=mid-1; } return
-1; //查詢失敗 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

問題二:
(面試官一直在打電話)如果你寫完了,你再寫一個從陣列中找出第二大的數。
答:
找第幾大的數突然間想到了堆排序,因為自己之前練習過堆排序,所以就花了十分鐘左右的時間寫下了堆排序。寫堆排序需要知道節點下標index的左子節點的下標為2*index+1,2*index+2。以陣列構造堆的話是一個完全二叉樹,陣列長度為len,那麼最後一個非葉子節點的下標是len/2-1。以堆排序來尋找第二大數參考程式碼如下:

//手寫大頂堆排序求大二大數
void adjust(int A[],int
index,int len){ if(index>len/2-1)//葉子節點,不同調整 return; int biggestIndex=0; if(2*index+2<len) biggestIndex=A[2*index+1]>A[2*index+2]?2*index+1:2*index+2; else biggestIndex=2*index+1; if(A[index]<A[biggestIndex]){ A[index]=A[index]+A[biggestIndex]; A[biggestIndex]=A[index]-A[biggestIndex]; A[index]=A[index]-A[biggestIndex]; adjust(A,biggestIndex,len); } } void createMaxHeap(int A[],int len){ for(int i=len/2-1;i>=0;--i){ adjust(A,i,len); } } int getSecondMax(int A[],int len){ createMaxHeap(A,len); //建堆 for(int i=0;i<2;++i){ A[0]=A[0]+A[len-1-i]; A[len-1-i]=A[0]-A[len-1-i]; A[0]=A[0]-A[len-1-i]; adjust(A,0,len-1-i); } return A[len-2]; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

很顯然,這個求解方法的時間複雜度是nlogn,不是較優解法,不是面試官想要的答案。面試官追問有沒有更好的方法,時間複雜度是O(n)。

稍微想了一下,回答氣泡排序和簡單選擇排序可以在O(2n)的時間複雜度找到第二大的數。他試官說還有沒有更快的方法呢?不要O(2n),只要O(n)。

正確答案是:
儲存最大值和第二大值,掃描一遍陣列即可找到,也就是以空間換時間。氣泡排序和簡單選擇排序都需要掃描兩遍,不太符合面試官的要求。下面給出正確的實現,參考如下程式碼:

int getSecondMax(int array[],int len){
    if(len<=1) 
        return -1;
    int max=0,secondMax=0;
    if(array[0]>array[1]){
        max=array[0];
        secondMax=array[1];
    }else{
        max=array[1];
        secondMax=array[0];
    }
    for(int i=2;i<len;++i){
        if(array[i]<=secondMax)
            continue;
        if(array[i]<=max&&array[i]>secondMax) //新的第二大數
            secondMax=array[i];
        else{
            secondMax=max;             //最大數退化為第二大數
            max=array[i];              //最大數
        }
    }
    return secondMax;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

這裡要感謝網友那年我們二十五提出的問題,已經修復參考程式碼的bug,感謝您細心的閱讀和慷慨的指教!

問題三:
C++中struct和class的區別?
答:
(1)關於繼承和訪問許可權,struct預設繼承和訪問許可權均為public,class均為private;
(2)關於模版,在模版中,型別引數前面可以使用class或typename,不能使用struct。

問題四:
說說C++多型的實現機制。
答:
簡單來說,就是通過虛擬函式表來實現的,具體請參考:CVTE面試問題彙總.

問題五:
C中static關鍵字的作用。
答:
(1)修飾變數,一是表名變數儲存空間在全域性靜態儲存區,二是變數生命週期是真個程式的生命週期,三是變數作用域限定在當前檔案。

(2)修飾函式,限制函式的作用域為當前檔案。

問題五:
C中extern關鍵字的作用。
答:
extern修飾變數和函式起到宣告的作用,以表示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他檔案中尋找其定義。

問題六:
C++中extern "C"的作用。
答:
C++中extern "C"修飾函式時,指明該函式以C的方式進行編譯和連結。具體來說就是C++函式支援函式過載,C不支援函式過載,原因二者的函式簽名不同。

如函式void foo(int,int)被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為”mangledname”)。

問題七:
說一下CC++程式的記憶體佈局。
答:
目前我還沒有找到很權威的著作對此問題有詳細的論述,肯定有,只是我還不知道。看了《C++高階進階教程》中描述如下。

如果記憶體地址由下到上的是從低地址到高地址,那麼程式的記憶體佈局大致如下:
這裡寫圖片描述

問題七:
殭屍程序是如何產生的。
答:
在UNIX 系統中,一個程序結束了,但是他的父程序沒有等待(呼叫wait / waitpid)他, 那麼他將變成一個殭屍程序。

問題八:
殭屍程序如何避免。
答:
(1)父程序通過wait和waitpid等函式等待子程序結束,但這會導致父程序掛起。
(2)如果父程序很忙,那麼可以用signal函式為SIGCHLD安裝handler,因為子程序結束後, 父程序會收到該訊號,可以在handler中呼叫wait回收。
(3)如果父程序不關心子程序什麼時候結束,那麼可以用signal(SIGCHLD,SIG_IGN)通知核心,自己對子程序的結束不感興趣,那麼子程序結束後,核心會回收, 並不再給父程序傳送訊號。
(4)還有一些技巧,就是fork兩次,父程序fork一個子程序,然後繼續工作,子程序fork一 個孫程序後退出,那麼孫程序被init接管,孫程序結束後,init會回收。不過子程序的回收 還要自己做。

問題九:
寫一個巨集,給定陣列名求陣列長度,陣列型別未知。
答:
想到sizeof就可以很容易求出來了。思路是先用sizeof(array)求出陣列佔用的記憶體空間大小(單位位元組),再通過sizeof求出陣列單個元素的型別大小(單位位元組),前者除以後者即可。

#define func(array) sizeof(array)/sizeof(array[0])
  • 1
  • 1

問題十:
Linux下awk和sed瞭解過吧,給定一個文字檔案,編寫shell指令碼將檔案中重複的行刪除。
答:
使用sort+uniq/awk/sed可以來完成。
方法一:利用sort以不重複的方式打印出檔案所有的行並排序-u,表示unique。

sort -u file
  • 1
  • 1

方法二:利用sort先對檔案按行排好序之後再交由uniq處理。sort -k 指定列,-t指定列分隔符。

sort -k 1 -t ':' file|uniq
  • 1
  • 1

方法三:利用sort+awk來完成。

sort file | awk '{if ($0!=line) print;line=$0}'
  • 1
  • 1

if ($0!=line) print;表示當前行是否等於上一行,不等於的話則列印,line開始是空的。line=$0表示當前行賦給line。

方法四:利用sort+sed來完成。

sort file | sed '$!N; /^\(.*\)\n\1$/!P;D'
  • 1
  • 1

sort file將檔案排序,排好序之後,重複的行會相鄰。sed的單引號內的編輯命令中,各條命令以分號隔開。
第一條語句:$!N;表示sed當前處理的行不是檔案的最後一行時,讀取下一行至當前處理的行的後面,一併儲存在sed的Pattern Space(模式空間)中。$表示最後一行,!N表示不讀取下一行。

第二條語句:/^\(.*\)\n\1$/!P;,斜槓//之間表示對行的匹配模式。匹配模式的描述是sed的對正則表示式的擴充。^\(.*\)表示開頭起任意字元,\n表示換行符,\1表示對前面第一個小括號內的字元重複,$表示行末。所以/^\(.*\)\n\1$/整個意思是匹配換行符前後相同的兩行。如this\nthis,這兩行記憶體在sed模式空間內。!P表示匹配成功的話,就不列印當前行,sed是預設列印當前處理後的行。

第三條語句:D命令是刪除當前模式空間開端至\n的內容(不在傳至標準輸出),放棄之後的命令,但是對剩餘模式空間重新執行sed。這樣就保證了sed的模式空間中除了最後一行時,不能讀取下一行,sed的模式空間中始終有兩行資料。

感覺sed不簡單啊。

問題十一:
有用過linux中的epoll嗎?它的作用是什麼?
答:
epoll是Linux核心為處理大批量檔案描述符而作了改進的poll,是Linux下多路複用IO介面select/poll的增強版本,它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統CPU利用率。

問題十二:
epoll和select的區別在哪,或者說優勢在哪?
答:
(1)epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered);
(2)select的控制代碼數目受限,在linux/posix_types.h標頭檔案有這樣的宣告:#define __FD_SETSIZE 1024,表示select最多同時監聽1024個fd。而epoll沒有,epoll的最大併發的連線數的理論值無上限,但由於實際記憶體資源有限,實際併發的連線數受到資源的限制和最大的開啟檔案控制代碼數目的限制;
(3)epoll的最大好處是不會隨著FD的數目增長而降低效率,在selec中採用輪詢處理,其中的資料結構類似一個數組的資料結構,而epoll 是維護一個佇列,直接看佇列是不是空就可以了。
(4)使用mmap加速核心與使用者空間的訊息傳遞。無論是select,poll還是epoll都需要核心把FD訊息通知給使用者空間,如何避免不必要的記憶體拷貝就很重要,在這點上,epoll是通過核心於使用者空間mmap同一塊記憶體實現的。

此外,epoll建立時傳入的引數是什麼?
epoll物件可通過int epoll_create(int size)來建立一個epoll的例項,size用來告訴核心這個監聽的數目一共有多大。這個引數不同於select()中的第一個引數,給出最大監聽的fd+1的值。需要注意的是,當建立好epoll控制代碼後,它就是會佔用一個fd值。所以在使用完epoll後,必須呼叫close()關閉,否則可能導致fd被耗盡。但是自從linux2.6.8之後,size引數是被忽略的。

此外,建立epoll例項,還可以通過int epoll_create1(int flags)
這個函式是在linux 2.6.27中加入的,其實它和epoll_create差不多,不同的是epoll_create1函式的引數是flags,當flag是0時,表示和epoll_create函式完全一樣,不需要size的提示了。

flag = EPOLL_CLOEXEC,建立的epfd會設定FD_CLOEXEC;

flag = EPOLL_NONBLOCK,建立的epfd會設定為非阻塞。

一般用法都是使用EPOLL_CLOEXEC。關於EPOLL_CLOEXEC,網上資料說明是對epfd的一個標識說明,用來設定檔案close-on-exec狀態的。當close-on-exec狀態為0時,呼叫exec時,fd不會被關閉;狀態非零時則會被關閉,這樣做可以防止fd洩露給執行exec後的程序。關於exec的用法,大家可以去自己查閱下,或者直接man exec。

問題十三:
給定資料表table1如下,編寫SQL語句找出出現次數前三的年齡。
這裡寫圖片描述
答:
在MS Access中測試跑通,參考語句如下:

select top 3 table2.Age,table2.num  from (select Age,count(Age) as num from table1 group by Age) as table2  Order By  table2.num DESC
  • 1
  • 1

上面的SQL語句在MS Access中驗證通過。其可分為兩部分。

第一部分是select Age,count(Age) as num from table1 group by Age。這一句是利用group by函式將選擇出來的以Age欄位進行分組,然後統計出相同年齡出現的次數並命名為新的欄位num。

第二部分是將第一部分選擇出來的結果集作為新表,再次從中選擇出以table2.num欄位降序排序後去前三行記錄。

問題十四:
網路的五層協議模型。
答:
很簡單,如下圖所示:
這裡寫圖片描述

問題十五:
咱們聊一下架構的事情。如果你現在是一位架構師,如何實現QQ的大量使用者併發登陸,應該考慮哪些問題?又該如何解決?
答:
(1)傳輸協議選擇

對於使用者的登陸,其口令驗證過程需要保證使用者相關資訊如使用者的口令(已經)順利傳輸到服務端,這就需要保證資料傳輸的得到可靠性。

TCP是面向連線的協議,也就是說協議本身向對方傳送資料前先確信對方準備好,對方收到後要回送確認。
UDP是無連線協議,就是說協議本身不管對方是否準備好,直接向對方傳送,不能確保對方收到。

所以,在使用者的登陸驗證的過程中,採用TCP協議傳輸。

好友之間傳送訊息,主要採用UDP協議,內網傳檔案採用了P2P技術。總來的說:
(a)登陸過程,客戶端client 採用TCP協議向伺服器server傳送資訊,HTTP協議下載資訊。登陸之後,會有一個TCP連線來保持線上狀態;

(b)和好友發訊息,客戶端client採用UDP協議,但是需要通過伺服器轉發。騰訊為了確保傳輸訊息的可靠,採用上層協議來保證可靠傳輸。如果訊息傳送失敗,客戶端會提示訊息傳送失敗,並可重新發送;

(c)如果是在內網裡面的兩個客戶端傳檔案,QQ採用的是P2P技術,最好使用TCP協議,不需要伺服器中轉。

(2)負載均衡
以下均是個人想法。為了應對QQ驗證登陸的併發量,短時間內響應對伺服器造成的壓力,我們可以採用多型伺服器,將同一類QQ提交到不同伺服器進行驗證,比如以QQ號的好兩位數字,可分為100中不同型別的QQ號,這樣就可實現伺服器的負載均衡。

(3)執行緒和程序的選擇
服務端程式,為了響應每一個QQ使用者的登陸驗證,應該採取執行緒進行服務。因為執行緒在資料共享、同步
記憶體佔用,切換,CPU利用率等方面佔有優勢,而程序間通訊複雜,佔用資源較大。

如何優化多執行緒併發服務呢?
我們沒有必要為每一位登陸驗證的使用者建立服務執行緒,可以採用執行緒池的方式來進行優化。

問題十六:
你瞭解過資料探勘嗎。對這方面感興趣嗎?

答:
沒了解過但很感興趣。

問題十七:
你還有什麼問題要問的。

答:
請問您搞安全技術方面需要對資料庫和SQL掌握很精通嗎?
面試官:無需精通,但是要有良好的基礎,常用的SQL要會寫。

3.小結

面試官也是比較溫和的人,很有耐心,整個過程節奏也很緩慢。不會的問題儘量去思考,面試官希望看到面試者的思考過程,不懂的我也向他請教,尋求提示,這也間接的產生了互動。

整個面試過程所遇到的問題,除了最後一個是比較開發的題目,前面都是比較基礎的題目。之所以問這些問題,還是就個人簡歷中提到的求職技能和相關專案進行相關問題提問。在SQL和shell指令碼方面,因為很多年沒寫SQL了,所以基本忘記,shell指令碼,儘管平時在Linux環境程式設計,但是很少寫,寫的話也是參考網上資源,一時無任何參考手寫確實有些不適,看來SQL和shell這方面要加強了。

參考文獻