【實習面經】阿里+360+字節

阿里雲二面:

內存泄漏: c++

內存泄漏通常爲在代碼中申請了一塊內存後因爲各類緣由在使用完成後沒去釋放這塊內存,操做系統這個時候認爲這塊內存還在被應用程序使用(由於程序沒去釋放),因而這一塊內存對於程序來講已經沒有用了(也不會去用),對於系統來講也沒有釋放出來,這樣的一件事情成爲內存泄漏。 算法

對於現代的操做系統而言,一個程序是運行在獨立的進程空間中的,當這個進程結束後,操做系統將回收這個進程申請的全部內存,也就是說,當進程結束後,該進程泄漏的內存會被回收 (無論你是否是泄漏的都回收了) 編程

至於後果,對於運行在通常用戶這邊的應用程序來講,因爲運行的時間不長,結束後會被操做系統整個回收,通常不會形成不良影響(儘管如此,仍是要儘量作到沒有內存泄漏);而對於服務來講,好比跑在服務器上的程序,會長時間長時間的運行,若是有內存泄漏的代碼,會在運行中不斷積累泄漏的內存,最後佔滿服務器的全部可用內存,致使宕機。 windows

內存滿了後再次申請內存會報錯,或者在最後幾回申請的時候發生內存溢出。 設計模式

野指針: 數組

野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)指針變量在定義時若是未初始化,其值是隨機的,指針變量的值是別的變量的地址,意味着指針指向了一個地址是不肯定的變量,此時去解引用就是去訪問了一個不肯定的地址,因此結果是不可知的。 安全

指針變量未初始化

任何指針變量剛被建立時不會自動成爲NULL指針,它的缺省值是隨機的,它會亂指一氣。因此,指針變量在建立的同時應當被初始化,要麼將指針設置爲NULL,要麼讓它指向合法的內存。若是沒有初始化,編譯器會報錯" 'point' may be uninitializedin the function " 服務器

指針釋放後以後未置空

有時指針freedelete後未賦值 NULL,便會令人覺得是合法的。別看freedelete的名字(尤爲是delete),它們只是把指針所指的內存給釋放掉,但並無把指針自己幹掉。此時指針指向的就是"垃圾"內存。釋放後的指針應當即將指針置爲NULL,防止產生"野指針" 微信

 

多線程和單線程的區別: 網絡

何時單線程快何時多線程快:對於處理時間短的服務或者啓動頻率高的要用單線程,相反用多線程!

一億個數用多線程找出其中的質數:一個線程負責一部分數的求解。好比10個線程就同時操做求是不是質數。

 

推薦我看Unix環境編程和Unix網絡編程

 

360一面:

繼承的機制和實際應用場景,

static和const的實際應用場景,

問hash結構,哈希衝突:

鍵(key)通過hash函數獲得的結果做爲地址去存放當前的鍵值對(key-value)(hashmap的存值方式),可是卻發現該地址已經有值了,就會產生衝突。這個衝突就是hash衝突了。

換句話說就是:若是兩個不一樣對象的hashCode相同,這種現象稱爲hash衝突。

解決哈希衝突

有如下的方式能夠解決哈希衝突:

開放定址法

再哈希法

鏈地址法

創建公共溢出區

開放定址法

這種方法的意思是:當關鍵字key的哈希地址p=H(key)出現衝突時,以p爲基礎,產生另外一個哈希地址p1,若是p1仍然衝突,再以p爲基礎,產生另外一個哈希地址p2,…,直到找出一個不衝突的哈希地址pi ,將相應元素存入其中。

線性探測再散列

當發生衝突的時候,順序的查看下一個單元

二次(平方)探測再散列

當發生衝突的時候,在表的左右進行跳躍式探測

僞隨機探測再散列

創建一個僞隨機數發生器,並給一個隨機數做爲起點

再hash法

這種方式是同時構造多個哈希函數,當產生衝突時,計算另外一個哈希函數的值。這種方法不易產生彙集,但增長了計算時間。

鏈地址法

將全部哈希地址相同的都連接在同一個鏈表中 ,於是查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於常常進行插入和刪除的狀況。hashmap就是用此方法解決衝突的。

創建一個公共溢出區

將哈希表分爲基本表和溢出表兩部分,凡是和基本表發生衝突的元素,一概填入溢出表。

優缺點

開放散列(open hashing)/ 拉鍊法(針對桶鏈結構)

優勢:

在總數頻繁變更的時候能夠節省開銷,避免了動態調整;

記錄存儲在節點裏,動態分佈,避免了指針的開銷

刪除時候比較方便

缺點:

由於存儲是動態的,因此在查詢的時候跳轉須要更多的時間的開銷

在key-value能夠預知,以及沒有後續增改操做時候,封閉散列性能優於開放散列

不容易序列化

封閉散列(closed hashing)/ 開放定址法

優勢:

容易序列化

若是能夠預知數據總數,能夠建立完美哈希數列

缺點:

存儲的記錄數目不能超過桶組數,在交互時候會很是麻煩

使用探測序列,計算時間成本太高

刪除的時候比較麻煩

 

 

字節跳動新業務一面:

C++ map底層實現:

1.vector      底層數據結構爲數組 ,支持快速隨機訪問

2.list          底層數據結構爲雙向鏈表,支持快速增刪

3.deque      底層數據結構爲一箇中央控制器和多個緩衝區,支持首尾(中間不能)快速增刪,也支持隨機訪問

