ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

360研发工程师笔试题解析:C/C++与系统原理核心考点

360研发工程师笔试题解析:C/C++与系统原理核心考点 过来人聊聊360这套2016年的研发工程师笔试题。虽然年份有点久但作为国内安全领域的头部公司360的笔试题风格在业内一直很有代表性尤其是对C/C功底、操作系统原理和网络协议的考察到现在依然能被很多公司的笔试题目“借鉴”。网上关于这套题二的讨论不少我把核心考点、典型题目和解题思路重新梳理了一遍结合我当年刷题和后来面试别人的经验给准备校招或者跳槽的朋友做个参考。1. 360笔试的底层逻辑它到底想招什么样的人先说结论360的研发笔试题尤其是这套2016年的二核心筛选目标不是“你背了多少题”而是“你有没有扎实的计算机底层功底”。安全公司对内存布局、指针操作、系统调用这些底层机制的敏感度要求极高因为漏洞挖掘、逆向分析、外挂对抗这些核心工作全都在跟这些底层细节打交道。1.1 考察目标与岗位画像从题目结构能反推出360对研发工程师的能力期望基本分三层第一层是语言功底。C/C是绝对主力Java和Linux相关的内容也有覆盖这和360大量底层组件、客户端工具用C/C实现直接相关。指针、数组、内存管理、字符串操作这些是必考区而且考得相当细细到很多人平时写业务代码根本不会注意的程度。第二层是系统原理。操作系统里的进程线程、死锁、内存管理、文件系统网络里的TCP协议、socket通信状态机这些不是零零散散出几道而是成体系地出现。安全研发为什么要懂这些因为无论是分析恶意样本的行为还是写网络抓包分析工具都需要对系统底层机制有预见性。第三层是算法思维。动态规划、链表操作、字符串处理这类经典算法题占一定比重但难度整体属于中等偏基础不像某些大厂那样动不动就上Hard级别的压轴题。这说明360更看重候选人能否用常见数据结构和基础算法解决实际问题而不是堆竞赛技巧。1.2 题量与时间的博弈策略这套题的时间压力是真实存在的。我印象里选择题就有几十道涵盖面又广后面还有大题正常情况下做完并保证正确率是有难度的。所以笔试不仅是考你会不会还在考你在有限时间内如何取舍、如何分配精力。实操层面的策略是先快速过一遍全部题目把有把握的题先做掉标记出需要犹豫的最后集中攻克。千万不要在一道选择题上死磕超过五分钟得不偿失。尤其是后面的大题分值权重高哪怕只能写出核心思路和部分代码也比空着强。阅卷时往往能看到候选人写在草稿上的思路有思考痕迹的答案比空白卷好太多。2. 核心考点拆解从真题反推复习重点把网络上能找到的这套题二回忆版汇总起来看考点分布比较集中我按模块拆开讲。2.1 C/C指针、数组与内存布局是重灾区数组和指针的辨析是C语言笔试最经典的考点360也不例外。有一个典型题目是给出类似这样的代码#include stdio.h int main() { int a[5] {1, 2, 3, 4, 5}; int *p (int *)(a 1); printf(%d, %d\n, *(a 1), *(p - 1)); return 0; }问你输出是什么。很多人栽在a 1上。这里a是整个数组的地址类型是int (*)[5]对它加1等于跨越了整个数组长度也就是指向数组末尾之后的位置。所以p - 1指向的是数组最后一个元素即5。而*(a 1)中数组名a在表达式里退化为指向首元素的指针加1后指向第二个元素值是2。正确答案是2, 5。这类题考察的不只是表面语法而是数组名和取地址符在不同上下文中的语义变化。数组名在大多数表达式中会退化为指针但在sizeof和操作符下不会退化。这个知识点必须刻在脑子里。字符串相关的考察也很频繁比如sizeof和strlen的区别char str[] hello; printf(%d %d\n, sizeof(str), strlen(str));sizeof(str)统计的是整个字符数组的大小包含结尾的\0所以是6strlen只统计到\0之前是5。更阴的考法是把它放到函数参数里void func(char str[]) { printf(%d\n, sizeof(str)); }这时数组参数退化为指针sizeof(str)结果是864位系统下指针大小而不是6。这就是笔试里常见的“数组作为函数参数会退化为指针”的考察点。另外内存分配相关的题也值得注意。例如malloc和free的配对使用动态分配二维数组的几种方式以及经典的内存泄漏场景。360的题目喜欢把内存泄漏和字符串操作结合比如char *getMemory() { char p[] hello world; return p; }这个函数返回了局部数组的地址函数结束栈帧销毁后返回的指针变成野指针调用方再用就属于未定义行为。这是面试官极爱问的“返回局部变量地址”陷阱。2.2 数据结构与算法链表是主角链表操作是笔试大题的高频选择尤其是单链表的反转、删除倒数第N个节点、判断是否有环这些经典问题。360考过的题目里有单链表逆序要求写出完整代码。单链表逆序的标准迭代法思路是维护三个指针prev、curr、next逐个翻转节点的next指向。实现不复杂但也考察了边界条件处理空链表、只有一个节点、链表很长时会不会丢节点。很多人递归版本写得很顺一问迭代法却卡壳这两种写法最好都练熟。动态规划在选择题里也有体现。我印象里有一道关于最长公共子序列或者背包变体的题要求分析时间复杂度和状态转移方程。这类题其实就是在考察你是否理解DP的本质重叠子问题和最优子结构。复习时不要把精力花在背题上而是要把常见的DP模型线性DP、区间DP、背包、LIS/LCS的推导过程吃透。二叉树相关的题目也有比如已知前序遍历和中序遍历让你推断后序遍历或者计算二叉树的深度。这类题有固定套路但需要熟练。2.3 操作系统从进程线程到死锁进程和线程的对比是送分题也是必考题但360喜欢换着花样考。比如问“进程中线程共享哪些资源”正确答案是堆、全局变量、文件描述符不共享的是栈和寄存器。很多人记反了把栈当成共享的这题就直接丢分。死锁产生的四个必要条件互斥、请求与保持、不可剥夺、循环等待也是必背内容。更进阶的考法会给一个具体的资源分配场景让你判断是否可能发生死锁以及如何通过破坏某个必要条件来预防。这类题需要你真正理解而不是死记硬背。内存管理里的虚拟内存、分页分段、页面置换算法也时有出现。LRU算法是考察最多的页面置换策略偶尔会让你用双向链表加哈希表去实现LRU缓存。这个题在LeetCode上也有146题属于面试高频原题建议重点准备。360出这类题其实很合理因为安全软件在做内存扫描和行为监控时和操作系统的内存管理机制打交道极深。2.4 网络协议TCP状态机是硬骨头网络部分最常见的考察点是TCP三次握手和四次挥手的状态变迁。TIME_WAIT状态为什么存在、主动关闭方为什么需要等待2MSL、CLOSE_WAIT状态出现在什么场景——这些都是面试笔试高频问答。有一个具体的考察方式假设客户端主动关闭连接问客户端和服务端各自经历的状态序列。客户端是FIN_WAIT_1 - FIN_WAIT_2 - TIME_WAIT - CLOSED服务端是CLOSE_WAIT - LAST_ACK - CLOSED。如果对状态机不熟这种题基本没法蒙。HTTP协议也会涉及比如GET和POST的区别、常见状态码含义。2016年那会儿HTTP/2刚兴起不久如果题目里出现了HTTP/2多路复用的概念很多人会措手不及。这类前沿题目其实是在考察你是否持续关注技术演进而不是只守着课本。3. 典型题目精讲把这几个最容易错的题掰开揉碎3.1 指针运算与数组的边界问题除了前面说的a 1还有一个常见变体int a[2][3] {1, 2, 3, 4, 5, 6}; int *p a[1]; printf(%d\n, *(p 1));这里的a[1]是第二行的数组名又退化成了指向第二行首元素的指针所以*(p 1)取到的是第二行第二个元素即5。如果题目改成*(*(a 1) 1)那a 1是指向第二行的行指针解引用后变成指向第二行首元素的指针再偏移1解引用结果同样是5。这两种写法在笔试中经常互换出现要能够一眼看懂。另一个常见坑是char *str hello; str[0] H;这段代码在运行时崩溃。因为str指向的是字符串字面量存储在只读数据段试图修改它会触发段错误。而如果用char str[] hello则没问题因为它是栈上的字符数组可修改。这个区别是C语言笔试题的经典陷阱同时也是实际开发中容易犯的错误。3.2 排序算法的理解深度排序算法在选择题里经常考察时间复杂度、稳定性、最好最坏情况。比如快速排序在什么情况下时间复杂度退化到O(n²)答案是当每次选择的基准值都极端不平衡时比如对已经有序的数组选取第一个元素作为基准。这个知识点很多人背过但是换个形式问就蒙了。还有一种考法是给出一组数据在某一轮排序后的中间状态让你判断用了哪种排序算法。这类题需要对各种排序的执行过程有画面感。我在为面试做准备时会把插入排序、选择排序、冒泡排序、快排、归并排序的每一轮打印出来观察理解每种算法的真实行为而不是只背结论。稳定性的记忆技巧选择排序是不稳定的交换可能跨越相同元素快排不稳定堆排不稳定稳定的是插入、冒泡、归并、基数。这个知识点要熟练到条件反射。3.3 网络编程相关的细节题有一类题让不少人翻车TCP连接建立后调用close和shutdown的区别。简单说close会立刻释放文件描述符并尝试发送FIN但如果socket的发送缓冲区还有数据这些数据可能发不完shutdown可以分别关闭读方向和写方向更精细控制。这类题在实际网络编程中很重要笔试中会用选择题或简答题出现考察的是真实场景中的系统API理解。4. 实操演练编程题完整解题与复盘记录4.1 单链表反转的标准实现我在这类题上吃过亏第一次写的时候只注意了主流程忽略了空指针判断。后来整理出了一套固定模板#include stdio.h #include stdlib.h typedef struct Node { int data; struct Node *next; } Node; Node* reverseList(Node* head) { Node *prev NULL; Node *curr head; Node *next NULL; while (curr ! NULL) { next curr-next; curr-next prev; prev curr; curr next; } return prev; }关键点在于next必须先保存否则一旦修改curr-next原来的后继就找不到了。这是我刚开始写的时候踩过的坑——顺序反了会直接导致链表断链。如果想用递归写法核心思路是假设当前节点之后的部分已经反转完毕只需把当前节点接到尾部Node* reverseListRecursive(Node* head) { if (head NULL || head-next NULL) { return head; } Node *newHead reverseListRecursive(head-next); head-next-next head; head-next NULL; return newHead; }递归写法在笔试中写出来很加分但面试时一定要能讲清楚递归的调用栈变化否则容易被追问到卡壳。4.2 判断链表是否有环环形链表的判环问题是高频中的高频。最经典的解法是快慢指针一个每次走两步一个每次走一步如果存在环两个指针必然相遇。int hasCycle(Node *head) { if (head NULL || head-next NULL) { return 0; } Node *slow head; Node *fast head; while (fast ! NULL fast-next ! NULL) { slow slow-next; fast fast-next-next; if (slow fast) { return 1; } } return 0; }这里有个细节值得展开为什么快指针每次走两步慢指针每次走一步就一定能相遇因为当慢指针进入环时快指针已经在环内某个位置。此后每次移动快指针相对慢指针追赶一步的距离。环的长度有限所以最多走一圈必定追上。如果你用快指针走三步的写法在某些环长和入环点的组合下反而可能永远追不上会反复跳过。4.3 最长括号匹配问题动态规划的题目在笔试中常常以中等难度出现。最长有效括号子串是LeetCode 32题也是我在整理这套卷子时发现的高相似度题目它的思路非常典型可能以类似形式出现。常用的解法是栈也可以动态规划int longestValidParentheses(char *s) { int len strlen(s); if (len 2) return 0; int *dp (int *)calloc(len, sizeof(int)); int maxLen 0; for (int i 1; i len; i) { if (s[i] )) { if (s[i - 1] () { dp[i] (i 2 ? dp[i - 2] : 0) 2; } else if (i - dp[i - 1] 0 s[i - dp[i - 1] - 1] () { dp[i] dp[i - 1] 2 (i - dp[i - 1] 2 ? dp[i - dp[i - 1] - 2] : 0); } if (dp[i] maxLen) maxLen dp[i]; } } free(dp); return maxLen; }这个状态转移方程第一次看会有点绕但拆开理解就清晰了dp[i]表示以i结尾的最长有效括号子串长度。如果s[i]是)且前一个是(那它们直接配对。如果前一个是)则需要往前跳过一段有效子串后再找到配对的(。刷DP题没捷径多画几个例子手动模拟状态变化比死记代码管用得多。5. 常见问题排查与备考避坑指南5.1 为什么代码本地跑通了笔试却过不了这是校招同学问得最多的问题。本地环境能编译运行不代表在线笔试系统能通过。常见的坑有三个。第一是输入输出格式不匹配。很多在线笔试要求严格的输出格式多打一个空格、少换一行都会判错。平时练习就要养成用标准输入输出写代码的习惯别依赖IDE的调试工具。第二是内存问题。笔试系统的编译器可能开了更严格的内存检查未初始化的变量、越界访问在本地可能“碰巧没事”在判题环境里就会崩溃。我见过有人用未初始化的局部变量做计算在本地编译器和在线OJ上给出不同结果。这个习惯一定要改。第三是算法复杂度和数据规模不匹配。题目给出的数据范围暗示了期望的时间复杂度但很多人不看数据范围直接用暴力解法提交后超时。收到“Time Limit Exceeded”时优先考虑优化算法而不是优化常数。5.2 选择题的排除法技巧360这套笔试题里选择题占大头。有些题考点你恰好没复习到也不能直接放弃要善用排除法。我总结的做题经验是优先排除那些“绝对化”的选项。比如“必定”“一定”“总是”这类绝对化的描述在计算机领域往往是错的因为真实系统的行为总是在边界条件下有例外。其次如果有两个选项表达的意思高度相似通常两个都是错的。这个方法虽然不能保证百分百正确但在模棱两可时能显著提高命中率。还有个小技巧遇到时间复杂度的选择题把数据规模代入选项验证。如果题目说数据量是10的5次方选项里有O(n²)和O(n log n)那O(n²)大概率不是出题人想要的答案因为10的5次方平方是10的10次方在普通OJ上肯定超时。5.3 考场上时间不够怎么办据我了解不少人在考这套题时都感觉时间紧。合理的时间分配非常重要。我的策略是先把所有题扫一遍把会做的、有把握的题全部做完并标记。然后把剩余时间投给分值高、有思路的大题。控制在选择题上平均每题不超过1分半。一道卡壳超过三分钟的选择题先标记跳过最后有时间再回头思考。编程大题优先写能跑通的暴力解法把部分分数拿住再考虑优化。很多同学一上来就想写最优解结果写了一堆边界条件处理不完代码报错最后连基本分都没拿到。先把能过示例数据的版本交上去再逐步优化从零到有比从无到有重要得多。5.4 复习路线与参考书籍针对360这套题的风格我建议的复习顺序是C语言基础重新过一遍《C程序设计语言KR》重点关注指针、数组、结构体、内存管理章节。数据结构把链表、栈、队列、二叉树、哈希表这五类基础结构的操作写到烂熟。算法按专题刷LeetCode的easy和medium题重点覆盖数组、字符串、链表、动态规划、贪心。操作系统通读《深入理解计算机系统》第8、9章也就是进程与虚拟内存部分结合题目做练习。网络重点掌握TCP/IP状态机HTTP协议基本概念用Wireshark抓包对比三次握手的实际报文加深理解。关于简历和笔试备考时间安排建议在正式校招季开始前至少两个月开始专项训练每周保持三个晚上刷题白天做项目或者复习理论节奏控制得比突击式复习效果要好得多。毕竟笔试考的是稳定输出不是考前爆发力。6. 写在最后笔试与真实研发的差距做过这套题后我有一个很直观的感受笔试题的考察点其实和360研发日常工作需要的基本功高度重合。虽然实际工作中不会有人天天让你反转链表但调试内存问题、分析崩溃转储、排查网络异常这些任务背后全是对底层原理的深刻理解。一套合格的笔试题目本质上是一面镜子照出你对这些基础知识的理解程度。如果你正在准备笔试遇到不会的题不要只记答案一定要追问自己“为什么”。比如为什么数组名在sizeof和下不退化为指针为什么TCP主动关闭方要等待2MSL为什么快慢指针能检测环。想清楚这些原理比多做十道同类型的题更有价值。我自己当年复习时最大的收获就是在反复追问这些“为什么”之后笔试和面试都变得顺畅了很多。最后给一个实操层面的建议平时刷题时把代码敲到编译运行通过还不算完试着脱离IDE在纯文本编辑器里写代码并编译模拟笔试环境的限制。这个习惯一开始会很不习惯但它能真正暴露你写代码时的盲点等到实际机考时会明显更从容。祝准备笔试的朋友都能拿到心仪的Offer。
返回列表