奇安信C/C++笔试试卷3:底层机制与内存安全考察全解析
2020年秋天我投了奇安信的C/C方向岗位。笔试链接发过来的时候题目后缀是“试卷3”我当时心里咯噔一下这种多套试卷随机分配的考法说明每一道题都可能是雷。整个笔试90分钟我印象最深的不是编程题有多难而是选择题里关于指针和内存的题密度极高几乎没有一道是纯背概念的题。后来我复盘认为这套卷子考的不是你会不会写C代码而是你有没有被真实项目里的内存问题教育过。这篇东西就围绕这套试卷3的考察方向、典型题目和备考方法展开写给正在准备C/C岗笔试的同学。如果你是准备投递安全公司C/C岗位的应届生或者已经在客户端、底层开发岗工作一两年但没系统刷过题这篇内容的参考价值会比较大。如果你只是打算背一点C面试题就上场我建议换个思路因为奇安信这类公司的C/C笔试卷对底层机制的考察会远超你的预期。文章里会有我记忆中的题型还原、解题思路、环境配置以及我最后复盘时总结出来的应试套路希望能帮你把准备周期缩短一到两周。1. 试卷3的题目地图奇安信C/C方向到底在筛什么人1.1 从题型分布反推岗位能力模型我先说我拿到的试卷结构不保证和所有人一样但应该有代表性。整张卷子包括约20道单选题、5道多选题、2到3道简答题以及2道编程题总时长90分钟。单选和多选覆盖C/C语言基础、数据结构、操作系统、Linux、网络协议和安全常识简答题集中在内存管理和TCP状态上编程题偏数据结构和字符串处理。为什么是这个结构因为安全公司的C/C岗位日常要面对的是终端检测引擎、网络报文解析、漏洞扫描插件这类底层模块这些模块最怕内存问题、并发问题、协议解析问题所以笔试必须把这几块摸清楚。这套题和很多人秋招刷的“大厂通用题库”有一个很大的不同它没有太多“场景设计”和“开放讨论”几乎每一题都在问某个具体机制到底怎么工作。比如它不会问“你怎么设计一个高并发队列”而会问“栈帧里局部变量和返回地址的相对位置是怎样的”。这意味着你光会写业务代码是不够的必须能把程序在内存里的行为画出来。我在考场上第一次意识到原来安全岗招的不是“会写C的人”而是“连出错时程序会怎么崩都能预判的人”。1.2 和普通互联网C岗的卷子有什么不一样我拿朋友投的某互联网大厂C开发岗卷子对比过最大的差异是普通岗喜欢考虚函数、多态、STL源码、并发队列、高并发场景设计而奇安信这类安全岗更关注内存安全、缓冲区边界、堆栈布局、进程地址空间、网络包处理、底层调试工具。同样是问内存普通岗可能问“new和malloc区别”安全岗会追问“为什么malloc返回的地址要用free释放如果越界写会发生什么”。同样是问网络普通岗可能问“HTTP状态码含义”安全岗会问“TCP序列号在伪重传里怎么处理UDP包可能被拆成几片”。我整理了一个表格你感受一下差异考察维度普通C开发岗安全公司C/C岗C语法虚函数、模板、STL应用指针、引用、内存布局、const/volatile算法LeetCode中高频题链表、二叉树、字符串、边界处理操作系统进程线程、锁虚拟内存、堆栈、页表、崩溃分析网络HTTP、TCP/UDP基础TCP状态机、报文解析、字节序安全基本不问缓冲区溢出、UAF、整数溢出、格式化字符串备考时如果有意往右侧准备比漫无目的地刷LeetCode要高效得多。我身边有些朋友花了三个月只刷算法题结果笔试遇到内存对齐和TCP状态直接懵了就是因为没看到这个差异。1.3 答题时的真实体感说实话做这套试卷3的时候我前20分钟是有些上头的。单选题里出现了好几道需要手动算结构体sizeof、手动推栈帧分布的题一道20分的选择可能要花2分钟。多选题更是变态少选不给分错选也不给分所以没有把握的选项坚决不选。编程题不算难但第一道反转链表我按照LeetCode习惯写了递归结果在本地跑得好好的在线平台测试用例直接栈溢出——不是算法错是递归深度太大链表长度10万爆栈了。这个教训让我后来再也不敢在有规模限制的情况下写递归。整体来看题量不大但每道题都需要你真正理解底层而不是背答案。考完那一刻我就有一种强烈的感觉这套卷子不是要刷掉那些“不会写代码的人”而是要筛掉那些“只会写代码但不知道代码为什么能跑的人”。所以如果你正在准备这个方向的笔试请从一开始就把目标定在“理解机制”上而不是“记住结论”上。2. 语言基础题里的坑指针、内存布局、编译链接一个都不能少2.1 指针、引用、const送分题还是送命题C/C笔试最经典的题就是指针和const的组合。试卷3里至少有两道题目是在考这两个概念的排列组合很多人一眼看上去都会但到了多选就开始纠结。我用自己的话还原了一道印象很深的题请你先判断输出#include iostream int main() { int a 10; int b 20; const int *p a; int * const q a; // p b; // 可以p本身不是const // *p b; // 不可以通过p不能修改*a // q b; // 不可以q本身是const *q b; // 可以通过q可以修改a std::cout a std::endl; return 0; }答案输出20。关键在于先判断是“指向const的指针”还是“const指针”const int *p变量名从左往右读p首先是一个指针指向的类型是const int所以指针本身可改、所指向对象不可改int * const q则先读到const说明q本身不可改但它指向的是普通int所以可以通过q修改对象。这类题在试卷里往往是连招比如还会加一个const int * const r a;表示指针和对象都不可改。我很推荐大家画一个“左右规则”从变量名开始向右看遇到const就说变量本身不可变向左看遇到const就说指向类型不可变。这个规则在笔试现场能救急。2.2 sizeof、内存对齐和栈分布不会算就白复习结构体对齐是C/C笔试的重灾区试卷3里也考了。我给你出一道和原题思路一致的题struct A { char c; // 1字节 int i; // 4字节 double d; // 8字节 }; static_assert(sizeof(A) 16, size check);为什么是16不是13因为默认对齐规则是每个成员对齐到“自身大小”和“当前编译器对齐值”的较小值64位Linux的gcc默认对齐值是8所以char后需要填充3字节让int对齐到4字节然后再填充4字节让double对齐到8字节。如果你改变成员顺序比如把double放前面int放中间char放后面大小还是16但很多同学会理解错。这里我想多说一句结构体对齐不只是为了考理论它直接影响网络协议解析。你定义一个包头结构体如果大小算错解析出来的字段全是错位的。奇安信做网络侧产品这种题完全是在筛选“能直接处理二进制数据”的人。笔试时一旦看到结构体对齐直接用“对齐到最大成员”这个经验估算省时省力。如果要覆盖实际产品场景你还需要额外考虑#pragma pack(push, 1)这种紧凑对齐很多题目会故意设置一个“协议头按1字节对齐”的条件这时候sizeof就要重新算。2.3 static、const、volatile、宏和内联多选题的高频考点多选题里有一类题非常搞心态它不单独考一个关键词而是把static、const、volatile、define、inline全部混在一起让你选出“说法正确”的选项。比如“static修饰的局部变量存储在全局区”是对的“const变量一定不可修改”是错的因为const int a 10; int *p (int*)a; *p 20;在C语言里可以骗过编译器只不过行为未定义。volatile告诉编译器每次从内存读取不能优化到寄存器我在做嵌入式相关开发时深有体会但笔试里考它往往是问你“哪些场景必须用volatile”——多线程共享变量、中断服务程序、硬件寄存器。编译链接过程也常考预处理宏展开、编译生成汇编、汇编生成目标文件、链接合并符号和重定位。单选题会给一个很长的编译命令问你哪一步报错。我记得有一次是考了静态库和动态库的链接顺序-lxxx放在源文件后面否则链接器找不到符号。这不是奇安信独有的坑但确实很多应届生不知道。你还需要理解-c只编译不链接-o指定输出文件名-shared生成动态库这些都是选择题里的常见选项。如果对链接过程不熟建议去读一读《程序员的自我修养——链接、装载与库》这本书的第四章和第五章基本就是笔试的题库源头。2.4 我在语言题上踩过的坑语言题最坑的不是不会而是“好像会”。我当年第一次做这套卷子多选题里有一个选项是“const成员函数中可以使用mutable修饰的成员变量”我因为没复习过mutable直接漏选了。后来翻书才知道mutable就是专门用来让const成员函数修改某些缓存的。这种东西如果你没见过真的只能靠运气。所以我的建议是把C语言基础复习到“每个关键字能写一个最小示例”的程度而不只是记结论。比如static在局部变量、全局变量、类成员、类函数里各有什么行为能举出四个例子你才算真会。纸上得来终觉浅这些知识必须自己编译运行一遍才能形成靠谱的直觉。3. 算法与数据结构题链表、二叉树和边界条件的战争3.1 第一道编程题反转链表我用迭代写试卷3的编程题第一道大概率是链表题我印象里是反转链表但测试用例给得很贼空链表、单节点、长链表。很多同学用递归写五个节点内很舒服但平台数据一拉长递归栈直接爆掉。我把常见写法贴出来struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseList(ListNode* head) { ListNode* prev nullptr; ListNode* cur head; while (cur) { ListNode* next cur-next; cur-next prev; prev cur; cur next; } return prev; }关键点三个保存next的临时变量不能丢三个指针的移动顺序别乱最后返回的是prev不是cur。笔试时如果要求O(1)空间必须用迭代如果允许递归也要先确认链表长度。我第二次准备时凡是遇到树的递归、链表的递归都会先问自己一句“如果递归深度超过1万会不会挂”。这个意识来自那次栈溢出安全岗的在线判题系统尤其喜欢用长链表和大数据量来测试你不考虑规模就很容易被卡到超时或者栈溢出。3.2 二叉树题层序遍历和最近公共祖先笔试中出现二叉树题的概率很高。层序遍历写法相对固定注意用队列而非递归根节点入队循环取队头把左右孩子入队记录每一层节点数即可。最近公共祖先的递归写法虽然简单但有一个容易出错的地方如果一个节点本身是另一个节点的祖先要记得直接返回当前节点。比如求5和4的LCA如果root是5直接在递归函数里判断root是否等于p或q等于就直接返回root不要在左右子树里空转。这里我还想提醒一点安全岗笔试题里的二叉树上限通常不会像LeetCode困难题那样考复杂的树形DP更多是考察你能否用C把基本结构写正确同时别忘了nullptr。我怀疑出题人故意把输入样例标得很简单让你掉以轻心实际判题数据里有大量空树和单节点测试。所以写二叉树递归时第一行永远写“如果当前节点为空返回什么”这比什么都重要。等这些基础题写顺了再去练一些稍微复杂的题比如先序遍历和中序遍历重建二叉树这类题在C/C岗位笔试里出现频率也不低而且考察的是对内存分配和递归的敏感度。3.3 STL的正确打开方式C笔试里最友好的部分就是STLvector、string、stack、queue、unordered_map都能大幅减少编码量。但STL的坑也不少我列几个高频的vector的erase返回下一个迭代器直接it会失效string的substr第二个参数是长度不是结束位置unordered_map遍历顺序不定需要顺序输出时用map用auto遍历容器时

相关新闻

最新新闻

日新闻

周新闻

月新闻