deque是一個雙端隊列(double-ended queue),也是在堆中保存內容的.它的保存形式以下:

[1] --> [2] -->[3] --> ...

每一個堆保存好幾個元素,而後堆和堆之間有指針指向,看起來像是listvector的結合品.

4.stack      底層通常用listdeque實現,封閉頭部便可,不用vector的緣由應該是容量大小有限制,擴容耗時

5.queue     底層通常用listdeque實現,封閉頭部便可,不用vector的緣由應該是容量大小有限制,擴容耗時

stackqueue實際上是適配器,而不叫容器,由於是對容器的再封裝)

6.priority_queue     的底層數據結構通常爲vector爲底層容器,堆heap爲處理規則來管理底層容器實現

7.set              底層數據結構爲紅黑樹,有序,不重複

8.multiset       底層數據結構爲紅黑樹,有序,可重複 

9.map            底層數據結構爲紅黑樹,有序,不重複

10.multimap    底層數據結構爲紅    黑樹,有序,可重複

11.hash_set     底層數據結構爲hash表,無序,不重複

12.hash_multiset 底層數據結構爲hash表,無序,可重複 

13.hash_map     底層數據結構爲hash表,無序,不重複

14.hash_multimap 底層數據結構爲hash表,無序,可重複 

15.unordered_map unordered_multimap底層數據結構

unordered_mapunordered_multimapkey爲無序排列,其底層實現爲hash table,所以其查找時間複雜度理論上達到了O(n),之因此說理論上是由於在理想無碰撞的狀況下,而真實狀況未必如此。

16.unordered_set & unordered_multiset

unordered_map & unordered_multimap相同,其底層實現爲hash table

 

字節跳動新業務三面:

就緒 運行 阻塞:進程調度:

四種進程間的狀態轉換:

1)進程的三種基本狀態

進程在運行中不斷地改變其運行狀態。一般,一個進程必須具備如下三種基本狀態:

就緒狀態: 

當進程已分配到除CPU之外的全部必要的資源,只要得到處理機即可當即執行,這時的進程狀態就稱爲就緒狀態;

執行狀態:

當進程已得到處理機,其程序正在處理機上執行,此時的進程狀態稱爲執行狀態;

阻塞狀態:

正在執行的進程,因爲等待某個事件發生而沒法執行時,便放棄處理機而進入阻塞狀態。引發進程阻塞的事件有不少種,例如,等待I/O完成、申請緩衝區不能知足、等待信號等。

2)進程三種狀態間的轉換

一個進程在運行期間,不斷地從一種狀態轉換到另外一種狀態,它能夠屢次處於就緒狀態和執行狀態,也能夠屢次處於阻塞狀態。 

A. 就緒—>執行

處於就緒狀態的進程,當進程調度程序爲之分配好了處理機後,該進程便由就緒狀態轉換爲執行狀態;

B. 執行—>就緒 

處於執行狀態的進程在其執行過程當中,因分配給它的一個時間片已經用完而不得不讓出處理機,因而進程從執行狀態轉換爲就緒狀態;

C. 執行—>阻塞

正在執行的進程因等待某種事件發生而沒法繼續執行時,便從執行狀態變成阻塞狀態;

D. 阻塞—>就緒

處於阻塞狀態的進程,若其等待的事件已經發生,因而進程便從阻塞狀態轉變爲就緒狀態。

 

進程線程區別:線程能夠獨佔內存嗎?能夠,線程的堆是共享的,棧是獨佔的

硬連接軟連接:

創建軟連接和硬連接的語法

軟連接:ln -s 源文件 目標文件

硬連接:ln 源文件 目標文件

源文件:即你要對誰創建連接

 

什麼是軟連接和硬連接

1,軟連接能夠理解成快捷方式。它和windows下的快捷方式的做用是同樣的。

2,硬連接等於cp -p 同步更新。(至關於給源文件加了一個智能指針,兩個指針指向同一個源文件內容)

區別: 軟連接文件的大小和建立時間和源文件不一樣。軟連接文件只是維持了從軟連接到源文件的指向關係(從jys.soft->jys能夠看出),不是源文件的內容,大小不同容易理解。

硬連接文件和源文件的大小和建立時間同樣。硬連接文件的內容和源文件的內容如出一轍,至關於copy了一份。

 

軟連接像快捷方式,方便咱們打開源文件,這一點在windows中深有體會,那硬連接有哪些應用呢?

在多用戶的操做系統裏,你寫一個腳本,程序等,沒有完成,保存後等下次有時間繼續寫,可是其餘用戶有可能將你未寫完的東西當成垃圾清理掉(這裏只是刪除了一個指向源文件的指針,還有硬連接指針存在),這時,你對你的程序,腳本等作一個硬連接,利用硬連接的同步更新,就能夠防止別人誤刪你的源文件了。

 

深拷貝淺拷貝:

淺拷貝(shallowCopy)只是增長了一個指針指向已存在的內存地址,

深拷貝(deepCopy)是增長了一個指針而且申請了一個新的內存,使這個增長的指針指向這個新的內存,

 

