二十万字C/C++、嵌入式软开面试题全集宝典六
目錄
101、 字節對齊有什么作用?
102、 C語言中#pragma用法
103、 new和malloc的區別?
104、 malloc/calloc/realloc三者之間的區別?
105、 delete p;與delete[]p,allocator
106、 new和delete的實現原理,delete是如何知道釋放內存的大小?
107、 malloc申請的存儲空間能用delete釋放嗎
108、 函數參數入棧的順序
109、 堆和棧區別
110、 堆與棧的優點和缺點
111、 內核空間 虛擬內存管理
112、 malloc與free的實現原理?
113、 malloc、realloc、calloc的區別
114、 __stdcall和__cdecl的區別?
115、 手寫字符串函數 strcat, strcpy, strncpy, memset, memcpy實現
116、 使用智能指針管理內存資源,RAII
117、 手寫實現智能指針類
118、 結構體變量比較是否相等
119、 位運算
120、 函數調用過程棧的變化,返回值和參數變量哪個先入棧?
?
101、 字節對齊有什么作用?
字節對齊的作用不僅是便于cpu快速訪問,同時合理的利用字節對齊可以有效地節省存儲空間。
編譯器中提供了#pragma pack(n)來設定變量以n字節對齊方式。n字節對齊就是說變量存放的起始地址的偏移量有兩種情況:第一、如果n大于等于該變量所占用的字節數,那么偏移量必須滿足默認的對齊方式,第二、如果n小于該變量的類型所占用的字節數,那么偏移量為n的倍數,不用滿足默認的對齊方式。
?
102、 C語言中#pragma用法
1.#pragma message
#pragma message("消息文本") 當編譯器遇到這條指令時,就在編譯輸出窗口中將消息文本打印出來。
2.#pragma code_seg
#pragma code_seg(["section-name"["section-class"]])
它能夠設置程序中函數代碼存放的代碼段。當我們開發驅動程序時便就會使用到它。
3.#pragma once
只要在頭文件的最開始加入這條指令就能夠頭文件被編譯一次。
4.#pragma hdrstop
表示編譯頭文件到此為止,后面的頭文件不進行預編譯。
5.#pragma resouce
#pragma resouce"*.dfm"表示*.dfm文件中的資源加入工程。*.dfm中包括了外觀定義。
6.#pragma warning
#pragma warning (disable:4507 34; once:4385; error:164) 等價于
#pragma warning (disable:4507 34) //不顯示4507和30號警告信息
#pragma warning (once:4385) //4358號警告信息僅報告一次
#pragma warning (error:164) //把164號警告信息作為一種錯誤
7.#pragma comment
#pragma comment(...) 該指令將一個注釋放入一個對象文件或可執行文件中,常用lib關鍵字幫我們鏈入一個庫文件。
如:#pragma comment(lib,"user32.lib") 該指令用來將user32.lib庫文件加入到本工程中。
8. #pragma pack
這條指令主要用作改變編譯器的默認對齊方式。
103、 new和malloc的區別?
1.new/delete是C++關鍵字,需要編譯器支持。malloc/free是庫函數,需要頭文件支持;
2.使用new操作符申請內存分配時無須指定內存塊的大小,編譯器會根據類型信息自行計算。而malloc則需要顯式地指出所需內存的尺寸。
3.new操作符內存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉換,故new是符合類型安全性的操作符。而malloc內存分配成功則是返回void * ,需要通過強制類型轉換將void*指針轉換成我們需要的類型。
4.new內存分配失敗時,會拋出bac_alloc異常。malloc分配內存失敗時返回NULL。
5.new會先調用operator new函數,申請足夠的內存(通常底層使用malloc實現)。然后調用類型的構造函數,初始化成員變量,最后返回自定義類型指針。delete先調用析構函數,然后調用operator delete函數釋放內存(通常底層使用free實現)。malloc/free是庫函數,只能動態的申請和釋放內存,無法強制要求其做自定義類型對象構造和析構工作。
104、 malloc/calloc/realloc三者之間的區別?
1)void *malloc(size_t size);
size表示要分配的字節數,其中要檢測空間是否開辟成功,開辟失敗時返回0。
作用:在內存中分配一個元素被初始化為0的數組。
2)void *calloc(size_t num, size_t size);
num表示元素的個數,size表示每個元素的大小
返回值:返回一個指向所分配空間的void指針。
作用:重新分配內存塊
3)void *realloc(void* memblock,size_t size);
memblock指向原先分配的內存塊,size表示新的內存塊的字節大小。
返回值:返回一個指向重新分配(可能移動了)的內存塊的大小。
注意:堆上的內存需要用戶自己來管理,動態malloc/calloc/realloc的空間,必須free掉,否則會造成內存泄漏
105、 delete p;與delete[]p,allocator
1.動態數組管理new一個數組時,[]中必須是一個整數,但是不一定是常量整數,普通數組必須是一個常量整數;
2.new動態數組返回的并不是數組類型,而是一個元素類型的指針;
3.delete[]時,數組中的元素按逆序的順序進行銷毀;
4.new在內存分配上面有一些局限性,new的機制是將內存分配和對象構造組合在一起,同樣的,delete也是將對象析構和內存釋放組合在一起的。allocator將這兩部分分開進行,allocator申請一部分內存,不進行初始化對象,只有當需要的時候才進行初始化操作。
106、 new和delete的實現原理,delete是如何知道釋放內存的大小?
1.new簡單類型直接調用operator new分配內存;而對于復雜結構,先調用operator new分配內存,然后在分配的內存上調用構造函數;對于簡單類型,new[]計算好大小后調用operator new;對于復雜數據結構,new[]先調用operator new[]分配內存,然后在p的前四個字節寫入數組大小n,然后調用n次構造函數,針對復雜類型,new[]會額外存儲數組大小;
○1new表達式調用一個名為operator new(operator new[])函數,分配一塊足夠大的、原始的、未命名的內存空間;
○2編譯器運行相應的構造函數以構造這些對象,并為其傳入初始值;
○3對象被分配了空間并構造完成,返回一個指向該對象的指針。
2.delete簡單數據類型默認只是調用free函數;復雜數據類型先調用析構函數再調用operator delete;針對簡單類型,delete和delete[]等同。假設指針p指向new[]分配的內存。因為要4字節存儲數組大小,實際分配的內存地址為[p-4],系統記錄的也是這個地址。delete[]實際釋放的就是p-4指向的內存。而delete會直接釋放p指向的內存,這個內存根本沒有被系統記錄,所以會崩潰。
3.需要在 new [] 一個對象數組時,需要保存數組的維度,C++ 的做法是在分配數組空間時多分配了 4 個字節的大小,專門保存數組的大小,在 delete [] 時就可以取出這個保存的數,就知道了需要調用析構函數多少次了。
107、 malloc申請的存儲空間能用delete釋放嗎
不能,malloc /free主要為了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作對象都是必須明確大小的。而且不能用在動態類上。new 和delete會自動進行類型檢查和大小,malloc/free不能執行構造函數與析構函數,所以動態對象它是不行的。當然從理論上說使用malloc申請的內存是可以通過delete釋放的。不過一般不這樣寫的。而且也不能保證每個C++的運行時都能正常。
108、 函數參數入棧的順序
○1大多數編譯器中,參數是從右向左?棧(原因在于采?這種順序,是為了讓程序員在使?C/C++的“函數參數?度可變”這個特性時更?便。如果是從左向右壓棧,第?個參數(即描述可變參數表各變量類型的那個參數)將被放在棧底,由于可變參的函數第?步就需要解析可變參數表的各參數類型,即第?步就需要得到上述參數,因此,將它放在棧底是很不方便的。)
○2本次函數調用結束時,局部變量先出棧,然后是參數,最后是棧頂指針最開始存放的地址,程序由該點繼續運?,不會產生碎?。
109、 堆和棧區別
1.管理方式:
○1棧由操作系統自動分配釋放,無需我們手動控制,無需我們手工控制,?般保存的是局部變量和函數參數等。
○2堆由程序員管理,需要?動 new malloc delete free 進?分配和回收,如果不進?回收的話,會造成內存泄漏的問題。
2.空間大小:一般來講在32位系統下,堆內存可以達到4G的空間,從這個角度來看堆內存幾乎是沒有什么限制的。但是對于棧來講,一般都是有一定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改: 打開工程,依次操作菜單如下:Project->Setting->Link,在Category 中選中Output,然后在Reserve中設定堆棧的最大值和commit。 注意:reserve最小值為4Byte;commit是保留在虛擬內存的頁文件里面,它設置的較大會使棧開辟較大的值,可能增加內存的開銷和啟動時間。
3.碎片問題:對于堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對于棧來講,則不會存在這個問題,因為棧是先進后出的隊列,他們是如此的一一對應,以至于永遠都不可能有一個內存塊從棧中間彈出,在他彈出之前,在他上面的后進的棧內容已經被彈出,詳細的可以參考數據結構,這里我們就不再一一討論了。
4.生長方向:
○1對于棧來講,是連續的內存空間,它的生長方向是向下的,是向著內存地址減小的方向增長。比如在函數調?的時候,首先?棧的主函數的下?條可執?指令的地址,然后是函數的各個參數。
○2對于堆來講,不連續的空間,實際上系統中有?個空閑鏈表,生長方向是向上的,也就是向著內存地址增加的方向,空間交?,較為靈活。
;當有程序申請的時候,系統遍歷空閑鏈表找到第?個?于等于申請??的空間分配給程序,?般在分配程序的時候,也會空間頭部寫?內存??,?便 delete 回收空間??。當然如果有剩余的,也會將剩余的插?到空閑鏈表中,這也是產?內存碎?的原因。
5.分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,它的動態分配是由編譯器進行釋放,無需我們手工實現。
6.分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。
110、 堆與棧的優點和缺點
1.堆的優缺點:堆得優點就是可以動態分配內存大小,生存期也不必告訴編譯器,因為它是在運行中動態分配內存的;缺點就是由于是在運行時動態分配內存的,所以讀取速度較慢。
2.棧的優缺點:棧的優點就是讀取速度快,而且數據可以共享;缺點就是存在于棧中的數據大小及周期必須是確定的,缺乏靈活性。
111、 內核空間 虛擬內存管理
1.虛擬內存管理負責從進程的虛擬地址空間分配虛擬頁,sys_brk負責用來擴大或收縮堆,sys_mmap負責從內存映射區域分配虛擬頁,sys_munmap用來釋放虛擬頁。
2.進程第一次訪問虛擬頁的時候觸發頁處理異常,直接從頁處理申請物理內存,然后映射到虛擬內存的頁表。
3.頁分配器負責分配物理頁,當前使用的頁分配器是伙伴分配器。內核空間提供把頁劃分為小內存塊分配的塊分配器,提供分配內存的接口kmalloc(),和釋放內存的接口kfree()。
4.不連續頁分配器提供分配內存的接口vmalloc()和釋放內存接口vfree(),在內存碎片化的時候,申請連續物理頁的成功率很低,可以申請不連續的物理頁,映射到連續的虛擬頁,即虛擬地址連續,頁物理地址不連續。
112、 malloc與free的實現原理?
malloc采用的是內存池的管理方式(ptmalloc),ptmalloc 采用邊界標記法將內存劃分成很多塊,從而對內存的分配與回收進行管理。
為了內存分配函數malloc的高效性,ptmalloc會預先向操作系統申請一塊內存供用戶使用,當我們申請和釋放內存的時候,ptmalloc會將這些內存管理起來,并通過一些策略來判斷是否將其回收給操作系統。
這樣做的最大好處就是,使用戶申請和釋放內存的時候更加高效,避免產生過多的內存碎片。
1.在標準C庫中,提供了malloc/free函數分配釋放內存,這兩個函數底層是由brk、mmap、,munmap這些系統調用實現的;
2.brk是將數據段(.data)的最高地址指針_edata往高地址推,mmap是在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區域的地方)找一塊空閑的虛擬內存。這兩種方式分配的都是虛擬內存,沒有分配物理內存。在第一次訪問已分配的虛擬地址空間的時候,發生虛擬中斷,操作系統負責分配物理內存,然后建立虛擬內存和物理內存之間的映射關系;
3.malloc小于128k的內存,使用brk分配內存,將_edata往高地址推;malloc大于128k的內存,使用mmap分配內存,在堆和棧之間找一塊空閑內存分配;brk分配的內存需要等到高地址內存釋放以后才能釋放,而mmap分配的內存可以單獨釋放。當最高地址空間的空閑內存超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行內存緊縮操作(trim)。在上一個步驟free的時候,發現最高地址空閑內存超過128K,于是內存緊縮。
4.malloc是從堆里面申請內存,也就是說函數返回的指針是指向堆里面的一塊內存。操作系統中有一個記錄空閑內存地址的鏈表。當操作系統收到程序的申請時,就會遍歷該鏈表,然后就尋找第一個空間大于所申請空間的堆結點,然后就將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序。
113、 malloc、realloc、calloc的區別
1.malloc函數
void* malloc(unsigned int num_size);
int *p = malloc(20*sizeof(int));申請20個int類型的空間;
2.calloc函數
void* calloc(size_t n,size_t size);
int *p = calloc(20, sizeof(int));
省去了人為空間計算;malloc申請的空間的值是隨機初始化的,calloc申請的空間的值是初始化為0的;
3.realloc函數
void realloc(void *p, size_t new_size);
給動態分配的空間分配額外的空間,用于擴充容量。
114、 __stdcall和__cdecl的區別?
1.__stdcall
__stdcall是函數恢復堆棧,只有在函數代碼的結尾出現一次恢復堆棧的代碼;在編譯時就規定了參數個數,無法實現不定個數的參數調用;
2.__cdecl
__cdecl是調用者恢復堆棧,假設有100個函數調用函數a,那么內存中就有100端恢復堆棧的代碼;可以不定參數個數;每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用__stacall函數大。
115、 手寫字符串函數 strcat, strcpy, strncpy, memset, memcpy實現
1. strcat
頭文件:#include <string.h>
用法:函數原型如下
char *strcat(char *dst, char const *src);
strcat 函數要求 dst 參數原先已經包含了一個字符串(可以是空字符串)。它找到這個字符串的末尾,并把 src 字符串的一份拷貝添加到這個位置。如果 src 和 dst 的位置發生重疊,其結果是未定義的。編程者需要保證目標字符數組剩余的空間足以保存整個字符串。
2. strcpy
頭文件:#include <string.h>
用法:strcpy 的函數原型如下:
char *strcpy(char *dst, const char *src);
函數把參數 src 字符串復制到 dst 參數,dst 字符串的結束符也會復制,如果參數 src 和 dst 在內存中出現疊,其結果是未定義的。由于 dst 參數將進行修改,所以它必須是個字符串數組或者是一個指向動態內存分配的數組指針,不能使用字符串常量。
需要注意的是:程序員必須保證目標字符串數組的空間足以容納需要復制的字符串。如果多余的字符串比數組長,多余的字符仍被復制,它們將覆蓋原先存儲于數組后面的內存空間。
3.memcpy
頭文件:#include <string.h>
用法:memcpy 提供了一般內存的復制,即memcpy對于需要復制的內容沒有限制,用途更廣泛。
void *memcpy(void *dst, const void *src, size_t length);
從 src 所指的內存地址的起始位置開始,拷貝n個字節的數據到 dest 所指的內存地址的起始位置。你可以用這種方法復制任何類型的值(例如:int,double,結構或結構數組),如果src和dst以任何形式出現了重疊,它的結果將是未定義的。
實現代碼:
4.strcpy 和 memcpy 的主要區別:
復制的內容不同。strcpy只能復制字符串,而memcpy可以復制任意內容,例如字符數組、整型、結構體、類等。
復制的方法不同。strcpy不需要指定長度,它遇到被復制字符的串結束符"\0"才結束,所以容易溢出。memcpy則是根據其第3個參數決定復制的長度,遇到'\0'并不結束。
用途不同。通常在復制字符串時用strcpy,而需要復制其他類型數據時則一般用memcpy;
5.strncpy
頭文件:#include <string.h>
函數原型如下:
char *strncpy(char *dst, char const *src, size_t len);
strncpy把源字符串的字符復制到目標數組,它總是正好向 dst 寫入 len 個字符。如果 strlen(src) 的值小于 len,dst 數組就用額外的 NUL 字節填充到 len 長度。如果 strlen(src)的值大于或等于 len,那么只有 len 個字符被復制到dst中。這里需要注意它的結果將不會以NUL字節結尾。
實現代碼:
5. memset
頭文件:#include <string.h>
函數原型如下:
void *memset(void *a, int ch, size_t length);
將參數a所指的內存區域前length個字節以參數ch填入,然后返回指向a的指針。在編寫程序的時候,若需要將某一數組作初始化,memset()會很方便。
實現代碼:
116、 使用智能指針管理內存資源,RAII
1.RAII全稱是“Resource Acquisition is Initialization”,直譯過來是“資源獲取即初始化”,也就是說在構造函數中申請分配資源,在析構函數中釋放資源。因為C++的語言機制保證了,當一個對象創建的時候,自動調用構造函數,當對象超出作用域的時候會自動調用析構函數。所以,在RAII的指導下,我們應該使用類來管理資源,將資源和對象的生命周期綁定。
2.智能指針(std::shared_ptr和std::unique_ptr)即RAII最具代表的實現,使用智能指針,可以實現自動的內存管理,再也不需要擔心忘記delete造成的內存泄漏。毫不夸張的來講,有了智能指針,代碼中幾乎不需要再出現delete了。
117、 手寫實現智能指針類
1.智能指針是一個數據類型,一般用模板實現,模擬指針行為的同時還提供自動垃圾回收機制。它會自動記錄SmartPointer<T*>對象的引用計數,一旦T類型對象的引用計數為0,就釋放該對象。除了指針對象外,我們還需要一個引用計數的指針設定對象的值,并將引用計數計為1,需要一個構造函數。新增對象還需要一個構造函數,析構函數負責引用計數減少和釋放內存。通過覆寫賦值運算符,才能將一個舊的智能指針賦值給另一個指針,同時舊的引用計數減1,新的引用計數加1
2.一個構造函數、拷貝構造函數、復制構造函數、析構函數、移走函數;
118、 結構體變量比較是否相等
1.重載了 “==” 操作符
struct foo { int a; int b; bool operator==(const foo& rhs) // 操作運算符重載 { return( a == rhs.a) && (b == rhs.b);} };
2.元素的話,一個個比;
3.指針直接比較,如果保存的是同一個實例地址,則(p1==p2)為真;
119、 位運算
若一個數m滿足 m = 2^n;那么k%m=k&(m-1);
位與相關性質和計算一個數的二進制表示中有多少個1的做法:
https://blog.csdn.net/qq_41687938/article/details/117324467
120、 函數調用過程棧的變化,返回值和參數變量哪個先入棧?
1、調用者函數把被調函數所需要的參數按照與被調函數的形參順序相反的順序壓入棧中,即:從右向左依次把被調函數所需要的參數壓入棧;
2、調用者函數使用call指令調用被調函數,并把call指令的下一條指令的地址當成返回地址壓入棧中(這個壓棧操作隱含在call指令中);
3、在被調函數中,被調函數會先保存調用者函數的棧底地址(push ebp),然后再保存調用者函數的棧頂地址,即:當前被調函數的棧底地址(mov ebp,esp);
4、在被調函數中,從ebp的位置處開始存放被調函數中的局部變量和臨時變量,并且這些變量的地址按照定義時的順序依次減小,即:這些變量的地址是按照棧的延伸方向排列的,先定義的變量先入棧,后定義的變量后入棧;
總結
以上是生活随笔為你收集整理的二十万字C/C++、嵌入式软开面试题全集宝典六的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Pytorch中参数和模型的保存与读取
- 下一篇: google地图 离线版 经纬度_一款钓