1.在以下聲明中哪一個表示“指向常量的指針”(指針指向的內容不能修改)?
A.char* const p
B.const char* p
C.char *p const
D.char const p
解析:
選B,const修飾的變量為常量,意味著不能修改
- A是常量指針,const修飾的是指針p本身了
- C語法錯誤
- D不是指針
2.關于智能指針的說法,哪些是正確的?
A.unique_ptr
可以共享所有權
B.shared_ptr
使用引用計數管理內存
C.weak_ptr
可以修改所指向的對象
D.auto_ptr
是C++17推薦使用的智能指針
解析:
選B,shared_ptr
是共享所有權的,通過引用計數機制跟蹤有多少個shared_ptr
指向同一對象,最后一個銷毀時釋放資源,但存在循環引用風險,可配合weak_ptr
解決,用make_shared<T>(args....)
創建,例如:std::shared_ptr<int> p1 = std::make_shared<int>(42);
,可以減少內存分配開銷
unique_ptr
是獨占所有權,不能被復制,只能移動(move()
),適用于獨占資源文件(如文件,網絡連接)weak_ptr
是shared_ptr
的弱引用,不擁有對象所有權,不能直接訪問對象,必須轉換為shared_ptr
(通過lock()
方法)才能訪問,主要用途是打破shared_ptr
的循環引用auto_ptr
已在C++11中被廢棄,由unique_ptr
取代
3.下列哪種情況不會產生野指針?
A.指針變量未初始化
B.指針指向的內存被釋放后繼續使用
C.指針指向棧上的局部變量,但變量已經離開作用域
D.使用make_shared<T>()
創建的shared_ptr
解析:
選D,D選項就是創建智能指針,智能指針是自動管理生命周期
- A指針變量不初始化,那指向的值就是隨機的
- B都被釋放了,還使用
- C局部變量離開作用域后,棧內存就被回收了
4.以下哪個聲明是指針數組(數組的元素是指針)?
A.int(*p)[10]
B.int *p[10]
C.int*(p[10])
D.int(*p[10])
解析:
選B和C,B和C是等價的,那個括號的調整優先級并沒有改變運算順序,[
的優先級高于*
,
- A是數組指針,表示指針p指向一個長度為n的一維數組
- D語法錯誤,補充以下可以表示函數指針數組
int (*p[10])(int, int); // p是一個數組,包含10個函數指針
5.關于void*
的類型轉換,以下哪個說法是正確的?
A.void*
可以直接賦值給任何類型的指針,不需要強制轉換
B.任何類型的指針都可以直接賦值給void*
,需要強制轉換
C.任何類型的指針都可以直接賦值給void*
,不需要強制轉換
D.void*
可以直接進行指針運算
解析:
選C,void*
是無類型指針,可以存儲任何類型指針的地址,但不能直接解引用或進行指針運算
6.關于引用作為函數參數,以下說法正確的是?
A.引用參數必須在函數調用時初始化
B.引用可以指向空值
C.const引用參數可以接收右值
D.引用參數比指針參數更占用內存
解析:
選C,const T&
可以綁定到右值(臨時對象、字面量等)
- A引用參數在函數定義時不需要初始化,而是在函數調用時由實參綁定
- B引用必須綁定到有效對象,不能為
nullptr
或空值(與指針不同) - D引用通常通過指針實現,兩者內存占用相同(在底層都是地址傳遞),但引用更安全
7.在32位系統中,假設int占4個字節,以下哪個結構體的大小是16字節?
A.struct {char c:int *p;char d;}
B.struct {int* p;char c;char d;}
C.struct {char c;char d;int *p;}
D.struct {int* p;int *q;}
解析:
- 個人覺得題目有問題,在32位系統中指針變量是4個字節,64位系統是8個字節(與類型無關),
- 如果正常按
int*
為4個字節,結構體大小計算就是向長看齊,編譯器會在成員之間插入空白字節,確保每個成員的地址是其大小的整數倍,char
是一個字節,A就是(1+3,4,1+3)=12,B就是(4,1,1+2)=8,C就是(1,1+2,4)=8,D是(4,4)=8,沒有一個對的上題目的 - 假如題目說的
int
占4個字節改變了的話,那把int*
看作8個字節,那么A是24,B是16,C是16,D是16,但這題是單選題,不明白(@_@)
8.下列哪種方式不能有效地檢查文件?
A.if(ptr!=NULL)
B.if(ptr)
C.if(ptr!=nullptr)
D.if(*ptr)
解析:
選D,這樣判斷如果ptr
是空指針,會報段錯誤
- A是檢查指針是否為
NULL
- B是隱式檢查指針是否為
NULL
- C是C++11空指針檢查
9.以下哪個是正確的函數指針聲明?假設要指向int foo(char,char)
A.int*fp(char,char)
B.int(*fp)(char,char)
C.int(fp)(char,char)
D.(*fp)(char,char)
解析:
選B,考察函數指針聲明格式
10.以下哪種情況不會導致內存泄漏?
A.new
分配的內存沒有對應的delete
B.循環引用的shared_ptr
C.使用unique_ptr
管理動態分配的資源
D.在異常發生時沒有釋放已分配的內存
解析:
選C,智能指針管理動態分配的資源,無需手動 delete
- A
new
和delete
必須成對使用,像malloc
和free
也是 - B
shared_ptr
使用引用計數,如果兩個對象互相持有對方的shared_ptr
,引用計數永遠不會歸零,導致內存無法釋放 - D如果
new
和delete
之間拋出異常,且未捕獲,則delete
不會執行
11.關于vector
的擴容機制,以下說法正確的是?
A.每次擴容固定增加一個元素的空間
B.一般情況下容量增長為原來的1.5倍或2倍
C.擴容時不會導致元素拷貝
D.擴容后原有的迭代器仍然有效
解析:
選B,通常按幾何增長(如 1.5 倍或 2 倍),而非固定增量
- A這樣性能太差了
- C擴容必須重新分配內存并拷貝所有元素(因為數組需要連續存儲)
- D擴容后,所有迭代器、指針、引用都會失效(因為內存地址變了)
12.下列哪個不是紅黑樹的特性?
A.根節點必須是黑色
B.紅色節點的子節點必須是黑色
C.每個葉節點(NIL)是黑色
D.紅色節點的父節點必須是紅色
解析:
選D
紅黑樹的5個特性:
-
節點是紅色或黑色(非黑即紅)。
-
根節點必須是黑色(A 選項正確)。
-
紅色節點的子節點必須是黑色(B 選項正確,即不能有連續的紅色節點)。
-
從任一節點到其每個葉子的所有路徑包含相同數量的黑色節點(黑高平衡)。
-
每個葉節點(NIL/NULL 節點)是黑色(C 選項正確)。
13.關于STL list的實現,以下說法錯誤的是?
A.每個節點包含前驅和后驅指針
B.在任意位置插入刪除的時間復雜度為O(1)
C.list
支持隨機訪問的迭代器
D.list
是一個循環雙向鏈表
解析:
選C,list
的迭代器是雙向迭代器,只能 ++/–,不支持隨機訪問(如 it + 5),鏈表的內存布局是非連續的,無法通過下標直接計算地址(如 vector 的 O(1) 隨機訪問)。訪問第 n 個元素需要從頭部開始遍歷 n 次,時間復雜度為 O(n)
14.關于deque
的內存布局,以下說法正確的是?
A.所有元素在內存中連續存儲
B.使用多個固定大小的數組塊來存儲元素
C.每個元素單獨分配內存
D.與vector
的內存布局完全相同
解析:
選B,使用多個固定大小的數組塊(如 map
管理塊指針)
deque
(雙端隊列)的核心特點:
分塊存儲:由多個固定大小的數組塊組成
邏輯連續:通過中央控制器(如指針數組)管理這些塊,對外表現為邏輯上的連續序列,但物理上不全連續,在同一數組塊中是連續的
動態擴展:可在頭尾高效插入/刪除- 由此,AC可以看出是錯的,
- D
vector
是嚴格連續內存,deque
是分塊存儲,布局完全不同
15.對于set容器,以下哪種說法是正確的?
A.通過哈希表實現元素唯一性
B.通過紅黑樹實現元素唯一性
C.插入重復元素會拋出異常
D.元素可以被修改
解析:
選B
- A
set
不使用哈希表(unordered_set
才用哈希表) - C插入重復元素時 不會拋出異常,而是被靜默忽略
- D
set
的元素是const
(不可直接修改,需先刪除再插入)
16.關于模板特化,以下說法正確的是?
A.偏特化可以應用于函數模板
B.必須先定義特化版本,再定義通用版本
C.特化版本必須在同一個命名空間
D.特化可以改變模板參數的數量
解析:
選D,特化可以增減或修改模板參數的數量(尤其是類模板的偏特化)
- 模板特化允許為特定類型或條件提供定制化的模板實現,分為:
全特化:為所有模板參數指定具體類型,參數數量必須與通用模板一致
偏特化:僅對部分模板參數指定類型或約束(僅適用于類模板),參數數量可以改變 - A函數模板不支持偏特化(僅支持全特化),但可通過重載實現類似功能
- B必須先定義通用版本,再定義特化版本(否則編譯器無法匹配通用模板)
- C特化版本可以位于不同的命名空間(但需顯式指定
template<>
)
17.以下哪個容器的迭代器不是雙向迭代器?
A.list
B.set
C.vector
D.map
解析:
選C,list
,set
,map
都是雙向迭代器,vector
是隨機訪問迭代器,隨機訪問迭代器是包含雙向迭代器的所有功能的,deque
也是隨機訪問迭代器
18.關于STL容器的線程安全性,以下說法正確的是?
A.vector
的多線程讀寫總是安全的
B.不同線程同時讀取同一容器是安全的
C.容器的所有操作都是線程安全的
D.所有容器操作都需要外部同步
解析:
選B,
- STL容器線程安全性:
讀操作(const 操作):多個線程同時讀取同一容器是安全的
寫操作(非 const 操作):必須保證同一時間只有一個線程修改容器,且不能與其他讀/寫操作并發
外部同步:STL 容器本身不提供內置鎖,需用戶自行加鎖(如std::mutex
)確保線程安全 - A
vector
的讀寫并發不安全,寫操作可能導致擴容,vector
擴容會重新分配內存并拷貝元素,其他線程的迭代器/引用會失效,并發修改同一元素是未定義行為 - C只有讀是安全的,
map
/set
的問題跟vector
一樣,list
修改不同節點可能安全(需確保節點不共享),但仍建議加鎖 - D讀操作無需同步,只有寫操作或讀寫混合時需要
- 想實現線程安全的話:
讀多寫少:使用讀寫鎖(std::shared_mutex
)
高頻修改:全局互斥鎖(std::mutex
)或并發容器(如 TBB 的concurrent_vector
)
19.map中哪個操作的平均時間復雜度不是O(log n)?
A.insert
B.find
C.erase
D.count_if
解析:
選D,count_if
是檢索所有符合條件的元素,需要遍歷所有元素,所以是O(n)
- 其他的
insert
(插入),find
(查找),erase
(刪除)都是O(log n)
20.關于allocator
,以下說法錯誤的是?
A.它將內存分配和對象構造分離
B.可以自定義內存分配策略
C.不能用于STL容器
D.支持異常處理
解析:
選C,STL 容器默認使用 std::allocator
,且支持自定義分配器(如 vector<int, MyAllocator<int>>
)
- allocator 是 C++ 標準庫中的 內存分配器,主要職責包括:
內存分配與對象構造分離:allocate()
:僅分配原始內存,construct()
:在已分配的內存上構造對象
自定義內存管理:允許用戶替換默認的內存分配策略(如池化分配、共享內存等)
異常安全:提供異常處理機制(如內存不足時拋出std::bad_alloc
) allocator
的使用:
#include <memory>
#include <iostream>int main() {std::allocator<int> alloc;// 分配內存int* p = alloc.allocate(1); // 僅分配,不構造// 構造對象alloc.construct(p, 42); // 在 p 處構造 int(42)std::cout << *p << std::endl; // 輸出 42// 銷毀并釋放alloc.destroy(p);//銷毀 p 處的對象alloc.deallocate(p, 1);//釋放內存return 0;
}
21.關于虛函數vtable,以下說法正確的是?
A.每個對象都有獨立的虛函數表
B.虛函數表在程序運行時動態創建
C.同一個類的所有對象共享同一個虛函數表
D.基類的虛函數表總是比派生類的小
解析:
選C,同一類的所有對象共享同一虛函數表(通過 vptr
指向它)
- 虛函數表
vtable
的機制:
虛函數表是 C++ 實現動態多態(運行時多態)的關鍵數據結構
共享性:同一個類的所有對象共享一個虛函數表(存儲在只讀數據段)
內容:虛函數表保存該類所有虛函數的地址(包括繼承的虛函數)
對象關聯:每個對象內部有一個隱藏的 虛指針(vptr
),指向其所屬類的虛函數表 - A虛函數表按類共享,非每個對象獨立(否則內存浪費嚴重)
- B虛函數表在 編譯期生成(非運行時動態創建)
- D派生類的虛函數表可能比基類大(新增虛函數)或相同(無新增)
22.在多重繼承中,以下哪種情況會產生多個虛表指針?
A.只繼承一個帶有虛函數的基類
B.繼承多個不帶虛函數的基類
C.繼承多個帶有虛函數的基類
D.虛繼承一個基類
解析:
選C,
- 多重繼承中的虛表指針(
vptr
)規則:
在 C++ 中,每個含有虛函數的基類(或虛繼承的基類)都會在派生類中生成一個獨立的 虛表指針(vptr
),用于指向對應的虛函數表(vtable
)
普通繼承(非虛繼承):若派生類繼承多個帶虛函數的基類,則會有多個vptr
(每個基類對應一個)
虛繼承:虛基類(無論是否含虛函數)會引入額外的vptr
或偏移量信息(編譯器實現相關) - A單繼承,就一個
vptr
- B沒虛函數,就沒有
vptr
- D虛繼承,也就額外多一個
vptr
23.在構造派生類對象時,構造函數的調用順序是?
A.派生類構造函數,基類構造函數,成員對象構造函數
B.基類構造函數,成員對象構造函數,派生類構造函數
C.成員對象構造函數,基類構造函數,派生類構造函數
D.基類構造函數,派生類構造函數,成員對象構造函數
解析:
選B
- 在構造派生類對象時,構造函數的調用順序遵循以下 固定順序:
基類構造函數(按繼承聲明順序,從左到右)
成員對象構造函數(按類定義中的聲明順序)
派生類自身的構造函數
例如(跑一下就知道了):
#include <iostream>
using namespace std;class Base {
public:Base() { cout << "Base Constructor" << endl; }
};class Member {
public:Member() { cout << "Member Constructor" << endl; }
};class Derived : public Base {Member m; // 成員對象
public:Derived() { cout << "Derived Constructor" << endl; }
};int main() {Derived d;return 0;
}
24.下列哪個運算符不能被重載?
A.operator+
B.operator[]
C.operator.
D.operator->
解析:
選C,
- C++中不能被重載的有:
成員訪問運算符.
成員指針訪問運算符.*
作用域解析運算符::
條件運算符?:
25.關于抽象類,以下說法錯誤的是?
A.抽象類不能實例化
B.純虛函數必須在派生類中實現
C.抽象類可以沒有純虛函數
D.抽象類可以包含構造函數
解析:
選C,在C++中抽象類至少包含一個純虛函數,其特性:
不能直接實例化:只能作為基類被繼承
純虛函數:用 = 0 聲明,強制派生類實現(除非派生類也是抽象類)
可以包含其他成員:如普通函數、構造函數、成員變量等
26.關于友元函數,以下說法正確的是?
A.友元關系可以被繼承
B.友元關系是雙向的
C.友元函數可以訪問類的私有成員
D.友元函數必須是類的成員函數
解析:
選C,友元函數是 C++ 中一種打破封裝性的特殊機制,允許非成員函數訪問類的私有(private
)和保護(protected
)成員,其特性包括:
訪問權限:友元函數可以訪問類的所有成員(包括私有和保護成員)
非成員性:友元函數不是類的成員函數(無 this 指針)
聲明方式:在類內部用 friend 關鍵字聲明
- A友元關系不能繼承,派生類不會自動成為基類友元
- B友元關系是單向的(類 A 聲明類 B 為友元,不意味著類 A 是類 B 的友元)
- D友元函數必須是非成員函數(或另一個類的成員函數)
27.以下哪種情況不會調用拷貝構造函數?
A.對象作為函數參數按值傳遞
B.函數按值返回對象
C.用一個對象初始化另一個對象
D.調用對象的成員函數
解析:
選D,
- 拷貝構造函數的調用場景:
用一個對象初始化另一個對象(直接初始化或拷貝初始化)
對象作為函數參數按值傳遞(傳遞時會拷貝一份實參)
函數按值返回對象(某些編譯器會優化,但理論上可能調用)
28.關于C++的動態綁定,以下說法正確的是?
A.所有成員函數都支持動態綁定
B.只有被聲明為virtual
的函數支持動態綁定
C.通過對象直接調用時發生動態綁定
D.靜態成員函數支持動態綁定
解析:
選B,
-
動態綁定是C++實現運行時多態的機制,其關鍵點包括:
虛函數(virtual
):只有聲明為virtual
的成員函數才能通過基類指針/引用調用派生類實現
觸發條件:必須通過 基類指針或引用 調用虛函數 -
靜態綁定(默認):非虛函數或直接通過對象調用時,在編譯期確定調用的函數
-
A只有虛函數支持動態綁定
-
C直接調用對象時是靜態綁定
-
D靜態成員函數無
this
指針,無法多態
29.在公有繼承中,基類的哪種成員在派生類中的類的訪問權限會發生改變?
A.public
成員
B.protected
成員
C.private
成員
D.所有成員的訪問權限都保持不變
解析:
選D,公有繼承表示派生類會保留基類成員的原始訪問權限
30.下列哪個不是設計接口類的正確做法?
A.所有成員函數都是純虛函數
B.提供公有的虛析構函數
C.包含非虛函數的實現
D.不定義非靜態數據成員
解析:
選C,接口類用于定義一組規范,不提供實現,實現為派生類來做
- A正確的,接口類的成員函數應都為純虛函數,由派生類實現
- B提供公有虛析構函數,確保通過基類指針刪除派生類對象時正確調用析構鏈
- D 常不定義非靜態數據成員(避免狀態綁定,保持純粹性)
31.以下哪種情況不適合使用右值引用?
A.實現移動構造函數
B.轉發函數參數
C.持有對象的永久使用
D.避免不必要的拷貝
解析:
選C.右值引用綁定到臨時對象,生命周期短暫,不適合長期持有
- 右值引用主要用于:
實現移動語義(如移動構造函數、移動賦值運算符)
完美轉發函數參數
避免不必要的拷貝(臨時對象或可移動資源的轉移) - A移動語義的核心應用,高效轉移資源
- B結合
std::forward
實現完美轉發 - C右值引用綁定到臨時對象,生命周期短暫,不適合長期持有
- D通過移動而非拷貝提升性能
32.關于std:move
,以下說法正確的是?
A.std:move
會自動移動對象的資源
B.被move
的對象不能繼續使用
C.std:move
實際上執行了資源的移動
D.std:move
僅進行類型轉換
解析:
選D,
- move核心行為是:
僅進行類型轉換:將左值強制轉換為右值引用(T&&
),不執行任何實際的資源移動
右值引用移動語義的觸發點:真正的資源移動由對象的 移動構造函數 或 移動賦值運算符 實現,而非std::move
- A
move
不實現移動邏輯 - B對象仍可使用,但狀態可能為空(取決于移動操作的實現)
- C不執行移動操作
33.以下哪個不是lambda表達式的有效捕獲方式?
A.[=]
捕獲所有變量的腳本
B.[&]
捕獲所有變量的引用
C.[this]
捕獲this指針
D.[static]
捕獲靜態變量
解析:
選D,lambda表達式不能直接捕獲靜態變量(靜態變量本身具有全局生命周期,無需捕獲)
- lambda表達式的捕獲方式:
捕獲方式 | 作用 |
---|---|
[=] | 以 值 捕獲所有外部變量(默認不可修改,需 mutable 修飾符) |
[&] | 以 引用 捕獲所有外部變量 |
[this] | 捕獲當前類的 this 指針(用于訪問成員變量/函數) |
[var] | 以值捕獲特定變量 var |
[&var] | 以引用捕獲特定變量 var |
[=, &var] | 默認值捕獲,但 var 以引用捕獲 |
mutable修飾符,修飾以值方式捕獲的變量,表示可以修改
34.auto
推導規則中,以下說法錯誤的是?
A.auto
會忽略引用類型
B.auto
會保留const
限定符
C.auto
可以推導出數組類型
D.auto
可以用于函數返回值
解析:
選A,auto
的推導規則與模板類型推導基本一致,如下:
表達式類型 | auto 推導結果 | 示例 |
---|---|---|
值類型(非引用) | 忽略引用和頂層 const | auto x = 42; → int |
引用類型(& ) | 保留引用 | auto& y = x; → int& |
常量引用(const& ) | 保留引用和 const | const auto& z = x; → const int& |
數組類型 | 退化為指針(除非用 auto& ) | auto arr = array; → int* |
函數返回值 | 支持(C++14 起) | auto foo() { return 42; } |
頂層
const
直接修飾 對象本身,在未顯式聲明時會被auto
忽略
底層const
修飾 指針或引用所指向的對象,表示指向的內容不可修改
- A默認會忽略掉,但顯式聲明會保留
- B顯式聲明會保留
- C
auto
默認將數組退化為指針,但auto&
可保留數組類型 - D(C++14 起)開始支持
35.實現完美轉發需要使用哪個類型?
A.std:move
B.std:forward
C.std:reference
D.std:ref
解析:
選B,完美轉發(Perfect Forwarding)
- 完美轉發的目標是:在函數模板中,將參數 原封不動(保留其值類別和
const
屬性)傳遞給其他函數
這需要兩點:
保持左值/右值屬性:左值仍為左值,右值仍為右值
避免不必要的拷貝:右值應觸發移動語義而非拷貝 - A
move
是強制轉換為右值,破壞了左值屬性 - B根據原始類型智能轉發左值/右值,
- C無該組件
- D生成
reference_wrapper
,用于按引用傳遞參數到std::bind
等場景
reference_wrapper
用于將引用包裝成可拷貝、可賦值的對象,主要用于需要在容器中存儲引用或傳遞引用到不允許直接使用引用的場景
例如:
#include <functional>
#include <vector>int a = 1, b = 2;
std::vector<std::reference_wrapper<int>> v = {a, b};
v[0].get() = 10; // 修改 a 的值
std::cout << a; // 輸出 10
std::bind
用于部分應用函數參數或重新綁定函數調用的參數順序。它將函數與部分參數綁定,生成一個新的可調用對象,綁定的參數默認按值捕獲,需用std::ref
或std::cref
傳遞引用
std::ref
或std::cref
用于生成 std::reference_wrapper 對象,將引用包裝為可拷貝、可賦值的類型。它們的主要用途是在需要按值傳遞參數的場景中保留引用的語義
std::ref
生成一個可修改的引用包裝器,std::cref
生成一個只讀的引用包裝器,多了個c可以理解為加了個const
36.以下哪種情況最適合使用weak_ptr
?
A.需要獨占資源所有權
B.解決循環引用問題
C.管理動態數組
D.共享資源所有權
解析:
選B,std::weak_ptr
就是設計用于打破 shared_ptr
的循環引用,避免內存泄漏。它不增加引用計數,僅通過 lock()
方法臨時獲取一個可用的 shared_ptr
- A應為
unique_ptr
- C應為
unique_ptr
- D應為
shared_ptr
37.關于noexcept
說法正確的是?
A.可以替代try-catch
B.編譯時檢查是否會拋出異常
C.運行時檢查是否會拋出異常
D.聲明函數不會拋出異常
解析:
選D,noexcept
是 C++11 引入的關鍵字,用于 聲明函數不會拋出異常
noexcept
有兩種形式:
noexcept
:函數保證不拋出任何異常
noexcept(expression)
:根據表達式結果(編譯期求值)決定是否可能拋出異常- A,B,C都同理,
noexcept
是聲明而非異常處理機制,無法捕獲異常,也無法拋出
38.以下哪個不能用constexpr修飾?
A.函數
B.構造函數
C.虛函數
D.變量
解析:
選C,constexpr
是 C++11 引入的關鍵字,用于聲明 編譯期可求值的常量或函數,在編譯器就能得到結果的函數和常量
- C虛函數依賴于運行時多態機制,與編譯期求值矛盾
39.關于std:atomic
,以下說法錯誤的是?
A.保證操作的原子性
B.可以避免所有的競態條件
C.提供內存有序選項
D.支持基本數據類型
解析:
選B,std::atomic
用于實現無鎖的原子操作,確保多線程環境下的數據安全訪問。但僅僅保證單次操作的原子性,復雜邏輯仍需其他同步機制(如互斥鎖)
原子操作 是編程中保證 不可分割性 的操作,即在多線程或并發環境中,一個操作要么 完全執行,要么 完全不執行,不會被其他線程的操作打斷
std::atomic
特性:
特性 | 說明 |
---|---|
原子性保證 | 對變量的操作(讀/寫/修改)是原子的 |
內存順序控制 | 支持 memory_order 選項(如 relaxed , seq_cst ) |
數據類型支持 | 支持基本類型(int , bool 等)和滿足條件的自定義類型 |
memory_order
是內存順序,std::atomic
提供了六種內存順序:
內存順序 | 作用 | 適用場景 |
---|---|---|
memory_order_relaxed | 無同步保證,只保證原子性 | 計數器、無依賴的簡單操作 |
memory_order_consume | 依賴加載(Depends-On 關系),較弱的順序保證(現代 CPU 很少使用) | 數據依賴的場景(如鏈表遍歷) |
memory_order_acquire | 加載操作,保證之后的讀寫不會被重排到它之前 | 讀端(Load)同步 |
memory_order_release | 存儲操作,保證之前的讀寫不會被重排到它之后 | 寫端(Store)同步 |
memory_order_acq_rel | 加載+存儲(如 fetch_add),兼具 acquire 和 release 語義 | Read-Modify-Write 操作 |
memory_order_seq_cst | 順序一致性(默認),最強保證,但性能較低 | 需要嚴格順序的場景 |
40.以下哪個不是C++標準庫提供的同步原語?
A.mutex
B.semaphore
C.critical_section
D.condition_variable
解析:
同步原語是指對線程操作的類型
選C,
- C++標準庫提供的同步原語:
同步原語 | 頭文件 | 說明 |
---|---|---|
std::mutex | <mutex> | 互斥鎖,提供基本的 lock() 和 unlock() 操作 |
std::semaphore | <semaphore> | 計數信號量(C++20 引入) |
std::condition_variable | <condition_variable> | 條件變量,用于線程間通知 |
std::atomic | <atomic> | 原子操作,支持無鎖編程 |
- C
critical_section
是Windows API 特有的
41.關于C++20協程,說法錯誤的是?
A.co_await
用于暫停協程執行
B.協程總是異步執行
C.協程可以有多個暫停點
D.協程需要編譯器支持
解析:
選B,
- 協程是一種可暫停和恢復的函數,其核心特性包括:
暫停與恢復:通過co_await
或co_yield
暫停執行,后續可恢復
無棧:依賴編譯器生成狀態機,不占用額外棧空間
編譯器支持:需要編譯器實現協程轉換
co_await
為異步等待,暫停當前協程,等待某個操作完成,后續由外部調度器決定何時恢復
co_yield
主要用于同步,暫停當前協程,并 返回一個值給調用者,后續由調用者決定是否恢復
- B 協程可以是同步也可以是異步
42.type_traits中,哪個不是類型萃取模板?
A.is_pointer
B.remove_reference
C.make_shared
D.enable_if
解析:
選C,
類型萃取模板(type_traits) 用于在編譯期 檢查或修改類型特性,,主要分為兩類:
類型檢查
類型轉換
- A
is_pointer
是編譯器檢查檢查類型是否為指針 - B
remove_reference
移除類型的引用 - C
make_shared
是用于創建智能指針shared_ptr
的,不是類型萃取模板 - D
enable_if
是條件化模板實例化,條件性地啟用或禁用函數模板或類模板的重載
43.以下哪種情況不適合使用RAll?
A.管理文件句柄
B.管理動態內存
C.管理全局變量
D.管理互斥池
解析:
選C,RAll
是通過 對象的生命周期 管理資源,全局變量的生命周期與程序相同,無需 RAII 管理釋放
- A
std::fstream
或自定義封裝類通過析構自動關閉文件 - B 智能指針是
RAll
的典型應用 - D
std::lock_guard
通過 RAII 自動釋放鎖
44.以下哪個特性不能用于編譯器計算?
A.constexpr
函數
B.template
元素編程
C.virtual
函數
D.static_assert
解析:
選C,虛函數依賴運行時多態(vtable
動態解析),無法在編譯期確定調用目標
- A 用于聲明 編譯期可求值的常量或函數,在編譯器就能得到結果的函數和常量
- B 模板在編譯期實例化
- D 編譯期靜態斷言,直接終止不符合條件的編譯
45.關于異常處理,以下說法正確的是?
A.noexcept
函數可以調用可能拋出異常的函數
B.異常對象總是通過引用捕獲
C.catch(....)
可以捕獲任何異常
D.構造函數中的異常不需要特殊處理
解析:
選C,catch(...)
是通配符,可捕獲所有異常(但無法直接訪問異常對象)
- A
noexcept
函數用于 聲明函數不會拋出異常,noexcept 函數內部調用可能拋異常的函數時,若異常未捕獲,程序會直接終止 - B異常對象可以按值、引用或指針捕獲(但按引用捕獲是最佳實踐)
- D 構造函數拋異常時,對象析構函數不會執行,需手動清理資源
46.關于進程狀態轉換,下列說法正確的是?
A.阻塞狀態下的進程可以直接轉換為運行狀態
B.就緒狀態的進程可以直接轉換為阻塞狀態
C.運行狀態的進程可以直接轉換為就緒狀態
D.結束狀態的進程可以轉換為就緒狀態
解析:
選C,
狀態 | 描述 | 允許轉換的目標 |
---|---|---|
運行(Running) | 進程正在 CPU 上執行指令 | → 就緒(時間片用完) → 阻塞(等待 I/O) |
就緒(Ready) | 進程已準備好運行,等待 CPU 調度 | → 運行(被調度) |
阻塞(Blocked) | 進程因等待某事件(如 I/O 完成)而暫停 | → 就緒(事件完成) |
結束(Terminated) | 進程執行完畢或被終止 | 無(生命周期結束) |
47.以下哪種同步機制最適合實現讀者-寫者問題?
A.信號量
B.互斥鎖
C.自旋鎖
D.原子操作
解析:
選A,
- 讀者-寫者問題要求:
并發性:允許多個讀者同時讀取數據
互斥性:寫者必須獨占訪問(讀寫互斥、寫寫互斥)
公平性:避免讀者或寫者饑餓 - A 可通過計數信號量實現讀者計數,配合互斥鎖保證寫者獨占
- B 僅支持完全互斥,無法區分讀寫操作,不能處理并發
- C 忙等待機制浪費 CPU,且無法直接支持讀者并發
- D 適用于簡單變量,無法管理復雜的讀寫隊列
48.下列哪個不是死鎖產生的必要條件?
A.互斥條件
B.請求和保持條件
C.循環等待條件
D.資源充足條件
解析:
選D,死鎖是指多個進程或線程因爭奪資源而互相等待,導致所有進程無法繼續執行的狀態
49.關于分頁機制,以下說法錯誤的是?
A.頁表項包含物理頁框號
B.頁面大小必須是2的冪
C.大頁面會增加內部碎片
D.TLB必須等到進程切換時才刷新
解析:
選D,
- 分頁機制的概念:
分頁是操作系統中管理內存的一種方式,將虛擬地址空間和物理內存劃分為固定大小的塊(頁和頁框)
頁表(Page Table):存儲虛擬頁到物理頁框的映射
頁面大小:通常為 2 的冪(如 4KB),便于地址計算
TLB(Translation Lookaside Buffer):緩存頁表項,加速地址轉換
虛擬地址拆分:虛擬頁號 = 地址 / 頁面大小 → 位運算優化(
>>
代替除法)
- C 大頁面可能導致未用內存浪費(如分配 2MB 但只用 1KB)
- D
TLB
可因頁表更新(如 invlpg 指令)或 ASID 機制即時刷新
50.在MacOS的文件系統中,以下哪個說法正確?
A.目錄文件和普通文件使用不同的數據結構
B.硬鏈接可以跨文件系統
C.inode存儲文件的數據內容
D.一個文件可以有多個硬鏈接
解析:
選D,
- macOS 文件系統基礎:
inode
:存儲文件的元數據(權限、所有者、大小等),不直接存儲文件內容
硬鏈接(Hard Link):多個文件名指向同一個inode
符號鏈接(Symbolic Link):類似快捷方式,存儲目標文件路徑 - A 目錄和普通文件在
inode
結構上類似,只是目錄文件內容為子項列表 - B 硬鏈接必須在同一個文件系統內(因
inode
編號是文件系統局部的) - C
inode
存儲元數據,數據內容存儲在磁盤塊中 - D 鏈接允許多個路徑指向同一
inode
51.關于中斷處理,以下哪個步驟是錯誤的?
A.保存當前進程上下文
B.執行中斷服務程序
C.允許更高優先級中斷
D.中斷處理時啟動新進程
解析:
選D,
- 中斷處理步驟:
中斷發生:硬件或軟件觸發中斷信號
保存上下文:CPU保存當前進程的上下文(包括程序計數器、寄存器狀態等),以便后續恢復
屏蔽中斷(可選):在某些情況下,可能會暫時屏蔽其他中斷,防止嵌套中斷帶來的復雜性
執行中斷服務程序(ISR):運行特定的中斷處理代碼
允許更高優先級中斷(可選):在ISR執行過程中,可以允許更高優先級的中斷嵌套
恢復上下文:中斷處理完成后,恢復之前保存的上下文
返回原程序:CPU繼續執行被中斷的程序 - D 啟動新進程通常延遲到中斷處理完成后
52.關于系統調用實現,下列說法正確的是?
A.用戶程序可以直接調用系統調用處理程序
B.系統調用號在內核空間分配
C.系統調用參數必須通過寄存器傳遞
D.系統調用可以通過軟中斷實現
解析:
選D,
系統調用的基本概念:
系統調用(System Call)是用戶程序與操作系統內核交互的接口。用戶程序通過系統調用請求內核提供的服務(如文件操作、進程控制等)。由于用戶程序運行在用戶態,而內核運行在內核態,系統調用需要一種安全的機制來實現用戶態到內核態的切換
系統調用的實現方式:
觸發機制:從用戶態轉換到內核態:
傳統方式:通過軟中斷(如x86
的int 0x80
(int
是中斷指令,0x80
是中斷號128))
現代方式:通過專門的指令(如x86的syscall/sysenter)
系統調用號:用于標識具體的系統調用(如read、write)
系統調用號在用戶空間和內核空間共享定義(如通過頭文件)
參數傳遞:
通常通過寄存器傳遞系統調用號和參數
參數較多時可能會通過內存(如結構體指針)
內核處理:
內核根據系統調用號跳轉到對應的處理函數
執行完成后返回用戶態
- A 系統調用處理程序(內核中的函數)運行在內核態,需要先從用戶態切換內核態
- B 系統調用號不是動態分配的,而是靜態定義的
- C 也可以用內存
53.以下哪種IPC方式無法跨機器通信?
A.管道
B.消息隊列
C.共享內存
D.套接字
解析:
選A,B,C, 都是本地 IPC 機制,無法跨機器通信
IPC(進程間通信)
- A 管道(
Pipe
)特點:
半雙工通信(數據單向流動)
基于內核緩沖區,數據由內核管理
有兩類,匿名管道(Unnamed Pipe
)和命名管道(FIFO
),匿名管道(Unnamed Pipe
)是單向的,只能在有親緣關系的進程間通信(父子進程或兄弟進程之間的通信);命名管道(FIFO
)以磁盤文件的方式存在,可以實現本機任意兩個進程通信 - B 消息隊列特點:
進程可以向隊列發送/接收結構化消息
消息存儲在內核,進程無需直接同步
消息隊列是消息的鏈接表,包括Posix
消息隊列和System V
消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩沖區大小受限等缺點 - C 共享內存特點:
多個進程映射同一塊物理內存,通信速度最快
需要同步機制(如信號量)避免競爭
共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信 - D 套接字(socket)特點:
全雙工通信,支持 TCP/UDP
可用于不同機器間的進程通信(網絡通信)
IPC(進程間通信)通信方式:
IPC方式 | 特點 | 適用場景 | 優點 | 缺點 |
---|---|---|---|---|
匿名管道(Unnamed Pipe ) | 1.半雙工,單向通信 2.僅適用于父子進程或兄弟進程之間的通信 3.數據流式傳輸 4.隨進程結束而銷毀 | 1.本地進程間簡單數據傳輸 2.父子進程間簡單數據傳遞 | 1.簡單易用 2.內核緩沖,無需額外同步 | 1.只能用于有親緣關系的進程 2.不能隨機訪問數據 3.速度慢,容量有限 |
命名管道(FIFO ) | 1.有名字,文件系統可見 2.可實現本地任意進程通信 3.半雙工 4.可長期存在(除非顯式刪除 unlink() ) | 本地多進程通信(如日志收集) | 1.比匿名管道更靈活 2.可長期存在 | 數據流式,不支持結構化消息,不能隨機訪問數據,速度慢,容量有限 |
消息隊列(Message Queue ) | 1.內核維護的隊列 2.支持結構化消息 3.可設置優先級 | 1.進程間異步通信(如任務調度) 2.需要可靠傳輸的場景 | 1.解耦生產者和消費者 2.支持消息持久化 | 1.性能較低(相比共享內存) 2.容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題 |
共享內存(Shared Memory ) | 1.多個進程映射同一塊物理內存 2.無需內核拷貝,速度最快 3.需同步機制 | 1.高性能數據共享(如數據庫、科學計算) 2.大塊數據頻繁讀寫 | 1.極快的通信速度 2.適合大規模數據交換 | 需要額外同步(如信號量) |
信號(Signal ) | 1.內核向進程發送異步通知 2.輕量級,無數據負載 | 1.進程控制 2.異常處理 | 1.即時性強 2.占用資源極少 | 1.不能傳遞復雜數據 2.可能丟失或沖突 |
信號量(Semaphore ) | 1.用于進程間同步 2.控制對共享資源的訪問 | 1.防止競爭條件(如多進程寫文件) 共享內存的同步 | 1.保證原子操作 2.適用于多進程協作 | 僅用于同步,不直接傳輸數據 |
套接字(Socket ) | 1.全雙工通信 2.支持本地進程/遠程進程通信 | 1.跨機器通信 2.本地高性能IPC | 1.最通用的IPC方式 2.支持跨機器和復雜協議 | 1.比共享內存/管道慢 2.需要處理網絡問題 |
54.哪種調度算法可能會導致進程饑餓?
A.輪轉調度
B.先來先服務
C.最短作業優先
D.多級反饋隊列
解析:
選C,
進程饑餓(Starvation) 是指某些進程長期得不到CPU資源,無法執行的現象。通常發生在非搶占式或優先級固定的調度算法中
- A 每個進程分配固定的時間片,時間片用完就切換到下一個進程,因為所有進程輪流執行,保證公平性
- B 按進程到達順序執行,當前進程運行完畢才調度下一個,可能導致長作業阻塞短作業,但不會出現新的進程搶占的情況
- C 優先執行預計運行時間最短的進程,如果有新的短作業到來,長作業可能永遠得不到執行
- D 結合了時間片輪轉和動態優先級調整:
新進程進入最高優先級隊列(短時間片)
如果進程用完時間片仍未結束,則降低優先級(進入更低級隊列,時間片更長)
低優先級隊列的進程仍有機會執行(避免完全饑餓)
55.關于虛擬內存,以下說法錯誤的是?
A.可以運行大于物理內存的程序
B.支持進程間內存隔離
C.所有頁面都必須有對應的物理頁
D.可以共享物理內存頁
解析:
選C,
虛擬內存的實現方式:
分頁(Paging):內存劃分為固定大小的頁(如4KB)
頁表(Page Table):記錄虛擬頁→物理頁的映射關系
頁面置換(Swapping):將不常用的頁換出到磁盤
- A 虛擬內存的核心作用就是讓程序認為它有連續的、足夠大的內存空間,即使物理內存不足;通過頁面置換(將部分數據暫存到磁盤),程序可以訪問比物理內存更大的地址空間
- B 每個進程有獨立的虛擬地址空間,頁表確保進程A無法直接訪問進程B的內存
- C 虛擬頁有三種狀態:已映射到物理內存(有對應物理頁);未分配(尚未使用,無物理頁);已換出到磁盤(對應物理頁被回收,數據在交換區)
56.在緩存一致性協議中,以下哪種狀態不屬于MESI協議?
A.Modified
B.Exclusive
C.Shared
D.Reserved
解析:
選D,
MESI協議(Modified, Exclusive, Shared, Invalid)是一種緩存一致性協議,用于多核CPU中維護多個核心的緩存數據一致性。每個緩存行(Cache Line)會處于以下四種狀態之一:
狀態 | 描述 |
---|---|
M (Modified ) | 緩存行已被修改(與主存不一致),當前核心獨占該數據 |
E (Exclusive ) | 緩存行與主存一致,且當前核心獨占該數據(其他核心無副本) |
S (Shared ) | 緩存行與主存一致,但可能被多個核心共享(多副本存在) |
I (Invalid ) | 緩存行無效(數據已過期或未被緩存) |
- D 不屬于MESI
57.關于動態鏈接,下列說法正確的是?
A.動態鏈接庫必須在程序啟動時加載
B.動態連接會增加程序的內存占用
C.動態鏈接的地址解析發生在運行時
D.動態鏈接庫不能被多個程序共享
解析:
選C,
動態鏈接(Dynamic Linking)是指程序在運行時才加載和鏈接所需的庫(如.so或.dll文件),與靜態鏈接(編譯時直接嵌入代碼)相對。其核心特點包括:
運行時加載:庫的加載可以延遲到實際使用時(如dlopen())
內存共享:多個程序可共享同一份庫的物理內存
地址延遲綁定:符號(函數/變量)的地址在運行時解析(通過PLT/GOT)
- A 必須錯了,動態鏈接庫可以按需加載
- B 動態鏈接減少內存占用,因為多個程序可共享同一庫的物理內存;靜態鏈接時,每個程序獨立包含庫代碼,內存占用更高
- D 動態庫是同一庫的代碼段在內存中只有一份副本,被多個進程映射
靜態鏈接對比動態鏈接:
特性 | 靜態鏈接 | 動態鏈接 |
---|---|---|
內存占用 | 高(每個程序獨立副本) | 低(共享庫代碼) |
磁盤占用 | 大(可執行文件包含庫代碼) | 小(可執行文件僅存引用) |
更新維護 | 需重新編譯程序 | 替換庫文件即可 |
加載時機 | 編譯時完成鏈接 | 運行時延遲綁定 |
58.ELF文件中,哪個段(Section)包含未初始化的全局變量?
A..text
B..data
C..bss
D..rdata
解析:
選C,
ELF(Executable and Linkable Format)是 Linux/Unix 系統下的可執行文件、目標文件和共享庫的標準格式
- A
.text
作用:存儲可執行代碼(函數、指令),占用文件空間 - B
.data
作用:存儲已初始化的全局/靜態變量,占用文件空間 - C
.bss
作用:存儲未初始化的全局/靜態變量,不占用磁盤空間(僅記錄大小,運行時由 OS 分配零頁內存),提升文件存儲效率(無需存儲全零數據) - D
.rdata
作用:存儲只讀數據(如字符串常量、const 變量)
59.關于程序的堆棧,以下說法錯誤的是?
A.棧向低地址方向增長
B.堆向高地址增長
C.棧空間自動回收
D.堆空間自動回收
解析:
選D,
堆(Heap
)和棧(Stack
)的特性:
特性 | 棧(Stack ) | 堆(Heap ) |
---|---|---|
增長方向 | 通常向低地址增長 | 通常向高地址增長 |
管理方式 | 編譯器自動管理(壓棧/彈棧) | 程序員手動管理(malloc /free ) |
分配速度 | 快(只需移動棧指針) | 慢(需查找合適內存塊) |
生命周期 | 函數調用結束自動回收 | 需顯式釋放(否則內存泄漏) |
碎片問題 | 無碎片 | 可能產生碎片 |
- D 堆需手動釋放
60.以下哪個不是Windows驅動程序的特征?
A.可以動態加載和卸載
B.運行在用戶空間
C.可以訪問內核函數
D.需要特權級別執行
解析:
選B,
Windows 驅動程序的特性:
特性 | 說明 |
---|---|
動態加載/卸載 | 可通過服務管理器(sc命令)或API動態加載(.sys 文件) |
運行空間 | 運行在內核空間(Ring 0),而非用戶空間(Ring 3) |
內核函數訪問 | 可直接調用內核API(如IoCreateDevice )和硬件資源(如端口/內存映射) |
特權級別 | 需在特權模式(Ring 0)執行,擁有最高權限 |
用戶空間(User Mode):運行普通應用程序(如記事本),受限訪問硬件
內核空間(Kernel Mode):運行驅動程序/內核,可執行特權指令
驅動必須在內核空間:否則無法直接操作硬件或調用內核API
Windows驅動類型:
驅動類型 | 運行空間 | 典型用途 |
---|---|---|
WDM | 內核空間 | 傳統硬件驅動(如USB/PCIe) |
KMDF | 內核空間 | 現代硬件驅動(微軟推薦) |
UMDF | 用戶空間 | 攝像頭/打印機等非關鍵驅動 |
61.關于MSVC的優化級別,下列說法正確的是?
A.O2
優化級別會破壞調試信息
B.Od
完全禁用編譯優化
C.Ox
總是會產生更快的代碼
D.O1
優化目標是最小的代碼體積
解析:
選B,題目為單選,B最準確
優化選項 | 作用 | 調試信息影響 | 典型用途 |
---|---|---|---|
Od | 完全禁用優化(默認調試配置) | 保留完整調試信息 | 調試階段 |
O1 | 優化目標為 最小代碼體積(等價于 Os + 其他空間優化) | 部分調試信息可能丟失 | 嵌入式/資源受限環境 |
O2 | 優化目標為 最大執行速度(等價于 Ot + 內聯等速度優化) | 調試信息可能不精確 | 發布版本(性能優先) |
Ox | 激進優化(類似 O2 ,但包含更多實驗性優化,如更積極的內聯) | 調試信息嚴重受損 | 極致性能場景 |
Os | 優化目標為 最小代碼體積(與 O1 類似,但策略更保守) | 部分調試信息可能丟失 | 對代碼大小敏感的場景 |
- A
O2
會降低調試信息可用性(如變量被優化掉、行號不準確),但并非完全破壞 - B
Od
是 “Disable Optimization” 的縮寫,確保代碼逐行按源碼執行,不進行任何優化 - C
Ox
是激進優化,但不保證總是更快,可能因過度內聯導致緩存抖動,某些情況下O2
反而更穩定(如避免寄存器溢出) - D
O1
明確優先 最小化代碼大小(而非速度),適用于嵌入式或磁盤/內存敏感場景(實現方式:減少內聯、優化分支布局、合并相同代碼塊等)
調試信息與優化的關系:
- 優化級別越高,調試信息越不可靠
- 變量可能被優化掉:未使用的局部變量直接刪除
- 代碼順序重排:斷點可能跳轉到意外位置
62.關于PE文件的導出表,以下說法錯誤的是?
A. .edata
段包含所有導出符號信息
B. 適用/STRIP
可以移除調試符號
C. 動態鏈接庫必須保留所有符號
D. 同名符號根據加載順序解析
解析:
選C,
PE文件導出表(Export Table)是 PE(Portable Executable) 文件中用于聲明動態鏈接庫(DLL)對外提供的函數/變量的結構,主要包含以下信息:
導出函數名(Name)
導出序號(Ordinal)
函數地址(RVA)
前向鏈接(Forwarder)
導出表通常存儲在.edata
段
- A 正確的,不過編譯器優化后可能將導出表合并到
.rdata
或其他段以節省空間 - B
/STRIP
是鏈接器選項(如link /STRIP:DEBUG
),用于移除調試符號(如.debug$
段),注意導出符號(用于動態鏈接)不會被移除,僅影響調試信息 - C DLL可以選擇性導出符號,通過
__declspec(dllexport)
或.def
文件顯式指定導出函數,未導出的符號對其他模塊不可見 - D 當多個DLL導出同名符號時,優先使用最先加載的DLL中的符號
__declspec(dllexport)
:標記需導出的函數/變量
.def
文件:精確指定導出符號和序號
63.在Windows系統中,調試器主要通過什么API實現對目標程序的控制?
A.CreateProcess()
B.DebugActiveProcess()
C.WaitForDebugEvent()
D.VirtualProtectEx()
解析:
選C,
API | 作用階段 | 是否核心控制API | 典型用途 |
---|---|---|---|
CreateProcess() | 調試會話初始化 | 僅啟動 | 以調試模式啟動新進程 |
DebugActiveProcess() | 調試會話初始化 | 僅附加 | 附加到已有進程 |
WaitForDebugEvent() | 調試事件處理 | 是 | 接收并處理斷點、異常等事件 |
VirtualProtectEx() | 內存操作輔助 | 非核心 | 修改內存權限以設置斷點或補丁 |
- A 作用:創建新進程,可指定調試標志(
DEBUG_PROCESS
或DEBUG_ONLY_THIS_PROCESS
) - B 作用:將調試器附加到已運行的進程
- C 作用:阻塞等待調試事件(如斷點、異常、DLL加載等),調試器的主事件循環依賴此API,是實際控制目標進程執行的關鍵
- D 作用:修改目標進程內存的保護屬性(如設為可寫/可執行),用于臨時修改內存權限(如設置軟件斷點
INT3
時需先將代碼段設為可寫)
調試器的工作流程:
- 初始化調試會話:
通過CreateProcess(DEBUG_PROCESS)
或DebugActiveProcess()
綁定目標進程 - 事件循環:
調用WaitForDebugEvent()
等待事件(如EXCEPTION_DEBUG_EVENT
) - 控制執行:
根據事件類型決定是否暫停、單步或繼續(ContinueDebugEvent()) - 輔助操作:
使用ReadProcessMemory
/WriteProcessMemory
修改內存,VirtualProtectEx
調整權限等
64.使用反匯編工具時,下列哪種情況會導致反匯編結果不準確?
A.代碼段中包含數據
B.函數使用了內聯
C.程序開啟了優化
D.使用了動態鏈接
解析:
選A,
- 反匯編工具(如IDA Pro、Ghidra、objdump)通過解析二進制文件的指令流,將機器碼轉換為匯編代碼。其準確性依賴于:
正確區分代碼與數據:指令和數據在二進制中均為字節流,需通過上下文或元數據區分
識別控制流結構:如函數調用、跳轉目標等
處理編譯器優化:優化可能改變指令順序或邏輯 - A 代碼段(如
.text
)中混入數據(如跳轉表、字符串常量)時,反匯編工具會錯誤地將數據當作指令解析 - B 內聯(
Inline
):編譯器將函數體直接嵌入調用處,反匯編結果仍為有效指令,只是邏輯結構更扁平化,反匯編工具能正確顯示內聯后的指令,但可能丟失原始函數邊界信息(需調試符號輔助) - C 優化影響邏輯可讀性,如指令重排、冗余代碼刪除等,可能改變反匯編的邏輯順序,但指令本身仍準確
- D 動態鏈接庫(
DLL
/SO
):外部函數調用通過PLT
/GOT
解析,反匯編工具能正確識別占位指令,需注意若反匯編時未加載依賴庫,外部函數名顯示為地址(如sub_401000
),但指令本身解析正確
65.以下哪個不是代碼靜態分析工具的主要功能?
A.檢測內存泄漏
B.發現未初始化變量
C.檢查代碼風格
D.識別死鎖風險
解析:
選D,
靜態分析工具(如 Clang Static Analyzer、Coverity、SonarQube)通過解析源代碼(不實際運行程序)來發現潛在問題,主要功能如下表
功能分類 | 典型問題 |
---|---|
內存安全 | 內存泄漏、緩沖區溢出、懸垂指針 |
代碼邏輯 | 未初始化變量、空指針解引用、除零錯誤 |
并發安全 | 數據競爭、死鎖風險、原子性違反 |
代碼規范 | 風格檢查(如命名規范)、冗余代碼、魔法數字 |
安全漏洞 | SQL注入、硬編碼密碼、不安全的API調用 |
- A 工具通過數據流分析追蹤內存分配(
malloc
)與釋放(free
)的匹配性 - B 分析變量在所有控制流路徑上的初始化狀態
- C 通過規則集檢查縮進、命名等風格問題
- D 死鎖識別是需要實際運行程序,觀察線程的鎖獲取順序
66.在LLDB中,哪個命令不能在程序運行時使用?
A.break
B.print
C.run
D.backtrace
解析:
選C,
LLDB(Low-Level Debugger)是 macOS 和 iOS 開發的默認調試器,支持以下主要操作階段:
啟動程序前:設置斷點、環境變量等
程序運行中:查看變量、調用棧、控制執行流
程序暫停時:修改內存、單步調試等
- A 設置斷點
- B 打印信息,可以在調試時
- C 啟動程序,調試時程序已經啟動,不能使用
- D 顯示當前線程的調用棧
67.關于堆棧回溯(Stack Trace),以下說法正確的是?
A.優化會影響堆棧回溯的準確性
B.必須有調試符號才能回溯
C.只能在程序崩潰時獲得
D.回溯信息總是完整的
解析:
選A
堆棧回溯(Stack Trace)是調試和崩潰分析中的重要工具,用于顯示函數調用鏈(即當前執行路徑上的所有嵌套調用)。其生成依賴以下信息:
棧幀(Stack Frame):每個函數調用時壓入棧的返回地址、局部變量等
調試符號(Debug Symbols):將地址映射到函數名和源碼位置(如.pdb
或.dwarf
文件)
棧完整性:棧內存未被破壞時才能正確解析
- A 編譯器優化(如內聯、尾調用優化)會改變棧幀結構,比如:
內聯(Inline
):消除函數調用,導致缺失中間幀
幀指針省略(-fomit-frame-pointer
):使回溯依賴調試符號或啟發式規則
尾調用優化(TCO):直接復用當前棧幀,跳過中間調用 - B 無符號時仍可回溯,通過棧指針(
SP
)和程序計數器(PC
)解析地址(顯示為0x401000
而非main
); 工具如addr2line
可將地址映射到源碼(需保留符號表.symtab
) - C 主動獲取方式:調試器命令(如
bt
、backtrace
);代碼中調用庫函數(如libunwind
、backtrace()
) - D 不一定,回溯的完整性取決于棧結構完整性和符號信息可用性
不完整的常見原因:
棧破壞:緩沖區溢出覆蓋返回地址
優化:尾調用或內聯導致中間幀丟失
動態代碼:JIT
或動態加載庫的棧幀可能無法解析
JIT
(Just-In-Time Compilation,即時編譯) 是一種動態編譯技術,在程序運行時將中間代碼(如字節碼)實時編譯成機器碼執行,兼具解釋執行的靈活性和原生代碼的高效性
68.關于硬件斷點,下列說法錯誤的是?
A.數量受CPU寄存器限制
B.可以監控內存讀寫
C.不會影響程序性能
D.可以設置在任意內存位置
解析:
選C
硬件斷點是通過 CPU 的調試寄存器(Debug Registers) 實現的,其特點包括:
直接硬件支持:由 CPU 硬件觸發,不修改目標代碼
功能類型:執行斷點(Instruction Execution)和內存讀寫監控(Data Read/Write)
數量限制:受 CPU 架構限制(如 x86 僅有 DR0-DR3 共 4 個斷點寄存器)
- A 正確,有幾個斷點寄存器,就最多只能設幾個斷點,軟件調試斷點是無數量限制的
- B 正確,硬件斷點支持三種模式:執行,寫入(監控內存修改),讀寫(監控內存訪問)
- C 錯誤,硬件斷點有性能開銷:CPU 需在每條指令執行后檢查調試寄存器條件,監控內存讀寫時,額外檢查每次內存訪問
- D 正確,滿足內存對齊要求即可
硬件斷點和軟件斷點比較:
特性 | 硬件斷點 | 軟件斷點 |
---|---|---|
實現方式 | CPU 調試寄存器 | 修改目標代碼為 0xCC |
數量限制 | 極少(x86 為 4 個) | 理論上無限制 |
性能開銷 | 持續檢查,影響較大 | 僅命中時暫停 |
內存監控 | 支持 | 不支持 |
修改代碼 | 不需要 | 需要(可能觸發代碼校驗) |
69.以下哪個工具不適用于Windows下CPU性能分析?
A.Windows Performance Analyzer(WPA)
B.Visual Studio Profiler
C.Process Monitor
D.Inter VTune Profiler
解析:
選C,
- A 是微軟官方工具,專用于分析
ETW
(Event Tracing for Windows) 記錄的性能數據;
支持CPU分析:查看上下文切換、CPU占用率、熱點函數等 - B 集成在Visual Studio中的性能分析工具,支持:采樣分析(Sampling),插樁分析(Instrumentation),并發分析(Concurrency)
CPU分析功能:檢測函數耗時、調用關系、熱點代碼 - C 用途是監控 文件系統、注冊表、進程/線程活動,不直接分析CPU性能
主要功能為記錄文件讀寫、注冊表訪問、進程啟動/退出等,用于調試文件/注冊表問題,而非CPU性能分析 - D 是Intel 提供的 高級CPU性能分析工具(可以跨平臺),支持:
硬件事件采樣(如緩存命中率、分支預測失敗)
熱點分析(函數/指令級耗時)
線程并發分析
工具 | 主要用途 |
---|---|
Windows Performance Analyzer(WPA) | ETW數據分析(CPU/內存/磁盤) |
Visual Studio Profiler | 函數級性能分析(采樣/插樁) |
Process Monitor | 文件/注冊表/進程活動監控 |
Inter VTune Profiler | 硬件級CPU性能分析(緩存/流水線) |
ETW(Event Tracing for Windows)是 Windows內核級的事件追蹤系統,用于高效記錄系統/應用的運行時行為(如CPU調度、磁盤I/O、網絡活動等)
70.關于代碼覆蓋率測試,以下說法正確的是?
A.語句覆蓋率100%意味著代碼完全測試
B.分支覆蓋率要求測試所有條件組合
C.路徑覆蓋率要求測試所有可能路徑
D.函數覆蓋率只統計函數是否被調用
解析:
選C,
- A 不檢查分支邏輯,可能漏測else的條件
- B 分支覆蓋率 僅要求覆蓋每個分支的真/假,不對分支判斷的條件組合全部測試
- C 路徑覆蓋率 是最高級別的覆蓋標準,要求覆蓋所有可能的執行路徑(包括循環、嵌套分支的組合)
- D 函數覆蓋率 是最基礎的指標,僅檢查函數是否被調用過,不關心內部邏輯或參數組合,可能會由于函數對于不同參數有分支而導致漏測
71.關于棧緩沖區溢出,以下說法正確的是?
A.strcpy
總是安全的字符串拷貝函數
B.可以通過溢出修改返回地址
C.DEP機制可以完全防止緩沖區溢出
D.棧溢出只影響局部變量
解析:
選B
棧緩沖區溢出是指 向棧上的緩沖區寫入超過其分配大小的數據,導致覆蓋相鄰內存(如返回地址、局部變量、函數參數等)。其危害包括:
控制流劫持:覆蓋返回地址,執行任意代碼
數據篡改:修改局部變量或函數指針
繞過安全機制:如繞過身份驗證或權限檢查
- A
strcpy
不檢查目標緩沖區大小,若源字符串長度超過目標緩沖區,必然導致溢出 - B 正確,溢出緩沖區,會覆蓋棧上的 返回地址(位于緩沖區下方)這是一種典型的攻擊手段
- C DEP(Data Execution Prevention) 的作用:標記內存頁為不可執行(
NX 位
),防止執行棧上的代碼
無法阻止 數據篡改(如覆蓋返回地址為已有函數地址,即ROP
攻擊);可被 代碼復用攻擊(ROP/JOP
) 繞過 - D 棧溢出可影響:局部變量(相鄰變量被覆蓋),函數參數(調用者傳入的參數),返回地址(導致控制流劫持),棧幀指針(
EBP/RBP
)(影響棧展開)
防御棧溢出的技術:
技術 | 作用 |
---|---|
DEP/NX | 禁止執行棧內存 |
ASLR | 隨機化內存布局,增加預測地址難度 |
Stack Canary | 在返回地址前插入校驗值,溢出時檢測 |
Control Flow Integrity (CFI) | 驗證控制流轉移的合法性 |
72.以下哪種格式化字符串用法最安全?
A.printf(user_input);
B.printf(“%s”,user_input);
C.printf(string.c_str());
D.printf(“%s%s”,str1,str2);
解析:
選D,題目說最安全,相比B,D更好些
73.關于棧保護(Stack Canary),以下說法錯誤的是?
A.在函數序言部分插入隨機值
B.可以防止任意地址寫入
C.在函數返回前檢查隨機值
D.編譯時使用-fstack-protector
開啟
解析:
選B,棧保護(Stack Canary
)主要是防止棧緩沖區溢出覆蓋返回地址,不保護其他內存區域
74.關于地址空間布局隨機化ASLR,下列說法正確的是?
A.可以隨機化所有內存段
B.必須重新編譯程序才能使用
C.完全消除了緩沖區溢出風險
D.增加了攻擊難度但不能完全防御
解析:
選D,
- A 所有不太對,某些固定地址區域還是不能的
- B
ASLR
是運行時特性,由操作系統加載器實現,無需重新編譯 - C 不能
75.堆噴射主要利用了什么特點?
A.堆內存連續分配
B.堆塊大小固定
C.堆內存可執行
D.堆地址可預測
解析:
選D,噴射的核心是 通過大量分配提高地址命中概率
堆噴射(Heap Spraying)是一種攻擊技術,通過 大量分配包含惡意代碼的堆內存塊,結合內存漏洞(如UAF、類型混淆),提高攻擊者控制的代碼或數據位于 可預測地址 的概率
76.關于沙箱技術,以下說法錯誤的是?
A.可以限制系統調用
B.完全阻止所有惡意行為
C.可以限制文件系統訪問
D.可以限制網絡訪問
解析:
選B
沙箱技術是一種安全機制,用于在隔離的環境中運行未信任的程序或代碼,以限制其對系統資源的訪問
- A 沙箱通常通過攔截或限制系統調用來控制程序對底層資源的訪問
- B 沙箱并不能完全阻止所有惡意行為。例如,它可能無法防范邏輯漏洞、側信道攻擊或某些逃逸技術(如沙箱逃逸)。沒有絕對的安全,沙箱也有其局限性
- C 沙箱可以限制對文件系統的訪問(如只讀、限制目錄等)
- D 沙箱可以限制網絡訪問(如阻止或過濾網絡連接)
77.以下哪種不是常見的代碼混淆技術?
A.字符串加密
B.控制流扁平化
C.垃圾代碼植入
D.源代碼加密
解析:
選D
- A 這是一種常見的混淆技術,通過加密程序中的字符串(如敏感信息、API密鑰等),在運行時解密使用,以增加靜態分析的難度
- B 這是一種常見的控制流混淆技術,通過打破代碼原有的邏輯結構,將代碼塊扁平化并添加調度器來控制執行流程,使代碼難以理解
- C 這是一種常見的混淆技術,通過插入無用的代碼(如死代碼、花指令)來增加代碼的復雜性,干擾分析者的視線
- D 這不是一種常見的代碼混淆技術。混淆通常針對編譯后的代碼或中間表示(如字節碼),而不是直接加密源代碼。加密源代碼本身并不會使代碼更難分析(因為解密后即可獲得原始代碼),而且在實際應用中,混淆的目的是保護代碼邏輯,而不是通過加密來隱藏源代碼(加密通常用于數據保護而非代碼保護)。常見的混淆操作是在代碼層面進行變換,而不是加密整個源代碼
78.關于加密算法的使用,以下說法正確的是?
A.對稱加密算法總是比非對稱加密快
B.散列函數可以用于加密
C.所有加密算法都需要密鑰
D.加密強度只取決于密鑰長度
解析:
選A
- A 對稱加密算法(如AES、DES)使用相同的密鑰進行加密和解密,計算復雜度較低,因此通常比非對稱加密算法(如RSA、ECC)快得多。非對稱加密涉及大數運算(如模冪運算),計算開銷較大
- B 散列函數(如SHA-256、MD5)是單向的,用于生成數據的指紋(摘要),但不能用于加密(因為無法從散列值恢復原始數據)。加密算法(如AES、RSA)是可逆的,而散列函數不可逆
- C A更嚴謹些
- D 加密強度還取決于算法設計(如抗 cryptanalysis 能力)、實現方式等
79.數字簽名驗證過程中,以下哪個步驟是錯誤的?
A.計算消息摘要
B.使用公鑰解密簽名
C.使用私鑰加密數據
D.比較摘要值
解析:
選C
數字簽名驗證過程:
1.發送方(簽方)
計算消息的散列值(消息摘要)
使用發送方的私鑰加密該摘要(即生成簽名)
將簽名和原始消息一起發送給接收方
2.接收方(驗證)
計算接收到的原始消息的散列值(消息摘要)
使用發送方的公鑰解密接收到的簽名,得到發送方計算的摘要
比較自己計算的摘要和解密得到的摘要,如果相同,則簽名有效;否則無效
- C 在驗證過程中,接收方不會使用私鑰加密數據。私鑰僅用于簽名(加密摘要),而驗證時使用公鑰解密簽名
80.以下哪種對稱加密算法的密鑰長度和分組大小均為64位?
A.DES
B.AES
C.RC4
D.3DES
解析:
選A
- A DES(數據加密標準):
分組大小:64位(但實際有效數據為56位,因為包含8位奇偶校驗)
密鑰長度:64位(通常說64位,但其中8位用于奇偶校驗,實際有效密鑰為56位) - B AES(高級加密標準):
分組大小:128位
密鑰長度:支持128、192或256位 - C RC4:
這是一種流密碼,沒有固定的分組大小(流密碼逐位加密)
密鑰長度可變(通常40-2048位),但不是固定64位 - D 3DES(三重DES):
分組大小:64位
密鑰長度:通常為112位或168位。3DES使用兩個或三個DES密鑰,因此密鑰長度更長
81.關于Qt框架,以下說法錯誤的是?
A.QObject
是所有Qt對象的基類
B.信號槽機制必須使用connect
顯示連接
C.Qt的內存管理采用父子對象樹
D.moc
用于處理Qt的元對象處理
解析:
選B
- A 正確,Qt中大多數類都繼承自
QObject
(例如QWidget
、QTimer
等),它提供了信號槽、對象樹管理等核心功能 - B 錯誤,Qt的信號槽機制不僅可以通過
QObject::connect
函數顯式連接,還可以通過自動連接(使用on_發送者對象名_信號名
的命名約定)或在Qt Designer中可視化連接。因此,并非必須顯式使用connect
- C 正確,QObject通過父子對象樹管理內存:當父對象被銷毀時,會自動銷毀其所有子對象,從而避免內存泄漏
- D
moc
(元對象編譯器)是Qt的工具,它處理包含Q_OBJECT
宏的類,生成額外的元對象代碼(如信號槽機制、屬性系統、動態類型信息等)
82.在Windows消息處理中,以下哪個消息不屬于鼠標相關消息?
A.WM_LBUTTONDOWN
B.WM_MOUSEMOVE
C.WM_PAINT
D.WM_MOUSEWHEEL
解析:
選C
- A
WM_LBUTTONDOWN
:鼠標左鍵按下消息,屬于鼠標消息 - B
WM_MOUSEMOVE
:鼠標移動消息,屬于鼠標消息 - C
WM_PAINT
:繪制消息,用于通知窗口需要重繪其客戶區(例如窗口大小改變、部分區域暴露等)。這是一個窗口管理消息 - D
WM_MOUSEWHEEL
:鼠標滾輪滾動消息,屬于鼠標消息
83.關于GUI事件循環,下列說法正確的是?
A.事件循環可以在多個線程同時運行
B.事件循環負責處理所有用戶輸入
C.事件處理函數必須立即返回
D,阻塞事件循環不會影響UI響應
解析:
選B
- A 錯誤,在大多數GUI框架(如Qt、
Windows API
)中,主事件循環(管理UI的主循環)通常只能在主線程運行。雖然可以在其他線程創建局部事件循環(例如Qt的QEventLoop
),但處理UI事件的主事件循環必須是線程安全的,且通常僅存在于主線程。多個線程同時運行全局事件循環會導致競態條件和UI不一致 - B 正確,GUI事件循環的核心職責是持續從事件隊列中獲取事件(包括用戶輸入,如鼠標點擊、鍵盤按鍵、窗口消息等),并將其分派給相應的目標對象(如窗口部件)進行處理
- C 錯誤,事件處理函數(如槽函數或消息處理函數)應該盡快返回,以避免阻塞事件循環(導致UI凍結),但并非“必須”立即返回。如果事件處理耗時,應使用多線程或異步操作來避免阻塞
- D 正確,阻塞事件循環(如在事件處理函數中執行長時間操作)會直接導致UI無響應,因為事件循環被卡住,無法處理后續事件(如重繪、輸入等)
84.使用OpenGL
進行渲染時,下列哪個操作不會影響性能?
A.頻繁切換著色器程序
B.修改uniform
變量
C.多次綁定相同的紋理
D.使用頂點數組對象
解析:
選B
- A 切換著色器程序(
glUseProgram
)是一個開銷較大的操作,因為GPU需要重新配置渲染狀態。頻繁切換會導致狀態切換開銷,降低性能 - B
uniform
變量是著色器中的常量,在繪制調用之間可以修改(通過glUniform
函數)。更新uniform
變量的開銷很小,因為它只涉及更新少量數據(通常存儲在GPU的常量內存中),不會引起管線狀態切換或資源綁定變化 - C 即使綁定的是同一個紋理,重復調用
glBindTexture
也會帶來不必要的開銷(驅動需要檢查狀態是否變化)。最佳實踐是避免冗余綁定(例如,通過狀態排序減少紋理切換) - D 通常不會影響性能,而且可能提高性能。頂點數組對象(
VAO
)用于封裝頂點數組狀態(如VBO
和屬性指針),使用VAO
可以減少重復調用glVertexAttribPointer
等函數,從而優化性能。但如果不正確使用(如頻繁創建/銷毀VAO
),也可能帶來開銷。不過,合理使用VAO
是推薦做法,不會負面性能
頂點數組對象(
VAO
)不是用來存儲頂點數據本身的,而是用來存儲“如何從緩沖區(如VBO)中讀取頂點數據”的配置狀態集合,
存儲狀態配置的容器,記錄了如何解析一個或多個 VBO 中的數據(即glVertexAttribPointer
和glEnableVertexAttribArray
的調用狀態)
VBO(頂點緩沖區對象) 是 OpenGL 中的一種緩沖區對象,用于在顯卡的高性能內存(VRAM) 中存儲大量的頂點數據
一次性將頂點數據批量上傳到 GPU 內存中,之后繪制時直接讓 GPU 從自己的高速內存中讀取數據,避免了重復的數據傳輸和函數調用
存儲數據的容器,它只負責存儲原始的二進制頂點數據,但不記得這些數據如何被解析(哪些是位置,哪些是顏色等)
85.關于圖像處理算法,以下說法正確的是?
A.中值濾波對椒鹽噪聲無效
B.高斯模糊是線性濾波器
C.Sobel
算子只能檢測水平邊緣
D.圖像銳化會減少細節
解析:
選B
- A 中值濾波(
Median Filter
)是一種非線性濾波器,它用像素鄰域內的中值代替中心像素值。它對椒鹽噪聲(Salt-and-Pepper Noise
)非常有效,因為椒鹽噪聲表現為極端的亮或暗像素點,而中值濾波能很好地消除這些異常值,同時較好地保留圖像邊緣 - B 高斯模糊(
Gaussian Blur
)是一種線性平滑濾波器。它通過對圖像與高斯核(符合高斯分布的卷積核)進行卷積操作來實現模糊。線性濾波器的特點是滿足疊加性和齊次性,高斯模糊完全符合這一特性 - C
Sobel
算子是一種用于邊緣檢測的離散微分算子。它可以檢測 水平邊緣 and 垂直邊緣,通常需要結合兩個方向的結果(如計算梯度幅值)來得到完整的邊緣信息 - D 圖像銳化(
Image Sharpening
)的目的是增強圖像的細節和邊緣,使圖像看起來更清晰。它通常通過增強圖像的高頻成分(如使用拉普拉斯算子或非銳化掩模)來實現。因此,銳化會增加而非減少圖像的細節表現
86.關于各種排序算法的特性,以下說法錯誤的是?
A.快速排序的平均時間復雜度是O(nlogn)
B.冒泡排序是穩定的排序算法
C.堆排序的空間復雜度是O(n)
D.歸并排序適合外部排序
解析:
選C
- A 快速排序在平均情況下的時間復雜度為
O(n log n)
,盡管最壞情況(如數組已排序)下為O(n2)
,但通過隨機化 pivot 選擇通常可以避免 - B 冒泡排序是穩定的排序算法,因為它只交換相鄰元素,不會改變相等元素的相對順序
- C 堆排序是一種原地排序算法,其空間復雜度為
O(1)
(僅需常數級別的額外空間用于交換元素和遞歸調用(如果實現為遞歸,但通常可以迭代實現))。它不需要額外的存儲空間(如歸并排序中的臨時數組) - D 歸并排序非常適合外部排序(即數據量太大,無法全部加載到內存中),因為它可以分塊讀取數據,排序后再合并。其外部排序版本通常用于處理大規模數據
87.在圖搜索算法中,以下哪種說法是正確的?
A.BFS總是能找到最短路徑
B.DFS總是比BFS要節省內存
C.兩點間所有路徑只能用DFS求解
D.BFS必須使用遞歸實現
解析:
選A
- A 廣度優先搜索(BFS)按照距離起點的層次順序遍歷圖。在無權圖(所有邊權重相等)中,BFS 總是能找到從起點到目標點的最短路徑(即經過邊數最少的路徑)。這是因為BFS會先探索所有距離為1的節點,再探索距離為2的節點,以此類推,因此第一次遇到目標節點時,路徑一定是最短的
- B 深度優先搜索(DFS)和BFS的內存消耗取決于圖的結構,
深度優先搜索(DFS)和BFS的內存消耗取決于圖的結構;
DFS需要維護一個棧,在最壞情況下(如長鏈)也可能消耗大量內存(線性深度)
實際上,BFS在廣度大的圖中內存消耗大,DFS在深度大的圖中內存消耗大 - C 雖然DFS常用于查找所有路徑(因為它能深入探索一條路徑到底),但BFS也可以用于查找所有路徑(需要記錄多條路徑信息)。此外,其他算法(如動態規劃)也可用于特定情況下的所有路徑查找
- D BFS通常使用迭代和隊列(
Queue
)實現,而不是遞歸。遞歸通常用于DFS(因為遞歸調用棧天然符合DFS的深度優先特性)。用遞歸實現BFS不僅不自然,而且可能導致棧溢出
88.以下哪個問題不適合使用動態規劃解決?
A.背包問題
B.最長公共子序列
C.單源最短路徑
D.樹的前序遍歷
解析:
選D,遍歷也就只訪問一次,沒有重復計算
動態規劃適用于具有以下兩個關鍵性質的問題:
1.重疊子問題(Overlapping Subproblems
):問題可以分解為若干子問題,且這些子問題會被多次重復計算。
2.最優子結構(Optimal Substructure
):問題的最優解包含其子問題的最優解
- A 背包問題(尤其是0-1背包)是動態規劃的經典應用。它具有最優子結構(當前物品是否放入背包取決于子問題的最優解)和重疊子問題(相同容量和物品范圍的子問題被重復計算),非常適合用DP解決
- B LCS是另一個DP經典問題。它要求兩個序列的最長公共子序列,具有最優子結構(當前字符是否匹配取決于前面的子序列)和重疊子問題(相同前綴子序列被重復計算),完全適合DP
- C 這里特指帶權有向圖(且權重非負) 的單源最短路徑(如
Dijkstra
算法)。Dijkstra
算法雖然常被歸類為貪心算法,但其本質也利用了動態規劃的思想(最優子結構:當前節點的最短路徑由前驅節點的最短路徑推導而來)。對于無負權邊的圖,DP是有效的。即使對于有負權邊的圖(如Bellman-Ford
算法),也使用動態規劃。因此,它適合DP - D 樹的前序遍歷是一個遍歷問題,不具有動態規劃所需的特性:它沒有重疊子問題(每個節點只訪問一次,沒有重復計算),它沒有最優子結構(不需要求解最優值,只是按順序訪問節點),樹的前序遍歷通常使用遞歸或迭代(棧)實現,不需要動態規劃
89.關于KMP算法,下列說法正確的是?
A.最壞時間復雜度是O(n2)
B.不需要預處理模式串
C.適用于多模式串匹配
D.利用部分匹配表避免回溯
解析:
選D,
- A KMP算法(
Knuth-Morris-Pratt算法
)的最壞時間復雜度是O(n + m)
,其中n是文本串長度,m是模式串長度。它通過避免不必要的比較實現了線性時間匹配,遠優于樸素算法的O(n*m)
- B KMP算法需要預處理模式串以生成一個重要的數據結構——部分匹配表(
Partial Match Table, PMT
),也稱為最長公共前后綴表(LPS Array
)。預處理的時間復雜度為O(m)
。沒有這個表,算法無法工作 - C KMP算法是單模式串匹配算法,即一次只能在一個文本串中搜索一個模式串。如果要同時搜索多個模式串,需要使用多模式串匹配算法,如
Aho-Corasick
算法(自動機)或后綴樹 - D 這是KMP算法的核心思想。當出現字符不匹配時,KMP算法不會回溯文本串的指針,而是利用預處理得到的部分匹配表,將模式串向右滑動盡可能遠的距離,然后繼續比較。這避免了樸素算法中文本指針的回溯,保證了線性時間效率
90.在并查集中,下列哪個操作的時間復雜度是O(1)?
A.不帶路徑壓縮的查找
B.帶路徑壓縮的合并
C.初始化操作
D.判斷兩個元素是否連通
解析:
選C
并查集支持三種主要操作:
1.初始化(Initialize
):創建并查集,通常將每個元素初始化為一個獨立的集合(每個元素的父節點指向自己)
2.查找(Find
):查找某個元素所在集合的根節點(代表元)
3.合并(Union
):將兩個元素所在的集合合并為一個集合。
- A 如果不使用路徑壓縮優化,查找操作的最壞時間復雜度為 O(n)(樹退化為鏈表時)。平均情況也不是O(1)
- B 合并操作通常需要先執行兩次查找(找到兩個元素的根),然后進行合并。即使使用路徑壓縮優化,查找操作的平均時間復雜度接近O(α(n))(阿克曼函數的反函數,幾乎為常數),但嚴格來說不是O(1)。合并操作本身(連接兩個根)是O(1),但整體操作依賴于查找
- C 初始化并查集時,需要為每個元素分配父節點(指向自己),這可以在O(1)時間內完成每個元素的初始化。但整體初始化n個元素的時間復雜度是O(n)。
- D 判斷兩個元素是否連通(即是否屬于同一集合)需要執行兩次查找操作(Find)。即使使用路徑壓縮,查找操作的平均時間復雜度是O(α(n)),不是O(1)
91.關于二叉樹的遍歷,以下說法錯誤的是?
A.中序遍歷可以得到BST的有序序列
B.后序遍歷適合釋放內存
C.層序遍歷必須使用隊列
D.前序遍歷一定比中序遍歷快
解析:
選D
- A 二叉搜索樹(BST)的性質是:左子樹所有節點的值 < 根節點的值 < 右子樹所有節點的值。中序遍歷(左子樹 -> 根 -> 右子樹)會先訪問左子樹(較小值),然后根(中間值),最后右子樹(較大值),因此恰好得到升序有序序列
- B 后序遍歷的順序是:左子樹 -> 右子樹 -> 根。這種順序先訪問子節點,再訪問父節點,非常適合用于釋放二叉樹的內存(或刪除樹),因為必須先釋放所有子節點才能安全釋放父節點。如果先釋放父節點,子節點將無法訪問
- C 層序遍歷(廣度優先遍歷)按層次從上到下、從左到右訪問節點。它必須使用隊列(
Queue
) 來維護當前層的節點順序:先將根節點入隊,然后出隊并訪問,同時將其子節點入隊,以此類推。沒有隊列無法實現層序遍歷(遞歸方式也需要模擬隊列) - D 二叉樹的前序、中序、后序遍歷的時間復雜度都是
O(n)
(每個節點訪問一次),因此它們的速度在理論上是一樣的,沒有誰更快之說。實際運行時間可能受具體實現、緩存命中率等因素影響,但無法斷定前序一定比中序快。遍歷速度主要取決于樹的大小,而不是遍歷順序
92.以下哪種哈希沖突解決方法不會導致查找時間增加?
A.線性探測法
B.二次探測法
C.完美哈希
D.鏈地址法
解析:
選C
- A 線性探測法(
Linear Probing
):屬于開放定址法。當發生沖突時,線性地查找下一個空桶。會導致查找時間增加,因為沖突可能形成聚集(clustering
),最壞情況下查找時間退化為O(n)
- B 二次探測法(
Quadratic Probing
):也屬于開放定址法。通過二次函數跳過距離以避免聚集,但仍然可能導致查找時間增加(雖然比線性探測好),特別是在高負載因子時,可能找不到空桶或需要多次探測 - C 完美哈希(
Perfect Hashing
):完美哈希是一種無沖突的哈希技術(通常用于靜態數據集)。它通過兩級哈希結構確保每個鍵都映射到唯一的位置,因此查找時間總是O(1)
,不會因沖突而增加。但缺點是需要預先知道所有鍵且構造成本高 - D 鏈地址法(Chaining):每個桶維護一個鏈表(或其他結構)存儲所有映射到該桶的鍵值對。查找時間可能增加,因為最壞情況下(所有鍵映射到同一桶)查找退化為O(n)(鏈表長度)。平均情況下是O(1 + α)(α為負載因子),但仍受沖突影響
93.下列哪個問題不能用貪心算法得到最優解?
A.哈夫曼編碼
B.最小生成樹
C.0-1背包問題
D.活動選擇問題
解析:
選C
貪心算法每一步都做出當前看來最優的選擇,希望導致全局最優解。但它并不總是能得到全局最優解,只有當問題具有貪心選擇性質和最優子結構時才適用
- A 哈夫曼編碼(
Huffman Coding
):哈夫曼編碼是一種用于數據壓縮的貪心算法。它通過總是合并頻率最小的兩個節點來構建前綴碼,能夠得到最優解(即平均編碼長度最短) - B 最小生成樹(
Minimum Spanning Tree, MST
):最小生成樹問題(如Prim
算法和Kruskal
算法)使用貪心策略。Prim
算法每次選擇與當前樹相連的最小權邊,Kruskal
算法每次選擇全局最小權邊且不形成環。兩者都能得到最優解(權重最小的生成樹) - C 0-1背包問題(
0-1 Knapsack Problem
):0-1背包問題中,物品不能分割(要么完整放入,要么不放)。如果使用貪心策略(例如按價值密度(價值/重量)降序選擇),無法保證得到最優解
0-1背包問題通常用動態規劃解決,貪心算法只能得到近似解 - D 活動選擇問題(
Activity Selection Problem
):活動選擇問題是貪心算法的經典應用。每次選擇結束時間最早的活動,以便留下更多時間給后續活動。這能得到最優解(最多數量的兼容活動)
94.關于Dijkstra
算法,以下說法正確的是?
A.可以處理負權邊
B.必須使用優先隊列實現
C.適用于有向和無向圖
D.只能求單點到單點的最短路
解析:
選C
Dijkstra
算法用于解決帶權有向圖或無向圖的單源最短路徑問題。所謂單源,是指它從一個指定的源點(Source Node)出發,計算該源點到圖中所有其他節點的最短路徑(即路徑上所有邊的權重之和最小)
注意:算法要求圖中所有邊的權重均為非負值
- A 錯誤,
Dijkstra
(迪杰斯特拉)算法的核心前提是圖中不能有負權邊(即邊的權重必須非負)。如果存在負權邊,算法可能無法得到正確的最短路徑,因為它基于貪心策略,一旦節點被標記為已訪問(最短路徑已確定),就不會再更新,但負權邊可能導致更短的路徑 - B 錯誤,優先隊列(通常是最小堆)是高效實現
Dijkstra
算法的常見方式(時間復雜度O((V+E) log V)
),但不是必須的。也可以使用簡單的數組來存儲距離(時間復雜度O(V2)
),這在稠密圖中可能更實用。因此,實現方式可以靈活選擇 - C 正確,
Dijkstra
算法既適用于有向圖,也適用于無向圖。對于無向圖,每條邊可以視為兩條方向相反的有向邊。算法本身并不限制圖的類型,只要權重非負即可 - D 錯誤,
Dijkstra
算法是單源最短路徑算法,它計算的是從單個源點到所有其他節點的最短路徑。雖然可以在找到目標點后提前終止,但算法的本質是求出源點到所有點的最短路徑
Dijkstra
算法時間復雜度:
取決于如何實現“從未訪問節點中選取距離最小的節點”這一步驟
數據結構 | 時間復雜度 | 適用場景 |
---|---|---|
數組(Array) | O(V2) | 稠密圖 |
優先隊列(最小堆) | O((V + E) log V) | 稀疏圖(常用) |
斐波那契堆(Fibonacci Heap ) | O(E + V log V) | 理論最優 |
V: 頂點數 (Vertex)
E: 邊數 (Edge)
95.在AVL
樹中,以下哪種操作可能導致樹的高度改變?
A.查找
B.單旋轉
C.插入
D.遍歷
解析:
選C
AVL
樹是一種自平衡二叉搜索樹,通過維護每個節點的平衡因子(左子樹高度 - 右子樹高度)在{-1, 0, 1}之間來保證平衡。樹的高度可能因改變樹結構的操作而變化
- A 查找操作只遍歷樹的結構,不會修改任何節點或樹的結構。因此,它絕對不會改變樹的高度
- B 單旋轉(Single Rotation)(如左旋或右旋)是AVL樹在插入或刪除后用于恢復平衡的操作。旋轉操作會重新調整節點的位置,但不會改變整個樹的高度;它是在高度不變的前提下調整平衡。實際上,旋轉通常是為了在插入導致高度增加后,通過調整來防止高度增加(或刪除后防止高度減少)
- C 插入操作會添加一個新節點到樹中,這可能增加樹的高度(例如,插入到最深層)。插入后,可能導致平衡因子超出范圍,從而觸發旋轉再平衡。但旋轉本身是為了平衡,而插入操作是直接可能導致高度改變的原發性操作
- D 遍歷(Traversal)(如中序、前序、后序)只訪問節點,不修改樹的結構,因此不會改變樹的高度
只有修改樹結構的操作(插入、刪除)才可能改變樹的高度
旋轉是響應高度變化(或潛在高度變化)的修復操作,其目的恰恰是防止高度不必要的增加或減少,而不是主動改變高度
96.關于iOS系統架構,以下說法錯誤的是?
A.Darwin
是iOS的核心操作系統
B.Cocoa Touch
是最上層的開發框架
C.所有系統進程都運行在用戶空間
D.沙盒機制用于應用隔離
解析:
選C,
- A 正確,
Darwin
是iOS(和macOS)的核心,是一個開源的Unix-like操作系統,它包含了XNU內核(混合內核)、設備驅動、BSD系統調用等基礎組件 - B 正確,
Cocoa Touch
是iOS應用開發的主要框架(基于Objective-C/Swift
),提供UIKit
等API,用于構建用戶界面和處理觸摸事件,位于架構的最上層 - C 錯誤,系統進程(如內核、驅動)運行在內核空間,而用戶應用(包括系統服務進程)運行在用戶空間
- D 正確,iOS嚴格使用沙盒(
Sandbox
)機制隔離每個應用,限制其文件系統訪問、網絡權限和其他資源,確保安全性和隱私
97.關于ARM指令集,下列說法正確的是?
A.所有指令長度都是4字節
B.支持x86
風格的變長指令
C.Thumb
模式指令都是16位的
D.條件執行只能用于分支指令
解析:
選C
- A 錯誤,這是ARM指令集早期的特點(
ARM
狀態),但并非所有指令都是4字節。為了節省代碼密度,ARM
引入了Thumb
指令集,其中指令長度主要為16位(2字節)。此外,Thumb-2
技術還支持16位和32位混合指令 - B 錯誤,
x86
指令集以其變長指令(1到15字節不等)著稱,而ARM
指令集采用定長指令(在ARM
狀態下為32位,在Thumb
狀態下主要為16位)。這是兩者最根本的區別之一,ARM
不支持x86
風格的變長指令 - C 正確,最初的Thumb指令集(ARMv4T)中的所有指令都是16位的,旨在提供更高的代碼密度(比ARM代碼小約30%)。后來的Thumb-2擴展引入了32位指令
- D 錯誤,這是
ARM
指令集的一個顯著特點:幾乎所有指令都可以條件執行(而不僅僅是分支指令)。這是通過指令編碼中的4位條件字段實現的(如EQ, NE, GT等)。這種設計可以減少分支預測失敗的開銷
98.關于Objective-C的消息發送機制,以下說法錯誤的是?
A.使用動態分發機制
B.方法調用是在運行時決定的
C.發送消息給nil
會導致崩潰
D.支持method swizzling
解析:
選C
- A 正確,
Objective-C
的方法調用(消息發送)采用動態分發(Dynamic Dispatch
)。這意味著調用哪個方法(實現)是在運行時(Runtime
)通過查找對象的類結構中的方法列表(method list
)來決定的,而不是在編譯時靜態綁定 - B 正確,這是動態分發機制的直接結果。當向對象發送消息(如
[obj message]
)時,運行時系統會檢查對象的類,并根據方法名(選擇子selector
)在類的方法列表中查找對應的實現(IMP
)來執行。這個過程完全發生在運行時 - C 錯誤,這是
Objective-C
的一個獨特且重要的特性:向nil
發送消息是安全的,不會導致崩潰。消息發送機制在運行時如果發現接收者是nil
,會直接返回(對于返回類型,返回0、nil
、0.0等初始值),而不會執行任何方法查找或調用。這是一種“安靜失敗”的行為 - D 正確,
Method Swizzling
是Objective-C
運行時的一個強大特性。它允許在運行時動態交換兩個方法的實現(即改變選擇子selector
和實現IMP
之間的映射)。這廣泛用于AOP
(面向切面編程)、調試、Monkey Patching
等場景,完全依賴于Objective-C
的動態消息機制
99.以下哪個不是Swift
的特性?
A.強類型相關
B.自動垃圾回收
C.可選類型
D.協議擴展
解析:
選B,Swift
使用自動引用計數(ARC
)來管理內存
強類型(Strong Typing):每個變量和常量都必須有明確的類型
- A
Swift
是強類型(Strongly Typed) 語言,并且在編譯時進行類型安全(Type Safe) 檢查。這意味著編譯器會嚴格檢查類型,防止不匹配的類型操作,從而在編譯期捕獲許多錯誤 - B
Swift
不使用傳統的垃圾回收(Garbage Collection, GC)機制。相反,它使用自動引用計數(Automatic Reference Counting, ARC) 來管理內存。ARC
在編譯時插入引用計數管理代碼,通過跟蹤對象的引用關系來釋放內存,其開銷和停頓時間比垃圾回收更可預測,性能更高 - C 可選類型(
Optionals
) 是Swift
的核心特性之一,用于處理值可能缺失的情況。它強制開發者安全地解包(unwrapping)可選值,避免了許多空指針異常(在Objective-C中為nil消息發送,在Java中為NullPointerException) - D 協議擴展(
Protocol Extensions
) 是Swift
非常強大的特性,它允許為協議(Protocol
)提供默認實現。這使得Swift的協議具備了類似其他語言中“抽象類”或“Mixin”的能力,是實現面向協議編程(Protocol-Oriented Programming
) 的基礎
100.關于iOS的Cocoa Touch框架,以下說法正確的是?
A.UIKit
只能用于iPhone應用開發
B.Foundation
框架依賴于UIKit
C.CoreData
是持久化框架
D.SpriteKit
只用于3D游戲開發
解析:
選C
- A 錯誤,
UIKit
是用于構建iOS應用用戶界面的框架,但它不僅限于iPhone。它同樣用于開發iPad(iPadOS)、Apple TV(tvOS)、CarPlay應用,甚至為Mac Catalyst
項目提供支持,允許將iPad應用移植到macOS。因此,它是所有iOS/tvOS設備UI開發的基礎 - B 錯誤,事實恰恰相反。
Foundation
框架是Cocoa Touch的基礎,提供了核心功能,如字符串處理(NSString
)、集合(NSArray
,NSDictionary
)、日期時間(NSDate
)、文件系統訪問、網絡請求(URLSession
)、通知等。UIKit
依賴于Foundation
,而不是反過來。Foundation
框架本身不涉及任何UI - C 正確,
CoreData
是Apple提供的一個對象圖管理和持久化框架。它可以將模型對象(Entities
)保存到各種存儲中(如SQLite
數據庫、二進制文件、內存等),并管理對象之間的關系、變更跟蹤、數據驗證等。它是iOS中進行復雜數據管理和離線存儲的首選方案 - D 錯誤,
SpriteKit
是Apple提供的2D游戲開發框架,專門用于高性能2D圖形、動畫和物理效果。對于3D游戲開發,Apple提供的是SceneKit
(用于3D場景和模型)和Metal
(底層圖形API)。因此,SpriteKit
主要用于2D游戲,而不是3D
101.使用IDA Pro分析程序時,以下哪個說法是錯誤的?
A.F5可以生成偽代碼
B.所有的交叉引用都可以被自動識別
C.可以自定義數據結構
D.支持動態調試功能
解析:
選B
IDA Pro(Interactive Disassembler)是強大的靜態反匯編和調試工具
- A
IDA Pro
的F5快捷鍵可以將當前函數的匯編代碼反編譯為高級語言風格的偽代碼(C-like
),極大提高了代碼的可讀性和分析效率。這依賴于Hex-Rays
反編譯器插件 - B 錯誤,
IDA Pro
雖然能自動識別許多直接交叉引用(如call
、jmp
指令的目標地址),但對于間接交叉引用(如通過函數指針、動態計算地址、虛函數表調用)或混淆過的代碼,IDA
可能無法自動識別所有引用。分析人員經常需要手動添加注釋、定義函數或結構來幫助IDA
完善交叉引用分析 - C
IDA Pro
允許用戶自定義數據結構(Structs
)(如C結構體、聯合體)。這對于解析內存布局、理解函數參數(尤其是指向結構的指針)至關重要,能顯著提升反匯編代碼的可讀性 - D
IDA Pro
不僅是一個靜態分析工具,還集成了強大的動態調試器。它支持調試多種架構(x86/x64
,ARM
,MIPS
等)和平臺(Windows
,Linux
,macOS
,Android
),允許分析人員在真實環境中運行程序、設置斷點、檢查內存和寄存器狀態
102.關于x64dbg的斷點功能,下列說法正確的是?
A.硬件斷點數量無限制
B.條件斷點可以使用寄存器值
C.內存斷點不會改變程序代碼
D.所有斷點都是即時生效的
解析:
選B,C
單選最準確選C
- A 硬件斷點依賴于
CPU
的調試寄存器(如x86/x64
架構的DR0-DR3
)。這些寄存器的數量是硬件限定的,通常只有 4 個。因此,你最多只能同時設置 4 個硬件斷點,數量是有限制的 - B
x64dbg
支持強大的條件斷點。你可以在設置斷點時附加一個條件表達式,該表達式可以包含寄存器值、內存地址、變量等。例如,你可以設置一個在eax == 0x401000
時才觸發的斷點。這是x64dbg
非常常用的功能 - C 內存斷點(
Memory Breakpoint
)的實現原理是修改目標內存頁的保護屬性(例如,設置為PAGE_GUARD
)。當程序訪問或修改該內存頁時,會觸發一個異常,調試器捕獲這個異常從而實現斷點效果。這個過程沒有修改程序本身的指令代碼,因此是正確的 - D 雖然大多數斷點設置后立即生效,但軟件斷點(
Software Breakpoint
)是一個例外。軟件斷點的原理是將目標指令的第一個字節臨時替換為0xCC
(即INT 3
指令)。當程序執行到此處時,會觸發一個調試異常。這個“替換”操作需要時間,并且在某些極端情況下(如自修改代碼)可能不會立即生效或需要額外處理。因此,并非所有斷點都是“嚴格即時”生效的
103.對于一個UPX加殼的程序,以下哪種方法不適合脫殼?
A.單步跟蹤到OEP
B.ESP
定律查找
C.內存dump
D.靜態解密代碼
解析:
選D
UPX(Ultimate Packer for eXecutables)
是一種常見的壓縮殼(Packer
),其目的是壓縮可執行文件并在運行時解壓還原。脫殼(Unpacking
)就是找到原始程序的入口點(OEP
,Original Entry Point
)并提取出解壓后的代碼
- A 這是手動脫殼的經典方法。使用調試器(如
x64dbg
、OllyDbg
)單步執行(F7/F8
),小心跳過循環和陷阱,直到到達OEP
。UPX
殼本身不抗調試,此方法有效 - B
ESP
定律是脫壓縮殼的常用技巧。UPX
殼在解壓完成后,通常會有一個大的跳轉(JMP
)指令跳到OEP
。在這個跳轉之前,堆棧指針(ESP
)的值通常會突然發生變化(變為一個正常值)。在調試器中給ESP
寄存器設置硬件訪問斷點,運行后就會中斷在跳轉到OEP
的指令處,非常高效 - C 當程序運行到
OEP
時,原始代碼已在內存中完全解壓。此時可以使用工具(如Scylla
、PETools
)將進程內存中的鏡像轉儲(Dump
)出來,形成一個PE
文件。這是脫殼的標準最后一步 - D
UPX
是壓縮殼,不是加密殼。它的主要機制是壓縮(Compression
),而不是加密(Encryption
)。壓縮算法(如LZMA
)的目的是減少體積,其過程是可逆的(有解壓算法)。但試圖靜態分析壓縮后的數據并手動還原(即“靜態解密”)是極其困難且不切實際的,因為你需要逆向UPX
的解壓算法本身。正確的方法是通過動態運行(調試),讓殼自身的代碼將程序解壓到內存中
104.關于DLL注入技術,下列哪個說法的錯誤的?
A.CreateRemoteThread
是最常用的注入方法
B.注入的DLL
必須有DllMain
函數
C.APC
注入只能在目標線程恢復時觸發
D.注入方法通常能被殺毒軟件檢測
解析:
選B
DLL注入是將自定義動態鏈接庫加載到另一個進程地址空間的技術
- A 這是最經典和廣泛使用的
DLL
注入技術。它通過在目標進程中創建一個遠程線程,該線程的起始地址設置為LoadLibrary
函數,參數為要注入的DLL
路徑。該方法穩定且直接 - B 錯誤,
DllMain
是DLL
的可選入口點。雖然絕大多數注入都利用DllMain
在DLL_PROCESS_ATTACH
通知時執行代碼,但并非絕對必要。注入的DLL
可以沒有DllMain
函數。注入成功后,可以通過其他方式(例如導出的函數、在遠程線程中手動調用初始化函數)來執行代碼 - C
APC
(異步過程調用)注入將注入代碼排隊到目標線程的APC
隊列中。這些APC只在線程進入可警報狀態(Alertable State
)(例如調用SleepEx
,WaitForSingleObjectEx
等)時才會被觸發執行。因此,它確實需要等待目標線程恢復并進入特定狀態 - D
DLL
注入是惡意軟件和rootkit
的常用技術,因此現代殺毒軟件(AV
)和終端檢測與響應(EDR
)系統會監控此類行為。它們通過鉤子系統調用(如NtCreateThreadEx
)、掃描內存中的惡意DLL
或檢測進程行為異常來發現注入
105.在網絡協議分析中,以下哪種情況最難分析?
A.明文HTTP協議
B.使用固定密鑰的加密協議
C.動態密鑰協商的加密協議
D.壓縮過的數據包
解析:
選C,因為它結合了加密和密鑰的動態性,使得在不獲取運行時密鑰信息的情況下,幾乎不可能解密通信內容
- A 最容易分析,
HTTP
協議默認以明文形式傳輸所有數據(請求、響應、頭部、內容)。使用Wireshark
等抓包工具可以直接看到全部通信內容,幾乎沒有任何分析障礙 - B 較難,但有突破口,如果協議使用加密,但密鑰是固定的(例如硬編碼在客戶端或服務器中),分析人員可以通過逆向工程找到密鑰。一旦獲得密鑰,就可以解密所有通信數據。雖然比明文難,但仍有靜態分析的路徑
- C 最難分析,現代安全協議(如
TLS/SSL
、SSH
、IPsec
)都使用動態密鑰協商(例如Diffie-Hellman
密鑰交換)。每次會話都會生成唯一的臨時會話密鑰。這意味著:
1.無法通過靜態逆向工程獲取密鑰:因為密鑰是臨時生成的,不在代碼中硬編碼
2.前向安全性:即使泄露了服務器的長期私鑰,也無法解密過去的通信記錄
3.解密必須捕獲握手過程:要解密通信,必須在抓包的同時獲取到密鑰交換時生成的主密鑰或預主密鑰。這通常需要通過調試器從客戶端內存中提取(如SSLKEYLOGFILE
),或者擁有其中一方的私鑰并配合Wireshark
解密,條件非常苛刻 - D 壓縮(如
GZIP
)確實會隱藏數據的原始結構,但它沒有加密。分析人員可以輕松地將抓到的數據包內容提取出來,然后用相應的解壓算法(如gzip -d
)進行還原。一旦解壓,數據就變為明文或可分析的格式
106.還原加密算法時,下列哪個特征不能幫助識別標準算法?
A.S盒特征值
B.循環輪數
C.局部變量數量
D.關鍵常量
解析:
選C,它是一個高度可變且與算法本質無關的實現細節
識別加密算法通常依賴于其獨特的、公開的算法特征和常數
- A 許多分組密碼(如
AES
,DES
)使用替換盒(S-Box
),這是一個預定義的、非線性的查找表。S盒的具體數值是算法標準的一部分,并且通常是固定的。在反匯編代碼中發現與標準算法(如AES
的S盒)完全匹配的字節數組,是識別該算法的最強信號之一 - B 標準加密算法有固定的加密輪數。例如,
AES-128
有10輪,AES-192
有12輪,AES-256
有14輪;DES
有16輪。在代碼中發現一個循環執行了特定次數,可以極大地縮小候選算法的范圍 - C 局部變量的數量完全取決于編譯器的優化策略和程序員的實現方式。同一個算法,不同的人編寫、不同的編譯器、不同的優化等級(-
O0
vs -O2
)都會產生完全不同數量的局部變量。這是一個實現細節,而非算法本身的特征,因此對識別標準算法沒有幫助 - D 許多加密算法使用獨特的常數(
Constants
)。例如:
MD5/SHA
家族:使用特定的初始化向量(IV
)和每輪的常數(K值)
AES
:在輪密鑰擴展中使用固定的rcon
數組
TEA/XTEA
:使用黃金比例常數0x9E3779B9
107.以下哪種反調試技術最容易被繞過?
A.檢測調試標志位
B.檢測執行時間
C.檢測特定進程名
D.檢驗代碼完整性
解析:
選C
反調試技術是軟件保護中常用的一類方法,其目的是阻止或干擾調試器對程序的正常分析,增加逆向工程的難度
- A 這種方法通過檢查系統為調試器設置的特定標志位(如
BeingDebugged
字段in PEB
、NtGlobalFlag
)來檢測調試器。雖然這些標志位是已知的,但修改它們需要在調試器中手動操作或編寫復雜的腳本,對新手來說有一定難度 - B 這種方法通過在關鍵代碼段前后讀取時間戳計數器(如
RDTSC
指令),如果執行時間過長則判定被調試。繞過它需要小心地控制調試步伐(如使用硬件斷點而非軟件斷點),或修改時間檢查的邏輯,需要一定的技巧 - C 這種方法通過枚舉系統進程,查找已知的調試器進程名,繞過它極其簡單:
1.重命名調試器:將x64dbg.exe
改名為notepad.exe
或其他任意名字
2.修改程序中的進程名列表:直接Patch
二進制文件,將檢測函數NOP
掉或清空要檢測的進程名列表 - D 這種方法通過校驗自身代碼段的內存
CRC
、哈希或校驗和來檢測是否被下斷點(軟件斷點會修改代碼為0xCC
)或被Patch
。繞過它需要手動計算正確的校驗值并修改檢查邏輯,或者使用硬件斷點(不修改代碼)來避開檢測,技術要求較高
108.關于API Hook,以下說法正確的是?
A.IAT Hook
不能hook
動態加載的API
B.Inline Hook
總是比IAT Hook
安全
C.Hook
系統API
一定需要管理員權限
D.遠程線程注入可以實現全局Hook
解析:
選A
API Hook是一種攔截和修改函數調用的技術,廣泛用于調試、監控、安全軟件等
- A
IAT
(導入地址表)Hook
通過修改程序的導入表,將API
函數的地址替換為自定義函數的地址。然而,動態加載的API
(例如通過LoadLibrary
和GetProcAddress
獲取的API
)不會通過I表,其地址被直接存儲在程序變量中。因此,IAT Hook
對這類API
調用完全無效 - B “安全”是相對的。
Inline Hook
通過直接修改目標API
函數開頭的幾個字節(通常是跳轉指令)來實現,它比IAT Hook
更強大(能Hook
任何函數,包括動態獲取的),但也更危險:
1.容易引發多線程競爭問題
2.如果實現不好,可能破壞堆棧平衡或寄存器狀態
3.更容易被檢測(代碼完整性校驗) - C
Hook
當前進程的API
(例如通過IAT Hook
或Inline Hook
)不需要任何特殊權限,因為進程是在修改自己的內存空間。只有當你需要Hook
其他進程或進行全局Hook
(如系統范圍的API
監控)時,才需要管理員權限來跨進程操作 - D 遠程線程注入(如
CreateRemoteThread
)通常用于將DLL
注入到單個目標進程中,然后在目標進程內部進行Hook
(如IAT Hook
)。它本身無法實現真正的全局Hook
(即一次性Hook
系統中所有進程)。傳統上,全局Hook
需要通過Windows
鉤子(SetWindowsHookEx
) 或內核態驅動來實現
109.制作程序補丁時,以下哪種方法最不安全?
A.修改內存數據
B.修改跳轉指令
C.NOP
填充指令
D.插入新的代碼片段
解析:
選A
制作補丁的目的是永久改變程序的行為。安全性 主要指補丁的穩定性、可靠性和可預測性
- A 這種方法是在程序運行時直接修改內存中的數據(如全局變量、配置值)。這本質是一種“內存破解”,非常脆弱,它的不安全性體現在:
1.臨時性:修改僅在當前運行實例中有效,程序重啟后失效,不是永久性補丁
2.易失性:目標數據地址可能隨版本更新而改變,或者被其他代碼重新寫入,導致修改被覆蓋
3.不可靠:如果修改時機不對(如修改后很快又被程序邏輯改回),可能導致行為不可預測。 - B,C 這兩種都是直接修改程序的代碼段(
.text
段),是制作永久性補丁的常用方法(風險在于需要精確計算指令長度和偏移,否則會破壞程序邏輯導致崩潰。但一旦正確應用,就是安全的):
1.修改跳轉指令:例如,將條件跳轉JZ
改為JNZ
,或將CALL
指令的目標地址改為新函數。只要跳轉邏輯正確,效果是穩定持久的
2.NOP
填充指令:將不需要的指令(如校驗、調用)用空操作指令0x90
填充。只要計算好要填充的指令長度,避免破壞后續指令的對齊,也是穩定可靠的 - D 這是最強大、最安全的方法。通常通過以下方式實現:
1.在文件空白區(如節間隙)或新添加的節中寫入新的代碼
2.在原代碼處用一個JMP
指令跳轉到新代碼
3.在新代碼執行完后,再跳回原流程繼續執行
這種方法避免了修改原有指令的長度,提供了最大的靈活性來執行復雜邏輯,并且穩定性最高。是專業補丁(如破解、漢化)的首選
110.關于ARM匯編,以下說法正確的是?
A.指令用于帶返回的跳轉
B.LDR
總是按字對齊訪問
C.PUSH
可以同時壓入多個寄存器
D.所有指令都可以條件執行
解析:
選A,C
若為單選,選C,PUSH
可以同時壓入多個寄存器 是一個更為明確和絕對正確的特性,它完美體現了ARM
指令集在堆棧操作上的高效設計
- A
BL(Branch with Link)
指令是ARM
匯編中用于函數調用的指令。它執行兩個操作:
1.將下一條指令的地址(返回地址)存入鏈接寄存器(LR
,R14
)
2.跳轉到目標地址執行。 - B
LDR
指令是用于從內存中加載數據到寄存器。標準的LDR
指令(如LDR R1, [R0]
)要求地址是字對齊的(4字節對齊),否則可能會產生對齊異常。但是,ARM
提供了非對齊訪問的支持(例如在某些架構版本中,或者使用LDR
的變種如LDRH
(半字)、LDRB
(字節)以及LDR
帶有移位或特殊后綴的形式),這些指令可以處理非對齊訪問。因此,不能一概而論 - C 這是
ARM
匯編的一個強大特性。PUSH
(和POP
)指令可以接受一個寄存器列表作為操作數,從而一次性將多個寄存器壓入棧中(或彈出)。
例如:PUSH {R0-R3, LR}
這條指令會將寄存器R0, R1, R2, R3
和LR
的值一次性壓入當前棧中。這極大地提高了代碼效率 - D 這是
ARM
架構的一個著名特性,但并非所有指令都可以。在傳統的ARM
指令集(A32
)中,大多數數據處理指令和分支指令可以條件執行(通過在指令后添加條件后綴,如ADDEQ, BNE
)。然而,并不是100%的指令都支持。例如,某些VFP
(浮點)指令和后期添加的擴展指令可能不支持條件執行。在Thumb
指令集(T16)中,只有分支指令B
支持條件執行。在Thumb-2
指令集中,通過IT(If-Then)
指令塊為后續最多4條指令添加條件執行能力
到此結束,真有人能看完了么(???(???(???*)