vector 容器擴容的整個過程,和 realloc() 函數的實現方法相似,大體分爲如下 4 個步驟:

  1. 分配一塊大小是當前 vector 容量幾倍的新存儲空間。注意,多數 STL 版本中的 vector 容器,其容器都會以 2 的倍數增加,也就是說,每次 vector 容器擴容,它們的容量都會提升到以前的 2 倍;
  2. 將 vector 容器存儲的全部元素,依照原有次序從舊的存儲空間複製到新的存儲空間中;
  3. 析構掉舊存儲空間中存儲的全部元素;
  4. 釋放舊的存儲空間。

 

經過以上分析不難看出,vector 容器的擴容過程是很是耗時的,而且當容器進行擴容後,以前和該容器相關的全部指針、迭代器以及引用都會失效。所以在使用 vector 容器過程當中,咱們應儘可能避免執行沒必要要的擴容操做。

要實現這個目標,能夠藉助 vector 模板類中提供的 reserve() 成員方法。不過在講解如何用 reserve() 方法避免 vector 容器進行沒必要要的擴容操做以前,vector 模板類中還提供有幾個和 reserve() 功能相似的成員方法,很容易混淆,這裏有必要爲讀者梳理一下,如表 1 所示。

表 1 vector模板類中功能相似的成員方法

成員方法

功能

size()

告訴咱們當前 vector 容器中已經存有多少個元素,但僅經過此方法,沒法得知 vector 容器有多少存儲空間。

capacity()

告訴咱們當前 vector 容器總共能夠容納多少個元素。若是想知道當前 vector 容器有多少未被使用的存儲空間,能夠經過 capacity()-size() 得知。注意,若是 size() 和 capacity() 返回的值相同,則代表當前 vector 容器中沒有可用存儲空間了,這意味着,下一次向 vector 容器中添加新元素,將致使 vector 容器擴容。

resize(n)

強制 vector 容器必須存儲 n 個元素,注意,若是 n 比 size() 的返回值小,則容器尾部多出的元素將會被析構(刪除);若是 n 比 size() 大,則 vector 會藉助默認構造函數建立出更多的默認值元素,並將它們存儲到容器末尾;若是 n 比 capacity() 的返回值還要大,則 vector 會先擴增,在添加一些默認值元素。

reserve(n)

強制 vector 容器的容量至少爲 n。注意,若是 n 比當前 vector 容器的容量小,則該方法什麼也不會作;反之若是 n 比當前 vector 容器的容量大,則 vector 容器就會擴容。

 

 

360廣告業務部二面

說一下Move函數:

C++11中,std::move存在於<utility>中,std::move函數能夠很方便的將左值引用轉換爲右值引用(左值、右值、左值引用、右值引用等相關介紹能夠參看:https://blog.csdn.net/xiaomucgwlmx/article/details/101346463)。實際上,std::move並不能夠移動任何東西,惟一的功能就是上邊說的將一個左值強制轉化爲右值引用,而後經過右值引用使用該值。

std::move函數原型以下:

// TEMPLATE FUNCTION move

template<class _Ty> inline

    constexpr typename remove_reference<_Ty>::type&&

        move(_Ty&& _Arg) _NOEXCEPT

    {    // forward _Arg as movable

    return (static_cast<typename remove_reference<_Ty>::type&&>(_Arg));

    }

這裏,函數參數T&&是一個指向模板類型參數的右值引用,經過引用摺疊,此參數能夠與任何類型的實參匹配(能夠傳遞左值或右值,這是std::move主要使用的兩種場景)。

1,引用摺疊規則:

     X& + & => X&

     X&& + & => X&

     X& + && => X&

     X&& + && => X&&

2,函數模板參數推導規則(右值引用參數部分):

     當函數模板的模板參數爲T而函數形參爲T&&(右值引用)時適用本規則。

     若實參爲左值 U& ,則模板參數 T 應推導爲引用類型 U& 。

  (根據引用摺疊規則, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )

    若實參爲右值 U&& ,則模板參數 T 應推導爲非引用類型 U 。

  (根據引用摺疊規則, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U或U&&,這裏強制規定T ≡ U )

3,std::remove_reference爲C++0x標準庫中的元函數,其功能爲去除類型中的引用。

      std::remove_reference<U&>::type ≡ U

      std::remove_reference<U&&>::type ≡ U

      std::remove_reference<U>::type ≡ U

4,如下語法形式將把表達式 t 轉換爲T類型的右值(準確的說是無名右值引用,是右值的一種)

      static_cast<T&&>(t)

5,無名的右值引用是右值

    具名的右值引用是左值。

源碼詳細說明:

1,原型定義中的原理實現:

 首先,函數參數T&&是一個指向模板類型參數的右值引用,經過引用摺疊,此參數能夠與任何類型的實參匹配(能夠傳遞左值或右值,這是std::move主要使用的兩種場景)。關於引用摺疊以下:

      公式一)X& &、X&& &、X& &&都摺疊成X&,用於處理左值

string s("hello");

std::move(s) => std::move(string& &&) => 摺疊後 std::move(string& )

此時:T的類型爲string&

typename remove_reference<T>::type爲string 

整個std::move被實例化以下

string&& move(string& t) //t爲左值,移動後不能在使用t

{

    //經過static_cast將string&強制轉換爲string&&

    return static_cast<string&&>(t); 

}

      公式二)X&& &&摺疊成X&&,用於處理右值

std::move(string("hello")) => std::move(string&&)

//此時:T的類型爲string 

//     remove_reference<T>::type爲string 

//整個std::move被實例以下

string&& move(string&& t) //t爲右值

{

    return static_cast<string&&>(t);  //返回一個右值引用

}

