360研发工程师笔试题解析:C/C++与系统原理核心考点
2026/8/30 5:06:19 网站建设 项目流程

过来人聊聊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&操作符下不会退化。这个知识点必须刻在脑子里。

字符串相关的考察也很频繁,比如sizeofstrlen的区别:

char str[] = "hello"; printf("%d %d\n", sizeof(str), strlen(str));

sizeof(str)统计的是整个字符数组的大小,包含结尾的\0,所以是6;strlen只统计到\0之前,是5。更阴的考法是把它放到函数参数里:

void func(char str[]) { printf("%d\n", sizeof(str)); }

这时数组参数退化为指针,sizeof(str)结果是8(64位系统下指针大小),而不是6。这就是笔试里常见的“数组作为函数参数会退化为指针”的考察点。

另外内存分配相关的题也值得注意。例如mallocfree的配对使用,动态分配二维数组的几种方式,以及经典的内存泄漏场景。360的题目喜欢把内存泄漏和字符串操作结合,比如:

char *getMemory() { char p[] = "hello world"; return p; }

这个函数返回了局部数组的地址,函数结束栈帧销毁后,返回的指针变成野指针,调用方再用就属于未定义行为。这是面试官极爱问的“返回局部变量地址”陷阱。

2.2 数据结构与算法:链表是主角

链表操作是笔试大题的高频选择,尤其是单链表的反转、删除倒数第N个节点、判断是否有环这些经典问题。360考过的题目里有单链表逆序,要求写出完整代码。

单链表逆序的标准迭代法思路是维护三个指针prevcurrnext,逐个翻转节点的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连接建立后,调用closeshutdown的区别。简单说,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这套题的风格,我建议的复习顺序是:

  1. C语言基础:重新过一遍《C程序设计语言(K&R)》,重点关注指针、数组、结构体、内存管理章节。
  2. 数据结构:把链表、栈、队列、二叉树、哈希表这五类基础结构的操作写到烂熟。
  3. 算法:按专题刷LeetCode的easy和medium题,重点覆盖数组、字符串、链表、动态规划、贪心。
  4. 操作系统:通读《深入理解计算机系统》第8、9章,也就是进程与虚拟内存部分,结合题目做练习。
  5. 网络:重点掌握TCP/IP状态机,HTTP协议基本概念,用Wireshark抓包对比三次握手的实际报文加深理解。

关于简历和笔试备考时间安排,建议在正式校招季开始前至少两个月开始专项训练,每周保持三个晚上刷题,白天做项目或者复习理论,节奏控制得比突击式复习效果要好得多。毕竟笔试考的是稳定输出,不是考前爆发力。

6. 写在最后:笔试与真实研发的差距

做过这套题后我有一个很直观的感受:笔试题的考察点,其实和360研发日常工作需要的基本功高度重合。虽然实际工作中不会有人天天让你反转链表,但调试内存问题、分析崩溃转储、排查网络异常这些任务,背后全是对底层原理的深刻理解。一套合格的笔试题目,本质上是一面镜子,照出你对这些基础知识的理解程度。

如果你正在准备笔试,遇到不会的题不要只记答案,一定要追问自己“为什么”。比如为什么数组名在sizeof&下不退化为指针,为什么TCP主动关闭方要等待2MSL,为什么快慢指针能检测环。想清楚这些原理,比多做十道同类型的题更有价值。我自己当年复习时最大的收获,就是在反复追问这些“为什么”之后,笔试和面试都变得顺畅了很多。

最后给一个实操层面的建议:平时刷题时把代码敲到编译运行通过还不算完,试着脱离IDE,在纯文本编辑器里写代码并编译,模拟笔试环境的限制。这个习惯一开始会很不习惯,但它能真正暴露你写代码时的盲点,等到实际机考时会明显更从容。祝准备笔试的朋友都能拿到心仪的Offer。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询