
1. 试卷整体扫描奇安信C/C岗到底在考什么先说结论这份2020年奇安信秋招C/C方向试卷2整体难度属于中上水平对基础功底的考察相当扎实不是靠背几道八股就能糊弄过去的。作为安全领域的头部厂商奇安信的C/C岗位笔试题目带有鲜明的“安全基因”——内存管理、指针操作、系统调用、网络编程的比重明显高于普通互联网公司的同类试卷。我拿到这份卷子第一感受是出题人几乎是贴着C/C程序员日常工作的痛点来命题的。整张卷子大致可以分成四块C/C语言基础与内存模型、数据结构和算法、Linux系统与网络编程、以及偏安全特色的综合性题目。这份试卷的参考价值在于它代表了“安全厂商C岗笔试”这个细分方向的典型范式和面阿里、腾讯那种重算法的风格不太一样更看重你对底层机制的理解深度。如果看完整套题你能明显感觉到奇安信需要的人才是那种“能把C写明白”且“对系统底层有敬畏心”的工程师。毕竟安全产品很多要直接对垒恶意样本和网络攻击流量代码里一个越界写可能就是被攻破的入口。对于准备方向我的建议很直接重点看内存布局、指针与引用、容器底层实现、多线程同步、TCP/UDP编程这几块。下面我按题型维度把这份卷子掰开揉碎地讲一遍每类题目我会拆解它的核心考点、给出解题思路再补充一些实操层面的经验。2. C/C语言核心考点拆解内存、指针、编译链接一个都没少2.1 指针与内存管理的“送命题”这份卷子里指针相关的题目占比相当高而且专门挑那些“看起来简单、一写就错”的场景来考。我记得有题是问char* p hello; p[0] H;会导致什么结果很多人第一眼觉得没问题但实际这行代码在C标准里是未定义行为——字符串字面量存储在只读数据段试图修改它通常会触发段错误。这里就牵出一个关键点C风格字符串常量是const char[]类型虽然语法上允许赋值给char*但任何写入操作都是违规的。这类题在笔试卷里属于“热身题”但它传递的信号很明确公司不想招那种连指针基本都搞不清楚就敢来面安全岗的人。还有一题考察了malloc和new到底有什么区别。我直接给一个比较实用的理解方式对比维度malloc / freenew / delete类型安全返回void*需强制转换返回具体类型指针类型安全初始化不调用构造函数内存是脏的调用构造函数完成初始化失败行为返回NULL抛出bad_alloc异常重载能力不可重载可针对类重载operator new底层关系C标准库函数C运算符内部可能调用malloc实际笔试中还有一个高频变体是delete一个void*指针会发生什么。标准答案是如果指针类型是void*delete表达式调用的析构函数是静态类型的析构函数对于void*来说就是“无析构函数”结果是未定义行为通常会直接崩溃。这条我在实际项目的代码评审里也见过不少人踩属于C最经典的坑之一。2.2 栈、堆、静态区的布局问题另一类必考题是“变量存放在内存哪个区域”。奇安信这份卷子问到了全局变量、静态局部变量、局部变量、字符串常量和new出来的对象分别存放在哪个段。这里有个容易混淆的点是static局部变量——它虽然作用域在函数内但生存周期是全局的存放在数据段.data或.bss不是在栈上。实际记忆的时候我一般建议从地址递增的角度去理解代码段在低地址接下来是只读数据段存放字符串字面量等再往上是已初始化数据段、未初始化数据段然后是堆从低地址向高地址生长最后是栈从高地址向低地址生长。搞清楚了这张内存布局图很多关于“为什么局部变量地址比堆对象地址大”“为什么栈溢出报段错误”的问题都能迎刃而解。这块知识不只是为了笔试实际排查线上问题非常有用。我自己处理过一个内存被踩的bug最后定位到是某处对栈上数组越界写把相邻变量的值改掉了问题表现为“随机变量莫名变化”。如果没有内存布局概念这种bug真的会排查到怀疑人生。2.3 C对象模型构造、析构、拷贝那些事C语言部分的另一大块是对象模型相关的题。试卷里有道题是关于拷贝构造函数什么时候会被调用值传递参数、返回对象、用一个对象初始化另一个对象。很容易漏答的是“返回值优化RVO”的情况——现代编译器在大多数场景下会做返回值优化实际拷贝可能被省略所以考题如果限定“不考虑编译优化”答案才是三次拷贝但如果你写出“编译器会优化所以不确定”反而体现你了解现代C编译器的行为。这里我补充一个在面试里很加分的细节C11之后移动语义加入vector扩容时优先调用移动构造而不是拷贝构造。前提是你自己定义的类要正确声明移动构造函数否则编译器会退回拷贝。奇安信笔试虽然不是面试但这种“知其所以然”的深度在后续技术面时非常吃香。还有一题问到虚函数的实现机制标准答案是虚函数表和虚表指针。具体来说每个含虚函数的类有一个虚函数表每个对象头部分有一个vptr指向该类虚表虚函数调用通过vptr间接跳转实现动态绑定。这里有个要点——构造函数中调用虚函数不会触发动态绑定因为构造期间对象的动态类型是当前正在构造的类。这块是C最常考也是最重要的底层机制安全产品里做插件架构、Pimpl模式都会用到。3. 数据结构与算法实战分析纸上代码比拼真功夫3.1 链表操作的“经典送分题”如何写得滴水不漏数据结构这部分奇安信考了链表反转、判断链表是否有环、以及合并两个有序链表。这些都是LeetCode原题级别的难度但笔试和OJ最大的区别在于手写代码时没人告诉你边界条件该怎么处理一旦漏掉就扣分。以链表反转为例最稳的写法是迭代三指针法struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseList(ListNode* head) { ListNode* prev nullptr; ListNode* curr head; while (curr ! nullptr) { ListNode* nextTemp curr-next; // 先保存后继 curr-next prev; // 反转指针 prev curr; // prev前进 curr nextTemp; // curr前进 } return prev; }这里有个我在真实笔试里见过无数人翻车的细节第三步prev curr和第四步curr nextTemp的顺序不能反。因为curr-next已经被改掉了如果你先执行curr curr-next得到的是旧的prev链表就断了。这种低级错误在IDE里有调试器还能发现但在笔试限时场景下很容易写飞。判断链表是否有环的经典解法是快慢指针快指针每次走两步慢指针每次走一步如果相遇说明有环。很多人能记住这个思路但我在笔试面试中见过有人把快指针初始化成head-next导致边界判断出错。更稳妥的写法是快慢指针都从head开始循环条件判断fast ! nullptr fast-next ! nullptr。3.2 字符串和数组的高频题从哈希到双指针字符串处理在安全岗的笔试题中地位很高因为很多漏洞分析、协议解析、日志清洗都绕不开字符串操作。这份卷子考了一道“找出字符串中第一个只出现一次的字符”和一道“最长无重复子串”。第一道题最简单直观的解法是哈希表统计两遍第一遍统计频次第二遍找第一个频次为1的字符。时间复杂度O(n)空间复杂度O(字符集大小)。这道题如果只用暴力双重循环八成会给面试官留下“基础不够扎实”的印象。最长无重复子串是滑动窗口的典型应用也是我在实际协议解析中经常用到的思路。核心代码如下int lengthOfLongestSubstring(string s) { int n s.size(); int left 0, right 0, maxLen 0; unordered_mapchar, int window; while (right n) { char c s[right]; window[c]; while (window[c] 1) { // 出现重复收缩左边界 char lc s[left]; window[lc]--; left; } maxLen max(maxLen, right - left 1); right; } return maxLen; }笔试的时候不少人会漏掉“收缩左边界直到窗口内重复字符被清除”这个循环导致结果偏大。我的心得是滑动窗口类题目一定要想清楚窗口的“合法条件”是什么然后严格维护它。3.3 一题二叉树考的是递归思维的完整性试卷中的二叉树题目是“判断二叉树是否对称”以及“层序遍历”。对称判断的递归解法很多人能写但层序遍历有一个易错点需要区分每一层的节点而不仅仅是按顺序输出。核心做法是用队列在每一轮循环中先记录当前队列长度size然后只弹出size个节点。这里有一个实际编码技巧在处理层序遍历时如果当前层的节点数不固定用queue.size()作为循环条件会动态变化导致分层错误。正确做法是把size先存到局部变量里。这个细节我在不少高级开发者的代码里也见过失误题虽然简单但“做对”和“做对且严谨”之间的差距就在这里。二叉树题目训练上我的建议是不要光看题解要动手把递归的“终止条件、当前层处理、递归调用、返回值”四要素在每道题上写出来练到条件反射。这样笔试时遇到变体题才不会慌。4. Linux系统与网络编程安全工程师的看家本领4.1 进程、线程与多线程同步奇安信对多线程编程的考察力度比普通公司大很多这完全可以理解——安全分析引擎、流量检测、沙箱系统都是典型的高并发C/C服务线程同步出了问题就是线上事故。卷子里有一道经典的“两个线程交替打印奇偶数”的题目考察的是std::mutex和std::condition_variable的基本用法。我建议不要背代码要理解它的核心模型共享状态 条件变量通知 循环等待条件。我分享一个实际笔试中比较稳的写法#include iostream #include thread #include mutex #include condition_variable std::mutex mtx; std::condition_variable cv; bool oddTurn true; void printOdd() { for (int i 1; i 99; i 2) { std::unique_lockstd::mutex lock(mtx); cv.wait(lock, [] { return oddTurn; }); std::cout i ; oddTurn false; cv.notify_all(); } } void printEven() { for (int i 2; i 100; i 2) { std::unique_lockstd::mutex lock(mtx); cv.wait(lock, [] { return !oddTurn; }); std::cout i ; oddTurn true; cv.notify_all(); } }这道题最关键的考点是wait为什么需要配合谓词第二个参数因为存在“虚假唤醒”的可能不检查条件就继续执行会造成状态错乱。这个知识点我在实际项目中确实遇到过——生产环境的多线程消费者如果不处理虚假唤醒偶尔会出现“幽灵数据”被消费掉极难排查而笔试能答出这一点说明你真理解条件变量的语义。4.2 网络编程Socket、select、poll与epoll的区别网络编程部分奇安信考了一道对比select、poll和epoll的题目。这是Linux C/C服务端开发必考的知识点也是安全设备中高并发连接处理的核心技术。我把关键差异整理成一个表格方便大家直接记维度selectpollepoll底层结构fd_set位图pollfd数组事件表(红黑树就绪链表)最大连接数FD_SETSIZE有限制(默认1024)理论上无上限理论上无上限I/O效率O(n)遍历全部fdO(n)遍历全部fd只返回就绪fdO(就绪数)消息传递每次需要从用户态拷贝fd_set到内核每次需要拷贝pollfd数组到内核通过epoll_ctl注册mmap共享内存触发模式仅水平触发仅水平触发支持水平触发和边缘触发(EPOLLET)笔试中常问的加分项是“边缘触发模式下为什么要配合非阻塞IO使用”。因为边缘触发只在状态变化时通知一次如果你没把数据读完剩余数据要等下次有新的数据到达才会再次触发而阻塞IO会导致你在等待中卡住无法处理其他连接。正确的做法是边缘触发 非阻塞IO 循环read直到返回EAGAIN。这个知识点在实际写高性能网关、流量分发系统时非常关键。我在做流量采集代理的时候早期用水平触发模式高并发下每次epoll_wait返回后都要遍历一遍就绪链表做读写判断虽然也能跑但CPU占用偏高后来改成边缘触发非阻塞模式同样吞吐量下CPU占用下降了近三分之一。这种实战经验如果能在面试中讲出来含金量远高于背概念。4.3 进程间通信IPC方式盘点进程间通信的题目虽然在这份卷子里占比不大但作为安全方向的基础知识还是值得展开。常见IPC方式包括管道匿名管道和命名管道、消息队列、共享内存、信号量、套接字、信号。我的经验是如果题目问“哪种方式最适合传输大量数据”优先回答共享内存因为共享内存是效率最高的IPC方式——数据不需要在用户态和内核态之间反复拷贝。但共享内存的痛点在于需要自己处理同步问题通常要搭配信号量或互斥锁使用。而管道和消息队列虽然更安全、更简单但每次都经过内核缓冲区拷贝性能瓶颈明显。实际安全产品中的模块间数据交换经常是共享内存无锁队列的组合这属于进阶话题笔试能提到“共享内存配合无锁队列”会非常加分。5. 安全特色题目为什么安全厂商的C题“不按套路出牌”5.1 从缓冲区溢出看C/C的“双刃剑”这份试卷最有辨识度的是那几道跟安全强相关的题目。其中一题是分析一段有明显脆弱性的C代码找出问题并给出修复建议。典型的场景是这样的void processMessage(const char* msg) { char buffer[64]; strcpy(buffer, msg); // 危险未检查源字符串长度 // 继续处理buffer... }这段代码的问题是典型的栈缓冲区溢出strcpy不检查目标缓冲区容量如果msg长度超过63字节就会越界写入可能覆盖栈上的返回地址导致程序崩溃严重情况下可被利用执行任意代码——这就是经典栈溢出攻击的核心原理。修复方案有多个层次最简单的用strncpy并确保最后一位置\0更推荐的是用C的std::string替代C风格字符串如果坚持用C风格也应该传入缓冲区长度并在写入前校验。在安全产品开发中这类代码审查是日常工作试卷考这个其实是在考察候选人能不能识别出自己写的代码会变成漏洞入口。实际项目中我还见过一个更隐蔽的问题用snprintf也未必安全有些人把目标大小参数传错比如sizeof(buffer)在某些上下文里被编译器解释成指针大小导致仍然溢出。所以在代码评审中我始终强调大小参数的单位是“字节”而且要确保是目标缓冲区的总容量不是“剩余容量”。5.2 编码与加密基础笔试中的低频陷阱试卷还考了Base64编码、MD5、SHA系列算法的基本概念以及对称加密和非对称加密的区别。这块虽然分值不高但答错很伤印象分。Base64的考法通常是给你一个字符串让你算编码后长度或者问为什么编码后比原文长。原理是每3个字节24位拆成4组6位数据每组映射到64个可打印字符所以编码后长度约为原来的4/3倍不足3字节的补。这个知识点在安全产品中常用于解析邮件附件、网络协议传输数据等场景。对称加密和非对称加密的区别属于安全基础知识。AES、DES属于对称加密速度快适合大数据量加密但密钥分发困难RSA、ECC属于非对称加密用公钥加密、私钥解密或反过来解决了密钥分发问题但性能差。实际安全通信协议如TLS都是“非对称协商密钥 对称加密传输数据”的混合方案。笔试时如果能把TLS的握手思路也顺带提一句会给面试官留下知识体系完整的印象。5.3 操作系统底层与安全视角的延伸题有几道题虽然名称是“系统编程”但明显带着安全视角。比如问“如何防止core dump文件泄露敏感信息”“如何创建守护进程”。前者在安全产品中极为重要——服务崩溃后core dump里可能包含内存中的密钥、Token、明文数据如果dump文件权限控制不当就是信息泄露。守护进程的创建步骤是经典考点fork后父进程退出子进程调用setsid创建新会话再fork一次让进程不再是会话首进程然后修改工作目录、重设文件权限掩码、关闭标准输入输出错误。这套“双fork”的流程在考卷上经常出现但很多人只记得原因不记得完整流程。我的经验是多写几遍把每一步的“为什么”理解到位——第一fork是为了让子进程成为孤儿进程从而能调用setsid第二fork是为了防止进程重新获得控制终端。6. 笔试踩坑实录与备战建议6.1 我在刷这套题时踩过的“编译期”坑先说一个非常实际的坑这套卷子有一道题需要在卷面上填写代码没有编译器帮你检查。很多人平时写代码依赖IDE自动补全和编译报错一上手写就各种问题。最典型的错误包括忘记包含头文件、main函数签名写错、变量没有初始化就使用、以及C里NULL和nullptr混用。我强烈建议平时练习时切换成“不依赖补全”的模式直接用文本编辑器写题再丢进编译器看报错。这个过程不是为了纠结语法而是训练脑内模拟编译器的能力。尤其是在笔试现场代码一旦写错一个分号整题基本就白写了。另外要养成“写一点、编译一点”的习惯不要写完一大段再编译否则错误定位成本极高。6.2 时间分配这份卷子最该优先抢分的地方奇安信这份卷子总题量不小我在模拟练习时算过如果想在90分钟内从容做完时间分配大概是这样语言基础和内存部分控制在20到25分钟数据结构和算法部分30分钟左右系统与网络编程25分钟左右安全特色题15分钟到20分钟剩下时间用来检查。我的个人建议是遇到卡壳超过5分钟的题先跳过不要恋战。笔试最忌讳的就是在前面写一个复杂算法题消耗掉太多时间后面简单的基础题反而没时间写。尤其这份卷子的语言基础题基本是送分题拿到的概率远高于一道难度很高的动态规划题。另外如果代码题做不完也要把解题思路用注释写上去有些公司会给“思路分”。6.3 备战奇安信C/C方向的高效路径针对这类安全厂商C/C岗位笔试我总结了一套比较高效的备战路径按优先级排序第一优先级C对象模型、智能指针auto_ptr、unique_ptr、shared_ptr、weak_ptr、STL容器底层原理、内存泄漏排查方法。这是C/C岗的必考项也是后续面试的基础。第二优先级手写链表、二叉树、字符串相关算法题主攻LeetCode简单到中等难度覆盖高频题型即可。第三优先级Linux系统编程多线程同步和网络编程的对比类题目select/poll/epoll、进程线程区别、IPC方式。第四优先级安全基础知识缓冲区溢出原理、常见编码算法、加密算法分类、以及这些知识如何影响代码编写。还有一点很重要多了解奇安信的产品线。他们做终端安全、网络安全、威胁情报、大数据安全分析笔试题目里那些“身份认证加密”“网络流量分析”的背景题如果准备时有所了解答题时不至于连题目意图都看不懂。我见过不少人栽在“看不懂题目想考什么”上其实出题人想考的就是一个很基础的知识点只是套了一个安全场景的外壳。6.4 最后提醒几个容易在细节上丢分的点第一个要注意的是代码风格。笔试虽然不会因为代码风格扣大分但如果代码变量命名为a、b、c函数写完连个空行都不打阅读体验会很差。万一后续进入面试环节面试官翻到你的笔试代码印象分就会受影响。第二个是“输出格式”。有些题目要求输出特定的格式比如每个数用空格隔开、行末没有多余空格、浮点数保留几位小数。这些细节我见过太多人因为没看清题直接丢分。考试时先读清楚输出要求再动手写代码省得写完再改浪费时间。第三个是针对C环境的一个隐患如果笔试题是“找出程序输出”要特别留意题目有没有暗示编译器版本和标准。C11、C14、C17在部分行为上有差异比如auto推导规则、结构化绑定等如果你用C17的语法习惯去解释C11标准的代码很容易选错答案。养成先看题目标注的标准版本的习惯。写在最后的一点个人经验我做C/C开发这些年大大小小的笔试面试经历过不少最大的体会是笔试题其实是一个双向筛选的过程。公司通过题目告诉你“我们关注什么”你通过做题判断自己适不适合这家公司。奇安信这份试卷确实能让人感受到它对底层功底和系统安全性的重视——这恰恰是安全产品开发中最需要的素质。如果现在还在准备阶段我建议别只看题、背题尽量把每道题背后的知识体系拉通说出这个知识点是什么、为什么这样设计、实际工程里哪里会用到它。这套功夫练下来受益的不仅是笔试更是未来真刀真枪写代码的那几年。