簡單來講,右值通過T&&傳遞類型保持不變仍是右值,而左值通過T&&變爲普通的左值引用.

②對於static_cast<>的使用注意:任何具備明肯定義的類型轉換,只要不包含底層const,均可以使用static_cast。

double d = 1;

void* p = &d;

double *dp = static_cast<double*> p; //正確

const char *cp = "hello";

char *q = static_cast<char*>(cp); //錯誤:static不能去掉const性質

static_cast<string>(cp); //正確 

 

③對於remove_reference是經過類模板的部分特例化進行實現的,其實現代碼以下

//原始的,最通用的版本

template <typename T> struct remove_reference{

    typedef T type;  //定義T的類型別名爲type

};

//部分版本特例化,將用於左值引用和右值引用

template <class T> struct remove_reference<T&> //左值引用

{ typedef T type; }

   

template <class T> struct remove_reference<T&&> //右值引用

{ typedef T type; }   

    

//舉例以下,下列定義的a、b、c三個變量都是int類型

int i;

remove_refrence<decltype(42)>::type a;             //使用原版本,

remove_refrence<decltype(i)>::type  b;             //左值引用特例版本

remove_refrence<decltype(std::move(i))>::type  b;  //右值引用特例版本

寫一個shared_ptr:

https://blog.csdn.net/qq_41822235/article/details/82934681?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1

字節跳動安全與風控一面:

char* a="aaaa";char a[]="aaa";的區別,轉換成二進制後的區別?

二者區別以下:

一. "讀" "寫" 能力

     char *a = "abcd";  此時"abcd"存放在常量區。經過指針只能夠訪問字符串常量,而不能夠改變它。

     而char a[20] = "abcd"; 此時 "abcd"存放在棧。能夠經過指針去訪問和修改數組內容。

二. 賦值時刻

     char *a = "abcd"; 是在編譯時就肯定了(由於爲常量)。

     而char a[20] = "abcd"; 在運行時肯定

三. 存取效率

     char *a = "abcd"; 存於靜態存儲區。在棧上的數組比指針所指向字符串快。所以慢

     而char a[20] = "abcd"; 存於棧上。快

另外注意:

     char a[] = "01234",雖然沒有指明字符串的長度,可是此時系統已經開好了,就是大小爲6-----'0' '1' '2' '3' '4' '5' '\0',(注意strlen(a)是不計'\0')

 

 

協程

對操做系統而言,線程是最小的執行單元,進程是最小的資源管理單元。不管是進程仍是線程,都是由操做系統所管理的。

線程的狀態

線程具備五種狀態:初始化、可運行、運行中、阻塞、銷燬

線程狀態的轉化關係

線程之間是如何進行協做的呢?

最經典的例子是生產者/消費者模式,即若干個生產者線程向隊列中系欸如數據,若干個消費者線程從隊列中消費數據。

生產者/消費者模式的性能問題是什麼?

  • 涉及到同步鎖
  • 涉及到線程阻塞狀態和可運行狀態之間的切換
  • 涉及到線程上下文的切換

什麼是協程呢?

協程(Coroutines)是一種比線程更加輕量級的存在,正如一個進程能夠擁有多個線程同樣,一個線程能夠擁有多個協程。(沒有返回值的函數)

coroutine is suspendable, resumable subroutine.

協程是可暫停和恢復執行的過程(過程就是函數)

常規子程序(函數)和協程的區別

子程序執行完返回把控制權返還給調用這個子程序的上層,讓上層繼續往下執行,一層套一層,這就是層級調用。

特徵:執行完畢才返回

不可中斷

協程

協程看上去也是子程序,但執行過程當中,在子程序內部可中斷,而後轉而執行別的子程序,在適當的時候再返回來接着執行。

特徵:能夠執行到一半先返回

可中斷、掛起再次執行

可恢復狀態

不一樣的語言對協程的實現方式多有不一樣,可是隻要可以在單線程裏實現協程的中斷恢復這兩個特徵那麼就是協程。

中斷過程與調用子程序過程類似點是表面的,從本質上講二者是徹底不同的。

二者的根本區別主要表如今服務時間與服務對象不同上。首先,調用子程序過程發生的時間是已知和固定的,即在主程序中的調用指令(CALL)執行時發生主程序調用子程序,調用指令所在位置是已知和固定的。而中斷過程發生的時間通常的隨機的,CPU在執行某一主程序時收到中斷源提出的中斷申請時,就發生中斷過程,而中斷申請通常由硬件電路產生,申請提出時間是隨機的(軟中斷髮生時間是固定的),也能夠說,調用子程序是程序設計者事先安排的,而執行中斷服務程序是由系統工做環境隨機決定的;其次,子程序徹底爲主程序服務的,二者屬於主從關係,主程序須要子程序時就去調用子程序,並把調用結果帶回主程序繼續執行。而中斷服務程序與主程序二者通常是無關的,不存在誰爲誰服務的問題,二者是平行關係;第三,主程序調用子程序過程徹底屬於軟件處理過程,不須要專門的硬件電路,而中斷處理系統是一個軟、硬件結合系統,須要專門的硬件電路才能完成中斷處理的過程;第四,子程序嵌套可實現若干級,嵌套的最多級數由計算機內存開闢的堆棧大小限制,而中斷嵌套級數主要由中斷優先級數來決定,通常優先級數不會很大。

操做系統中的協程

