这套“腾讯2016研发工程师笔试题二”虽然过去了几年但放在今天看依然是检验C基本功、数据结构、操作系统和网络基础的很好标尺。我当年复习校招时也刷过这套题后来面试别人时也偶尔会抽出其中的知识点来聊确实有代表性。这篇文章不打算简单贴答案而是把每一类题目背后想考察的底层逻辑拆开来讲清楚同时结合我在实际开发里遇到的场景做补充希望能帮你建立一套“以不变应万变”的知识框架。1. 整体考点结构拆解1.1 五类核心考察方向腾讯的研发工程师笔试题二整体风格偏基础、偏原理少见偏题怪题但覆盖面很广所有题目都指向一个目标考察候选人是否具备扎实的计算机底层功底。这套题主要围绕五个方向展开C/C语言底层机制sizeof、指针、内存布局、虚函数数据结构与算法树、链表、排序、查找、哈希操作系统核心概念进程与线程、死锁、内存管理、文件系统计算机网络基础TCP/IP分层、三次握手、滑动窗口Linux命令与调试进程排查、日志分析、性能排查有意思的是这套题几乎没有涉及具体框架或工具链比如当年的主流框架、前端工程化时代刚刚兴起的技术一概没有出现。这跟今天很多公司面试上来就问你“用过Redis哪些数据结构”“会不会调优JVM”的风格完全不同。它不是考察“你用过什么”而是考察“你理解得有多深”。如果你未来想去大厂做偏底层的岗位比如云平台研发、存储引擎、高性能中间件这种题目风格其实比框架题更值得反复刷。1.2 为什么选择这种出题方式笔试环节的最大价值是在最短时间内筛选出“计算机学科功底扎实”的候选人。框架和技术栈可以入职后快速上手但指针和内存的理解短板很难短期突击操作系统和网络的认知深度也不是背八股能弥补的。一线研发岗天天在处理内存、并发、网络延迟、数据一致性如果底层概念模糊很容易在线上出大事故。我自己在面试候选人时也偏好这种出题风格先问几道看似基础的选择题再通过追问判断他到底是“背过”还是“想明白了”。比如问他sizeof一个空类是多少很多人能答出“1”但当你追问“为什么是1不是0”时能答清楚的人不到三成。知识的“迁移能力”才是笔试想测的东西这也是这套题到今天依然值得一刷的原因。2. C/C经典考点实战解析2.1 数组与指针的sizeof陷阱这套卷子里出现频率最高、也最容易让人翻车的考点之一就是数组名和指针在sizeof下的区别。很多人在牛客网上刷题时反复做错同一类题目不是没记住结论而是没理解数组名在什么场合会“退化”成指针。看下面这个经典例子char str[] hello; char *p str; int a[10]; sizeof(str); // 6包含结尾的\0 sizeof(p); // 4或8取决于平台位数64位系统为8 sizeof(a); // 4010个int数组名在sizeof中代表整个数组的字节数而在作为函数参数传递时会退化成指向首元素的指针。因此你在函数内部sizeof一个传入的数组形参永远只能得到指针大小而不是数组大小。这就是为什么C/C源码里约定俗成地要把数组长度一起传进函数。我在实际工作中踩过一个很类似的坑写一个日志模块时用memcpy拷贝固定大小的配置结构体没有用sizeof(struct)而是用了个硬编码数字后来结构体里加了一个字段线上日志数据被截断排查了一个多小时。凡是涉及内存大小的地方一律用sizeof而不是魔法数字这是C研发的基本素养。2.2 字符串与内存越界的隐藏风险字符串处理是笔试题的另一个重灾区。经典的strcpy拼接问题、strlen与sizeof混用、缓冲区溢出这套卷子里都有覆盖。很多人在刷题时容易忽略一个关键点strlen计算的是字符串长度不包含结尾的\0而strcpy会连\0一起拷贝。如果把strlen的结果当作拷贝字节数来用会漏掉结尾符轻则打印时出现乱码重则越界读写导致程序崩溃或被利用。char src[] tencent; char dst[20]; // 危险写法 strncpy(dst, src, strlen(src)); dst[strlen(src)] \0; // 得手动补一个但很容易忘 // 推荐写法 snprintf(dst, sizeof(dst), %s, src);snprintf在C99标准后已成为业界推荐的字符串格式化方式它会在末尾自动补\0且根据传入的缓冲区大小限制写入字节数。我见过不少高级工程师写起代码来还是喜欢用sprintf一旦格式化内容超长缓冲区直接被打破线上偶发崩溃排查起来极其痛苦。不要在字符串处理上炫技用最稳妥的API最省心。2.3 虚函数与内存布局的理解C面向对象相关的考点让人最头疼的就是虚函数表vtable和虚函数表指针vptr。笔试常问两个问题一个含有虚函数的类sizeof是多少为什么构造函数不能是虚函数先说第一个问题。一个类只要包含至少一个虚函数就会在对象内存布局的最前面多出一个指针vptr指向该类的虚函数表。所以在32位平台下sizeof会至少多出4字节在64位平台下会多出8字节。如果类里既没有虚函数也没有其他成员变量空类的大小是1字节这是编译器为了确保“同一类型的不同对象具有不同地址”而做的特殊处理。再说第二个问题。构造函数天生不能是虚函数原因在于对象在构造过程中vptr还没有完成初始化虚函数表还没有正确建立此时调用虚函数无法完成动态绑定。如果你尝试把构造函数声明为虚函数编译器会直接报错这算是语言规范层面的强制防护。我面试别人时经常会追加一个追问“虚函数表里还有哪些信息”能答出“除了函数指针还可能在-2偏移位置存type_info指针用于RTTI”的候选人通常对编译原理和对象模型有更深入的理解。这道题考察的深度弹性很大而2016年这套笔试题恰好把最基础的部分抓出来了。3. 数据结构与算法题深度拆解3.1 二叉树遍历的推导与还原这套笔试题在数据结构部分几乎必然包含二叉树相关题目。出题形式通常有两种一是直接给出先序和中序序列让求后序二是给出中序和后序让推导层序或先序。这种题本质是考察递归思想和对遍历定义的理解不是背序列的交互规则。以“先序 中序推导后序”为例核心思路只有两条先序序列的第一个元素一定是整棵树的根节点。在中序序列中根节点左侧的所有元素构成左子树的中序右侧构成右子树的中序。然后递归处理左子树和右子树就能逐步还原整棵树。面试或者笔试时画一遍比记结论可靠得多。举个例子先序ABDCE 中序DBACE先序第一个是A所以A是根。中序中A左边的DB是左子树中序右边的CE是右子树中序。回到先序找左子树元素B、DB在前说明B是左子树的根D是B的左孩子。右子树C、E先序中C在前说明C是右子树的根E是C的左孩子。整棵树就复原了。这种题一定要自己多画几遍不要背口诀理解了递归才能“反手就写”。在实际工程中遍历推导的用途不仅限于刷题。比如序列化一棵二叉树时最常见的做法是保存先序和中序两套序列反序列化时再还原再比如数据库索引的B树结构分析底层也是递归理解的问题。基本功和工程能力从来不是割裂的。3.2 排序算法与复杂度边界条件排序算法在历年的腾讯笔试题中从不缺席。高频考察点包括快速排序的“最坏情况”与优化手段归并排序的稳定性与空间复杂度堆排序的建堆和调整过程各排序算法稳定性的横向对比这里比较关键的知识点是快速排序的时间复杂度分析。平均情况下快速排序的时间复杂度是O(n log n)在数据分布随机时效率最高但如果每次选择的基准值都是当前区间的最大值或最小值退化成O(n^2)。所以工程实现里会采用三数取中法选取基准值或者当递归深度过深时改为堆排序库里实际使用的introsort就是快排加插排加堆排的组合。笔试不会深入到这么细但如果你能在回答排序复杂度时顺带提一句“STL的std::sort其实是内省排序”面试官对你的印象会明显不一样。稳定性也是高频考点。选择排序、快速排序、堆排序都不稳定插入排序、冒泡排序、归并排序是稳定的基数排序的稳定性则取决于具体实现。为什么需要稳定排序一个常见的业务场景是先按时间排序再按用户ID排序如果排序算法不稳定两次排序后时间的相对顺序可能被破坏。类似的需求在报表里很常见。3.3 链表操作与边界条件链表的题目无论笔试还是面试都是常青树。这套笔试题里涉及链表的题目难度不算大但很考验边界处理。经典题型包括单链表反转迭代和递归两种写法都要会快慢指针找中间节点判断链表是否有环链表相交判断删除倒数第N个节点以单链表反转为例迭代法维护三个指针prev、curr、next核心逻辑是每次迭代保存下一个节点然后反转当前节点的指向再整体前移。没有正确保存next的话链表会直接断掉。递归写法更简洁但容易忘边界基线条件是“当前节点或当前节点的下一个节点为空”直接返回当前节点。这种题目在工程上的价值主要体现在共享内存池、无锁队列、操作系统空闲链表等场景。我还真在一个底层网络库的代码里见过作者手写链表节点结果插入时忘了更新prev指针导致双向链表遍历死循环。刷题时练出的边界感是可以迁移到工业级代码里的。4. 操作系统与网络协议核心要点4.1 进程与线程的底层差异操作系统相关题目中出现频率最高的是进程与线程的区别。这道题几乎是所有互联网公司笔试的“必答题”腾讯这套题也不例外。标准回答是进程是资源分配的基本单位线程是CPU调度的基本单位同一进程的多个线程共享地址空间和文件描述符等资源而不同进程之间则通过IPC机制通信。但笔试如果只答到这一层只能拿一半分数。真正能拉开差距的是以下几个细节线程切换比进程切换开销小的根本原因是同进程内线程共享页表不需要切换地址空间只有寄存器上下文和栈需要切换进程通信手段有管道、消息队列、共享内存、信号、套接字、信号量等其中共享内存是速度最快的IPC方式因为它省去内核态和用户态之间的数据拷贝线程模型在Linux上的实现本质上是clone系统调用由内核统一管理所谓“用户态线程”协程则是另一套独立的调度机制。我在做高并发服务时对“线程切换开销”的体会特别深。早期用“一连接一线程”模型连接数一涨上下文切换成了明显的瓶颈。后来改成基于epoll的事件驱动模型配一个极小的线程池处理耗时操作性能提升非常明显。这套底层认知到位了再看Nginx的高性能原理和Redis的单线程模型都会有种“原来如此”的透亮感。4.2 死锁的必要条件与应对思路死锁是操作系统基础题里的经典知识点四个必要条件互斥、持有并等待、不可剥夺、循环等待。这四点缺一不可破坏其中任何一个死锁就不会发生。笔试经常以“下面哪些情况会导致死锁”这类形式出现实际就是考这四个条件的识别。工程上处理死锁的常见思路包括加锁时统一顺序避免循环等待使用带超时的锁获取机制不让线程无限期等待尽量缩小锁粒度减少持有锁的时间使用tryLock检测锁状态拿不到就把资源让出千万不要以为死锁只存在于“操作系统教材”里。我处理过一次线上事故两个服务互相调用且都先自己加锁再远程调用对方调用链一旦交叉两边就互等对方释放资源最终导致接口大面积超时。事后复盘本质就是循环等待。分布式环境下死锁比单机更难排查因为没有全局视角。4.3 TCP连接建立与断开的关键过程网络部分TCP三次握手和四次挥手属于基础中的基础但细节能问得很深。腾讯这套笔试题不会绕过这个点常见的考察角度包括三次握手的每一步做了什么SYN、SYNACK、ACK各代表什么状态为什么是三次而不是两次或四次四次挥手中TIME_WAIT状态存在的意义SYN Flood攻击的本质是什么有个高频追问为什么建立连接是三次握手断开连接却是四次挥手因为TCP是全双工的。建立连接时服务端收到SYN后可以同时回SYNACK合并成一步断开连接时某一端收到FIN后可能还有数据要发送所以ACK和FIN必须分开发于是多了一次交互。TIME_WAIT也是重点。主动关闭连接的一方在收到对端的FIN后会进入TIME_WAIT状态持续2*MSL最大报文段生存时间一般为2分钟目的有两个一是确保最后的ACK能到达对端如果ACK丢失可以重发二是让本连接的所有旧报文在网络中自然消失避免干扰后续复用同一四元组的新连接。实际开发中如果你用短连接频繁访问MySQL或Redis在ss -tan里经常能看到一堆TIME_WAIT这就是主动关闭方留下的。如果量大到影响端口分配就得考虑连接复用或调整参数。4.4 滑动窗口与拥塞控制的工程理解TCP的可靠性除了靠确认重传还靠流量控制和拥塞控制。笔试题里对这块的考察通常停留在概念层面比如滑动窗口的作用是“控制发送速率、避免接收方来不及处理”拥塞控制的算法有“慢启动、拥塞避免、快重传、快恢复”。但开发者在实战中需要有更具体的感知。比如内网调用延迟突然升高你抓包发现大量TCP Dup ACK说明网络丢包了TCP正在触发快重传。这时候去查交换机光模块、链路丢包率比盲目优化应用代码更有意义。再比如为什么云厂商的负载均衡实例通常建议把tcp_tw_reuse开起来因为高并发短连接场景下主动断开方的TIME_WAIT数量会极其庞大不处理就把端口池耗尽。另外有一个不少人容易混淆的知识点流量控制是端到端的窗口拥塞控制是整个网络的状态判断。前者通过接收方的通告窗口来限制发送后者通过拥塞窗口来动态调整。实际发送窗口取两者较小值。理解这层关系对排查网络性能问题很有帮助。5. 数据库与Linux的常见考点5.1 SQL查询与索引优化基础大厂笔试对数据库的考察一般不会出特别复杂的SQL主要看你对索引和查询计划的理解。腾讯这套题在数据库部分常见考点是给定一张表结构判断某条SQL能否用到索引或者比较几种写法的性能差异。这里最重要的一个概念是“最左前缀原则”。创建联合索引(a, b, c)后以下查询能用索引WHERE a 1 AND b 2 WHERE a 1但以下查询很可能走全表扫描WHERE b 2 WHERE c 3 AND a 1第二种写法不是因为字段顺序不同而失效MySQL优化器一般会尝试优化顺序真正导致失效的是带头字段缺失导致联合索引的B树无法按最左匹配定位。还有一个高频考点是“为什么不要对索引列使用函数”。比如WHERE DATE(create_time) 2024-01-01会导致索引失效正确写法是WHERE create_time 2024-01-01 AND create_time 2024-01-02。原理是B树的索引是按原始值排序的对字段做函数操作后原索引顺序不再匹配优化器只能放弃索引扫描。实际线上排查慢查询时最常用的命令就是EXPLAIN SELECT ...看type字段是否为ALL全表扫描、key字段是否为空。如果发现一个高频查询正在做全表扫描通常第一反应不是加索引而是先看能不能改写SQL让查询条件落在索引列上。5.2 事务隔离级别与锁机制数据库的ACID和事务隔离级别也是腾讯笔试题里反复出现的内容。四个隔离级别分别是读未提交、读已提交、可重复读、串行化其中MySQL InnoDB默认是可重复读。很多候选人能背出这四个名字但问起“可重复读和读已提交在底层实现上有什么区别”就露馅了。简单说读已提交通过MVCC每次读都生成当前最新的快照所以同一个事务内两次查询可能看到不同数据可重复读则是事务开始后第一次读生成快照之后每次读都从这个快照里取数据保证事务内的读一致性。这也是为什么网上很多文章说“MySQL默认隔离级别解决了幻读但严格来说没有完全解决”的原因唯一能彻底解决幻读的是串行化。加锁的细节也是笔试的常见切入点。行锁、间隙锁、临键锁这几个概念很容易混。间隙锁锁的是“记录之间的空隙”让其他事务无法在这个区间插入新记录这就是可重复读级别下避免幻读的重要手段。但间隙锁也容易引发性能问题尤其是DELETE或者UPDATE一个大范围数据时可能会锁住大量间隙阻塞其他写入。我遇到过类似线上事故一个数据清理任务锁住了全表业务写入大面积阻塞就是因为间隙锁的覆盖范围比预想大太多。5.3 Linux常用排查命令最后一块高频考点是Linux命令与分析工具。这套题的整体难度不高但会考察你是否能在给定场景下选对工具。经典的场景包括查看CPU负载高的线程toptop -H -pjstackJava场景内存排查free -g、vmstat、pmap磁盘IO排查iostat、iotop网络排查netstat、ss、tcpdump、ping、telnet系统日志dmesg、journalctl每个工具背后都有一串真实的工作思路。比如你发现线上接口变慢第一个动作往往是top查看CPU占用率再用vmstat看是否上下文切换过高再用iostat确认是否磁盘IO瓶颈。如果CPU、内存、IO都正常才考虑是不是网络延迟、依赖服务抖动或者代码本身的锁竞争。很多校招生笔试时能把命令背出来一到线上就不知道怎么把这些命令组合成一套排查流程。所以我一直建议刷题之余在自己的虚拟机上搭一个简单的Web服务制造CPU打满、内存泄漏、慢日志等场景反复用这些命令去定位比刷一百道选择题有效得多。5.4 Lua脚本与原子性操作的角色虽然不是这套笔试题的主干内容但腾讯内部不少中间件和服务框架都用到了Lua做扩展脚本比如OpenResty里写Nginx扩展、Redis的Lua脚本。在笔试题里偶尔会出现类似“Redis的Lua脚本能保证原子性吗”的问题。答案是可以Redis服务端会把Lua脚本作为一个整体执行期间不允许其他命令插入因此等价于原子的。但脚本执行时间过长会阻塞所有操作所以要注意脚本的耗时控制。这个话题在面试追问环节更容易成为亮点。如果你能进一步解释Lua脚本的原子性依赖Redis单线程执行模型在集群模式下EVAL命令要求脚本中涉及的键最好在同一槽位否则会报CROSSSLOT错误。这些细节能体现你不仅在背概念而且接触过真实分布式环境。虽然这套题不会考这么深但多了解一些总是加分项。6. 常见失分点与刷题方法论6.1 几个反复出现的错误类型我梳理了一下历年刷这套题的人最容易失分的几个点虽然题目形式不同但问题本质高度一致。第一类错误是概念混淆比如把数组名在函数参数中“退化”为指针错误套用到sizeof上。错一次可以说是大意但如果你连续做错三道同类型题就说明你对数组和指针的内存模型理解得还不够系统。第二类错误是边界条件遗漏写程序题时只考虑常规情况不写空链表、空字符串、长度为1的数组等边界分支。很多笔试的判分逻辑里边界条件占了非常大的比例因为线上代码最容易出问题的就是边界。第三类错误是时间复杂度的误判比如把哈希表在链地址法下的最差情况O(n)当成平均情况O(1)来回答或者忽略快速排序最坏情况退化为O(n^2)这回事。别人不会因为你答了平均情况而加分但你要是没提最坏情况就会被认为“理解不完整”。第四类错误是不写推导过程。选择题猜对答案和推导出答案在面试官眼中差异巨大。卷面上的涂改痕迹、草稿纸上的推演过程有时候比最终答案更能反映一个候选人的思维习惯。这道题如果不会哪怕蒙对了也建议在注释里写清思考路线至少展示出你的逻辑是清晰的。6.2 一套有效的刷题方法根据我自己的经验刷题不是追求数量而是要形成“做题—总结—再做题”的闭环。具体做法可以分三步。第一步按知识点分类刷题。先把历年面试题按C、数据结构、操作系统、网络、数据库、Linux等主题归好类每类题目集中攻克。这样一次只接触一类问题知识点会成块地沉淀下来而不是零散地散落在各个套题里。第二步每道题都要能讲给别人听。这是一条非常苛刻但极其有效的标准。当你做对一道题后尝试用大白话解释给一个完全不熟悉的人听如果你能让他明白说明你真的理解了这个知识点。如果解释得磕磕绊绊那说明还有盲区赶紧回看书本或者博客。第三步配合实战项目去验证。纸上得来终觉浅。有条件的话自己写一个简单的网络库、一个高并发的echo服务、一个性能计数器模块把笔试里的进程线程、TCP状态、内存管理这些知识点全部落到代码里。这套题不需要你跟风重新学习已经过时的技术栈你只需要在建自己小项目的过程中不断思考“这里的底层机制对应试卷上的哪个考点”知识就活了。6.3 面试时如何展现这套知识体系笔试只是第一关后面还有一轮轮面试。如果你已经把这类基础题吃透了面试时建议主动展示知识体系的“连接关系”而不是只回答问题本身。举个例子面试官问你TCP三次握手你不要只回答三次握手的过程可以顺带提一下TSOTCP分段卸载、握手过程中的SYN队列与Accept队列、SYN Flood防护机制。这些扩展不是炫耀而是展示你“理解网络栈的分层与协作”。再比如面试官问你快速排序的时间复杂度你除了回答O(n log n)平均、O(n^2)最坏还可以主动提到STL中的内省排序、尾递归优化、以及工业级排序库为什么放弃纯快排。这样连续给出几个延伸点面试官会顺着你的方向往下问你就能把话题引到自己熟悉并且真正有深度的领域整个面试节奏就被你掌握了。这套2016年的笔试题其实是一张很好的“知识地图”把地图吃透后面无论面试官怎么变着花样问你都能定位到它在地图上的准确位置。7. 真题复盘中的核心知识点总结这部分把整套“腾讯2016研发工程师笔试题二”涉及的主要知识点汇总成一张速查表。方便大家在考前快速过一遍看看有没有遗漏。考点模块核心考察点易错细节C/Csizeof、strlen、数组指针、虚函数表数组名在函数传参时退化为指针C/C构造函数与析构函数、拷贝控制构造函数不能为虚函数数据结构二叉树遍历、链表反转、快慢指针边界条件空树、单节点、环排序查找快速排序、归并排序、堆排序、二分查找快排最坏O(n^2)、稳定性判断操作系统进程线程、死锁四个条件、虚拟内存线程切换不切换页表计算机网络TCP三次握手四次挥手、UDP、TIME_WAIT主动断开方进入TIME_WAIT数据库事务隔离级别、索引失效场景最左前缀、索引列不使用函数Linuxtop、ps、netstat、tcpdump、strace排查问题先看整体再深入细节这张表虽然不能覆盖整套卷子的每一道题但足以覆盖考试体系的绝大部分考查场景。如果你能把表里每一个点都展开讲出“为什么”这套笔试题绝对难不倒你。我个人的看法是这份2016年的笔试题放到今天依然比很多“新式八股”更有区分度。它没有任何框架绑定不偏不怪却能把一个人的科班功底照得明明白白。准备校招或者社招跳槽时与其到处收集最新面经不如静下心来把这类经典套题吃透举一反三的能力才是真正的“稳定内核”。最后再分享一个小技巧做题时不要只满足于把答案选对一定要亲自把代码敲一遍再把涉及的核心概念试着讲给身边人听。知识只有经过“输入—加工—输出”三个环节才算真正变成你自己的。