協程不是被操做系統內核所管理的,而是徹底由程序所控制,也就是在用戶態執行。這樣帶來的好處是性能大幅度的提高,由於不會像線程切換那樣消耗資源。

協程不是進程也不是線程,而是一個特殊的函數,這個函數能夠在某個地方掛起,而且能夠從新在掛起處外繼續運行。因此說,協程與進程、線程相比並非一個維度的概念。

一個進程能夠包含多個線程,一個線程也能夠包含多個協程。簡單來講,一個線程內能夠由多個這樣的特殊函數在運行,可是有一點必須明確的是,一個線程的多個協程的運行是串行的。若是是多核CPU,多個進程或一個進程內的多個線程是能夠並行運行的,可是一個線程內協程卻絕對是串行的,不管CPU有多少個核。畢竟協程雖然是一個特殊的函數,但仍然是一個函數。一個線程內能夠運行多個函數,但這些函數都是串行運行的。當一個協程運行時,其它協程必須掛起。

進程、線程、協程的對比

  • 協程既不是進程也不是線程,協程僅僅是一個特殊的函數,協程它進程和進程不是一個維度的。
  • 一個進程能夠包含多個線程,一個線程能夠包含多個協程。
  • 一個線程內的多個協程雖然能夠切換,可是多個協程是串行執行的,只能在一個線程內運行,無法利用CPU多核能力。
  • 協程與進程同樣,切換是存在上下文切換問題的。

上下文切換

  • 進程的切換者是操做系統,切換時機是根據操做系統本身的切換策略,用戶是無感知的。進程的切換內容包括頁全局目錄、內核棧、硬件上下文,切換內容保存在內存中。進程切換過程是由"用戶態到內核態到用戶態"的方式,切換效率低。
  • 線程的切換者是操做系統,切換時機是根據操做系統本身的切換策略,用戶無感知。線程的切換內容包括內核棧和硬件上下文。線程切換內容保存在內核棧中。線程切換過程是由"用戶態到內核態到用戶態",切換效率中等。
  • 協程的切換者是用戶(編程者或應用程序),切換時機是用戶本身的程序所決定的。協程的切換內容是硬件上下文,切換內存保存在用戶本身的變量(用戶棧或堆)中。協程的切換過程只有用戶態,即沒有陷入內核態,所以切換效率高。

 

 

管程

1、     管程的概念

1.     管程能夠看作一個軟件模塊,它是將共享的變量和對於這些共享變量的操做封裝起來,造成一個具備必定接口的功能模塊,進程能夠調用管程來實現進程級別的併發控制。

2.     進程只能互斥得使用管程,即當一個進程使用管程時,另外一個進程必須等待。當一個進程使用完管程後,它必須釋放管程並喚醒等待管程的某一個進程。

3.     在管程入口處的等待隊列稱爲入口等待隊列,因爲進程會執行喚醒操做,所以可能有多個等待使用管程的隊列,這樣的隊列稱爲緊急隊列,它的優先級高於等待隊列。

2、     管程的特徵

1.     模塊化。

管程是一個基本的軟件模塊,能夠被單獨編譯。

2.     抽象數據類型。

管程中封裝了數據及對於數據的操做,這點有點像面向對象編程語言中的類。

3.     信息隱藏。

管程外的進程或其餘軟件模塊只能經過管程對外的接口來訪問管程提供的操做,管程內部的實現細節對外界是透明的。

4.     使用的互斥性。

任何一個時刻,管程只能由一個進程使用。進入管程時的互斥由編譯器負責完成。

3、     enter過程、leave過程、條件型變量c、wait(c) 、signal(c)

1.     enter過程

一個進程進入管程前要提出申請,通常由管程提供一個外部過程--enter過程。如Monitor.enter()表示進程調用管程Monitor外部過程enter進入管程。

2.     leave過程

當一個進程離開管程時,若是緊急隊列不空,那麼它就必須負責喚醒緊急隊列中的一個進程,此時也由管程提供一個外部過程—leave過程,如Monitor.leave()表示進程調用管程Monitor外部過程leave離開管程。

3.     條件型變量c

條件型變量c其實是一個指針,它指向一個等待該條件的PCB隊列。如notfull表示緩衝區不滿,若是緩衝區已滿,那麼將要在緩衝區寫入數據的進程就要等待notfull,即wait(notfull)。相應的,若是一個進程在緩衝區讀數據,當它讀完一個數據後,要執行signal(notempty),表示已經釋放了一個緩衝區單元。

4.     wait(c)

wait(c)表示爲進入管程的進程分配某種類型的資源,若是此時這種資源可用,那麼進程使用,不然進程被阻塞,進入緊急隊列。

5.     signal(c)

signal(c)表示進入管程的進程使用的某種資源要釋放,此時進程會喚醒因爲等待這種資源而進入緊急隊列中的第一個進程。

TCP報文中syn標誌位除了申請鏈接還有什麼用?

無其餘做用。

https中的非對稱加密用了什麼算法,這個算法是怎麼加密的

RSA加密?

1. RSA 簽名驗證

A和B分別具備本身的公鑰和私鑰。A知道本身的公私鑰和B的公鑰,B知道本身的公私鑰和A的公鑰匙。

流程以下:

A 方:

1. A利用hash算法對明文信息message進行加密獲得hash(message),而後利用本身對私鑰進行加密獲得簽名,以下

PrivateA(hash(message))=sign

2. 利用B的公鑰對簽名和message進行加密,以下:

PublicB(sign+message)=final

 

B 方:

1. 利用本身的私鑰解密

PrivateB(final)=sign+message

2.利用A的公鑰鑰對簽名進行解密

PublicA(sign)=hash(message)

3.利用與A相同對hash算法對message加密,比較與第二步是否相同。驗證信息是否被篡改

 

字節跳動安全與風控二面:

C++ copy和=重載何時用

C++中通常建立對象,拷貝或賦值的方式有構造函數,拷貝構造函數,賦值函數這三種方法。下面就詳細比較下三者之間的區別以及它們的具體實現

1.構造函數

構造函數是一種特殊的類成員函數,是當建立一個類的對象時,它被調用來對類的數據成員進行初始化和分配內存。(構造函數的命名必須和類名徹底相同)

首先說一下一個C++的空類,編譯器會加入哪些默認的成員函數

·默認構造函數和拷貝構造函數

·析構函數

·賦值函數(賦值運算符)

·取值函數

**即便程序沒定義任何成員,編譯器也會插入以上的函數!

注意:構造函數能夠被重載,能夠多個,能夠帶參數;

析構函數只有一個,不能被重載,不帶參數

   

而默認構造函數沒有參數,它什麼也不作。當沒有重載無參構造函數時,

  A a就是經過默認構造函數來建立一個對象

下面代碼爲構造函數重載的實現

   

class A  

{  

int m_i;  

Public:  

  A()   

{  

 Cout<<"無參構造函數"<<endl;  

}  

A(int i):m_i(i) {}  //初始化列表  

}

class A

{

int m_i;

Public:

A()

{

Cout<<"無參構造函數"<<endl;

}

A(int i):m_i(i) {} //初始化列表

}

 

2.拷貝構造函數

拷貝構造函數是C++獨有的,它是一種特殊的構造函數,用基於同一類的一個對象構造和初始化另外一個對象。

當沒有重載拷貝構造函數時,經過默認拷貝構造函數來建立一個對象

A a;

A b(a);

A b=a;  都是拷貝構造函數來建立對象b

強調:這裏b對象是不存在的,是用a 對象來構造和初始化b的!!

   

先說下何時拷貝構造函數會被調用:

在C++中,3種對象須要複製,此時拷貝構造函數會被調用

1)一個對象以值傳遞的方式傳入函數體

2)一個對象以值傳遞的方式從函數返回

3)一個對象須要經過另外一個對象進行初始化

   

何時編譯器會生成默認的拷貝構造函數:

1)若是用戶沒有自定義拷貝構造函數,而且在代碼中使用到了拷貝構造函數,編譯器就會生成默認的拷貝構造函數。但若是用戶定義了拷貝構造函數,編譯器就不在生成。

2)若是用戶定義了一個構造函數,但不是拷貝構造函數,而此時代碼中又用到了拷貝構造函數,那編譯器也會生成默認的拷貝構造函數。

   

由於系統提供的默認拷貝構造函數工做方式是內存拷貝,也就是淺拷貝。若是對象中用到了須要手動釋放的對象,則會出現問題,這時就要手動重載拷貝構造函數,實現深拷貝。

下面說說深拷貝與淺拷貝:

淺拷貝:若是複製的對象中引用了一個外部內容(例如分配在堆上的數據),那麼在複製這個對象的時候,讓新舊兩個對象指向同一個外部內容,就是淺拷貝。(指針雖然複製了,但所指向的空間內容並無複製,而是由兩個對象共用,兩個對象不獨立,刪除空間存在)

深拷貝:若是在複製這個對象的時候爲新對象製做了外部對象的獨立複製,就是深拷貝。

   

拷貝構造函數重載聲明以下:

A (const A&other)

下面爲拷貝構造函數的實現:

   

class A  

{  

  int m_i  

  A(const A& other):m_i(other.m_i)  

  Cout<<"拷貝構造函數"<<endl;  

}

class A

{

int m_i

A(const A& other):m_i(other.m_i)

{

Cout<<"拷貝構造函數"<<endl;

}

}

   

3.賦值函數

當一個類的對象向該類的另外一個對象賦值時,就會用到該類的賦值函數。

當沒有重載賦值函數(賦值運算符)時,經過默認賦值函數來進行賦值操做

A a;

A b;

b=a; 

強調:這裏a,b對象是已經存在的,是用a 對象來賦值給b的!!

   

賦值運算的重載聲明以下:

 A& operator = (const A& other)

   

一般你們會對拷貝構造函數和賦值函數混淆,這兒仔細比較二者的區別:

1)拷貝構造函數是一個對象初始化一塊內存區域,這塊內存就是新對象的內存區,而賦值函數是對於一個已經被初始化的對象來進行賦值操做。

   

  1. class  A;  
  2. A a;  
  3. A b=a;   //調用拷貝構造函數(b不存在)  
  4. A c(a) ;   //調用拷貝構造函數  
  5.     
  6. /****/  
  7.     
  8. class  A;  
  9. A a;  
  10. A b;     
  11. b = a ;   //調用賦值函數(b存在)

class A;

A a;

A b=a; //調用拷貝構造函數(b不存在)

A c(a) ; //調用拷貝構造函數

 

/****/

 

class A;

A a;

A b;

b = a ; //調用賦值函數(b存在)

 

2)通常來講在數據成員包含指針對象的時候,須要考慮兩種不一樣的處理需求:一種是複製指針對象,另外一種是引用指針對象。拷貝構造函數大多數狀況下是複製,而賦值函數是引用對象

3)實現不同。拷貝構造函數首先是一個構造函數,它調用時候是經過參數的對象初始化產生一個對象。賦值函數則是把一個新的對象賦值給一個原有的對象,因此若是原來的對象中有內存分配要先把內存釋放掉,並且還要檢察一下兩個對象是否是同一個對象,若是是,不作任何操做,直接返回。(這些要點會在下面的String實現代碼中體現)

   

!!!若是不想寫拷貝構造函數和賦值函數,又不容許別人使用編譯器生成的缺省函數,最簡單的辦法是將拷貝構造函數和賦值函數聲明爲私有函數,不用編寫代碼。如:

   

  1. class A  
  2. {  
  3.   private:  
  4.  A( const A& a); //私有拷貝構造函數  
  5.  A& operate=( const A& a); //私有賦值函數  
  6. }

class A

{

private:

A(const A& a); //私有拷貝構造函數

A& operate=(const A& a); //私有賦值函數

}


若是程序這樣寫就會出錯:

  1. A a;  
  2. A b(a); //調用了私有拷貝構造函數,編譯出錯  
  3. A b;  
  4. b=a; //調用了私有賦值函數,編譯出錯

A a;

A b(a); //調用了私有拷貝構造函數,編譯出錯

 

A b;

b=a; //調用了私有賦值函數,編譯出錯

 

因此若是類定義中有指針或引用變量或對象,爲了不潛在錯誤,最好重載拷貝構造函數和賦值函數。

   

下面以string類的實現爲例,完整的寫了普通構造函數,拷貝構造函數,賦值函數的實現。String類的基本實現見我另外一篇博文。

   

String::String(const char* str)    //普通構造函數  

{  

 cout<<construct<<endl;  

 if(str==NULL)        //若是str 爲NULL,就存一個空字符串"" 

{  

 m_string=new char[1];  

 *m_string ='\0';  

}  

 else  

{  

  m_string= new char[strlen(str)+1] ;   //分配空間  

  strcpy(m_string,str);  

}  

}  

String::String(const String&other)   //拷貝構造函數  

{  

 cout<<"copy construct"<<endl;  

 m_string=new char[strlen(other.m_string)+1]; //分配空間並拷貝  

 strcpy(m_string,other.m_string);  

}  

    

String & String::operator=(const String& other) //賦值運算符  

{  

 cout<<"operator =funtion"<<endl ;  

 if(this==&other) //若是對象和other是用一個對象,直接返回自己  

 {  

  return *this;  

 }  

 delete []m_string; //先釋放原來的內存  

 m_string= new char[strlen(other.m_string)+1];  

 strcpy(m_string,other.m_string);  

 return * this;  

}

String::String(const char* str) //普通構造函數

{

cout<<construct<<endl;

if(str==NULL) //若是str 爲NULL,就存一個空字符串""

{

m_string=new char[1];

*m_string ='\0';

}

else

{

m_string= new char[strlen(str)+1] ; //分配空間

strcpy(m_string,str);

}

}

 

String::String(const String&other) //拷貝構造函數

{

cout<<"copy construct"<<endl;

m_string=new char[strlen(other.m_string)+1]; //分配空間並拷貝

strcpy(m_string,other.m_string);

}

 

String & String::operator=(const String& other) //賦值運算符

{

cout<<"operator =funtion"<<endl ;

if(this==&other) //若是對象和other是用一個對象,直接返回自己

{

return *this;

}

delete []m_string; //先釋放原來的內存

m_string= new char[strlen(other.m_string)+1];

strcpy(m_string,other.m_string);

return * this;

}

 

一句話記住三者:對象不存在,且沒用別的對象來初始化,就是調用了構造函數;

           對象不存在,且用別的對象來初始化,就是拷貝構造函數(上面說了三種用它的狀況!)

           對象存在,用別的對象來給它賦值,就是賦值函數。

 

 

指針和引用的區別(更深一步,彙編層面)

首先是引用情形下的c++源碼:

void add(int a, int b, int&c) {

c = a + b;

}

 

 

int main() {

int a = 1;

int b = 2;

int c = 0;

add(a, b, c);

 

}

下面是main對應的彙編碼:

; 6 : int main() {

push ebp

mov ebp, esp

sub esp, 12 ;爲該調用函數的棧空間預留12byte,用來存儲局部變量a,b, c

; 7 : int a = 1;

mov DWORD PTR _a$[ebp], 1;初始化a _a$爲a存儲空間地址相對於ebp基址的偏移量

; 8 : int b = 2;

mov DWORD PTR _b$[ebp], 2;初始化b _b$爲b存儲空間地址相對於ebp基址的偏移量

; 9 : int c = 0;

mov DWORD PTR _c$[ebp], 0;初試化c _c$爲c存儲空間地址相對於ebp基址的偏移量

; 10 : add(a, b, c);

lea eax, DWORD PTR _c$[ebp]; 獲取c存儲空間相對於ebp基址的偏移量(即c存儲單元的偏移地址),放在寄存器eax中

push eax;保存c存儲空間的偏移量到堆棧中

mov ecx, DWORD PTR _b$[ebp];將b存儲空間裏面的值(即b的值)放在寄存器ecx中

push ecx;保存b存儲空間的值到堆棧中

mov edx, DWORD PTR _a$[ebp];將a存儲空間裏面的值(即a的值)放在寄存器edx裏面

push edx;保存a存儲空間的到堆棧

;上面push eax push ecx push edx在棧裏面存儲了原來局部變量a,b,c的值,只不過對於c來講,存儲的是c存儲空間的偏移地址

;所以,對於a,b來講,也就是將他們的值得一份拷貝存了起來,也就是傳值;而c只是存儲了本身存儲空間的偏移地址,也就是傳地址

call ?add@@YAXHHAAH@Z ; 調用add函數,上面的語句已經爲傳遞參數作好了準備

add esp, 12 ; 因爲剛纔爲調用函數add傳遞參數進行了壓棧,這裏釋放棧空間,即釋放參數

;這就是爲何函數調用完成後局部變量和參數無效的緣由,由於他們的空間被釋放了

; 11 :

; 12 : }

xor eax, eax

mov esp, ebp

pop ebp

ret 0

下面是函數add對應的彙編碼:

; 1 : void add(int a, int b, int&c) {

push ebp

mov ebp, esp

; 2 : c = a + b;

mov eax, DWORD PTR _a$[ebp];取參數a的值到寄存器eax中

add eax, DWORD PTR _b$[ebp];取參數b的值與eax中a的值相加,結果放到eax中

mov ecx, DWORD PTR _c$[ebp];去c的偏移地址放到寄存器ecx中

mov DWORD PTR [ecx], eax;將eax中的結果寫到由ecx指定的地址單元中去,即c所在存儲單元

; 3 : }

pop ebp

ret 0


從上面能夠看到,對於傳值,c++確實傳的是一份值拷貝,而對於引用,雖然是傳值的形式,可是其實編譯器內部傳遞的是值得地址

 

下面是指針的情形的c++源碼:

void add(int a, int b, int* c) {

*c = a + b;

}

int main() {

int a = 1;

int b = 2;

int c = 0;

add(a, b, &c);

 

}

mian函數對應的彙編碼:

; 6 : int main() {

push ebp

mov ebp, esp

sub esp, 12 ;

; 7 : int a = 1;

mov DWORD PTR _a$[ebp], 1

; 8 : int b = 2;

mov DWORD PTR _b$[ebp], 2

; 9 : int c = 0;

mov DWORD PTR _c$[ebp], 0

; 10 : add(a, b, &c);

lea eax, DWORD PTR _c$[ebp]

push eax

mov ecx, DWORD PTR _b$[ebp]

push ecx

mov edx, DWORD PTR _a$[ebp]

push edx

call ?add@@YAXHHPAH@Z ; add

add esp, 12 ;

; 11 :

; 12 : }

xor eax, eax

mov esp, ebp

pop ebp

ret 0


add函數對應的彙編碼:

; 1 : void add(int a, int b, int* c) {

push ebp

mov ebp, esp

; 2 : *c = a + b;

mov eax, DWORD PTR _a$[ebp]

add eax, DWORD PTR _b$[ebp]

mov ecx, DWORD PTR _c$[ebp]

mov DWORD PTR [ecx], eax

; 3 : }

pop ebp

ret 0

能夠看到,指針和引用的彙編碼同樣,所以二者的做用也同樣

字節跳動安全與風控三面:

界面監聽而後修改數據的模式是什麼設計模式?

用數組實現循環隊列(給定長度)?

 

騰訊微信支付中心-事務開發部 一面:

用戶態在什麼狀況下可使用到內核態?

1.系統調用(來自應用)

2.中斷(來自外設)異步

3.異常(來自錯誤應用)同步

講講http協議

  • HTTP是無鏈接:無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間。
  • HTTP是媒體獨立的:這意味着,只要客戶端和服務器知道如何處理的數據內容,任何類型的數據均可以經過HTTP發送。客戶端以及服務器指定使用適合的MIME-type內容類型。
  • HTTP是無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺乏狀態意味着若是後續處理須要前面的信息,則它必須重傳,這樣可能致使每次鏈接傳送的數據量增大。另外一方面,在服務器不須要先前信息時它的應答就較快。

客戶端請求:

GET /hello.txt HTTP/1.1

User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3

Host: www.example.com

Accept-Language: en, mi

服務端響應:

HTTP/1.1 200 OK

Date: Mon, 27 Jul 2009 12:28:53 GMT

Server: Apache

Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT

ETag: "34aa387-d-1568eb00"

Accept-Ranges: bytes

Content-Length: 51

Vary: Accept-Encoding

Content-Type: text/plain

 

char a[1G];

聲明一個1G的字符串會發生什麼?由於是放在棧中的,可是通常的棧都只有2M,因此最大也就char a[2077144] 再大就會報錯

a[1G -1 ] = 'x';

這個語句會發生什麼?這個更不可能發生了,要是在範圍內還能夠執行,1G真的太大了

string b(1G);

聲明一個1Gstring會發生什麼?至關於構建一個string對象,調用構造函數是向內存中申請空間,也就是堆,這個最大空間很大,聲明一個1G的對象毫無問題。

vector<char> c(16);

堆棧區別

線程模型

 

用戶態

內核態

vector<char> c(16);

內存管理

虛擬內存 物理內存

 

ip 分片 重組

tcp 滑動窗口

慢啓動算法 

 

倒序合併兩個鏈表

A->B->C

F->G->H

C->H->B->G->A->F

相關文章
相關標籤/搜索