拓冰建站拓冰建站
首页 / 资讯中心 / 正文

360 C研发工程师笔试复盘:指针内存与底层原理全解析

朋友的内推名额来得突然。2016年那会儿360的C研发工程师内推笔试已经在牛客网和论坛上被大家反复讨论题目风格偏底层、偏原理、偏“抠细节”跟平时刷的LeetCode完全是两路东西。我帮他把网上能扒到的回忆版题目和考点整理了一遍又对着自己的笔记做了几轮复盘越看越觉得这套题的含金量被很多人低估了。这篇就当一份“事后复盘笔记”写出来给准备投C/C方向、尤其是想走安全或中间件方向的同学做个参考。这套题适合谁看两类人一类是马上要笔试、想快速抓重点的应届生另一类是工作几年、想回头补基础的老兵因为里面的知识点到现在依然是面试高频区。先给结论它不考“算法难”考的是“你真的懂C吗”。1. 笔试整体结构与考点分布1.1 题型结构完全拆解从回忆版来看整套题大致分成四块选择题、填空题、简答题、编程题。占比上选择题最多简答题和编程题分值最重时间给得不算宽裕我朋友当时做完还剩十分钟不到属于“能写完但没时间检查”的状态。选择题的特点非常明显题干短选项迷惑性极强尤其是跟指针、运算符优先级、类型转换相关的题几乎每个选项都是精心设计过的陷阱。填空题多集中在内存布局、字节对齐、字符串处理、位操作上要求写出具体输出或补全代码片段。简答题跑不出进程线程、网络状态、Linux命令、static/const/volatile这几个大方向。编程题两到三道涉及链表、字符串、数组的经典操作不需要高级算法但要求一次写对因为在线IDE没有编译提示稍不留神就漏了边界条件。时间分配上如果让我给建议选择题控制在25分钟以内填空题15分钟简答题15分钟编程题留足40分钟。编程题宁可先写框架再补细节也不要在一道题上抠太久。我朋友第一次模拟时在一道链表反转上钻了牛角尖结果后面简答题差点没写完这个教训很典型。1.2 考点热度排布哪些是重灾区我在整理的时候按出现频率给考点排了个序这个排序到今天看依然适用排名考点方向出现频率典型题型1指针与数组极高sizeof、指针运算、二维数组退化2内存管理极高内存四区、malloc/free、内存泄漏3字符串处理高strlen实现、strcpy溢出、逆序输出4结构体与字节对齐高offsetof计算、sizeof(struct)5进程线程与同步中高区别、互斥锁、生产者消费者6Linux常用命令中grep、find、ps、gdb7网络基础中TCP状态、三次握手四次挥手8C语言基础语法中const、static、volatile、typedef这个分布说明出题人的思路很清楚不追求偏怪难而是看候选人有没有深厚的底层基本功。尤其指针和内存几乎每次笔试都会占30%以上重要性远超算法本身。所以复习的时候别一上来就刷题先把这两块吃透。2. 指针、内存与底层原理拉开差距的地方2.1 指针和数组的经典陷阱题这套题里最经典的一道我几乎在每一版回忆里都能看到就是sizeof相关。比如定义一个数组char arr[] hello问sizeof(arr)和strlen(arr)分别是多少。答案是6和5这个很多人知道。但它紧接着会考sizeof(arr 0)、sizeof(arr[0])、sizeof(arr)这三个结果在64位平台下都是8因为都变成了指针。这里有个特别容易搞混的细节arr单独出现在表达式中除了作为sizeof或的操作数时都会“退化”成指向首元素的指针。sizeof(arr)保留整数组的6字节arr得到的是指向整个数组的指针类型是char(*)[6]值虽然跟arr[0]一样但步长不同(arr 1)会直接跨过6个字节。另一类经典题是二维数组的指针运算。比如int a[3][4]问*(a 1)和a[1]是否相等*(*(a 1) 2)等于什么。这类题的底层逻辑是a的类型是int(*)[4]是一个指向“长度为4的数组”的指针所以a 1步进的是4个int也就是一行。*(a 1)拿到的是第二行这个一维数组的名字退化后是int*类型指针。*(*(a 1) 2)就是a[1][2]。很多人在这一步混了指针的“类型”和“值”导致写代码时访问越界。我的建议是画图理解把内存格子画出来比死记公式有效得多。2.2 内存四区与字节对齐计算内存四区栈、堆、全局/静态区、代码区几乎是必考。常见问法全局变量、static变量、局部变量、malloc分配的内存分别存在哪里程序结束时哪些内存会自动释放哪些不会。这里有个坑很多人认为全局变量和static变量都在同一个区但从ISO C标准角度看它们确实都属于静态存储期不过实际运行时未初始化的全局变量通常在BSS段已初始化的在数据段这个细节在笔试的填空题里经常被挖出来。字节对齐是填空题里的常客。题目一般是给一个结构体struct { char a; int b; char c; } s;问sizeof(s)。按常见平台的默认4字节对齐答案是12不是6。因为char a占1字节后填充3字节int b占4字节char c占1字节后填充3字节。如果换一下声明顺序struct { int b; char a; char c; } s;sizeof(s)就变成8。这个例子特别适合讲给新手听字段的排列顺序直接影响结构体大小能帮你在嵌入式场景里省掉很多内存。笔试如果问offsetof记住offsetof(struct, member)就是取成员相对结构体首地址的偏移offsetof(s, b)在第一个结构体里是4在第二个里是0。2.3 栈溢出与堆泄漏的排查思路简答题里可能会问“你用C写过内存泄漏的bug吗怎么定位的”。这种题没有固定答案但有几个点要答到一valgrind是Linux下最常用的内存检测工具用valgrind --leak-checkfull ./a.out能输出泄漏位置二ASanAddressSanitizer在编译时加-fsanitizeaddress运行时报错更直观适合集成测试阶段三如果线上没有工具可以通过观察进程内存变化趋势、打开/proc/pid/status里的VmRSS字段判断是否持续增长。我实际遇到过一种坑程序里用了第三方库库内部静态分配了一块大缓存导致valgrind报“still reachable”的泄漏这其实不是真泄漏需要结合上下文判断。笔试答题时能说出这种“假泄漏”的区分会很加分。3. 字符串处理与常用算法手写3.1 字符串函数笔试最容易出事的三道题字符串处理在这套题里出现频率极高最容易被考的是三道手写strlen、手写strcpy、字符串逆序。手写strlen的常规版本size_t my_strlen(const char *s) { const char *p s; while (*p) p; return p - s; }这个版本能拿基础分但进阶版是用“指针对齐”的技巧一次判断多个字节面试场合能写出来会让人眼前一亮。不过笔试建议写简单版因为容易验证、不易出错。手写strcpy就要注意返回值类型了标准strcpy返回的是目的地址很多人会写成返回voidchar *my_strcpy(char *dest, const char *src) { char *ret dest; while (*dest *src) ; return ret; }这段代码里*dest *src这个表达式本身就是循环条件先赋值再判断赋值结果是否为\0。这个写法既简洁又考验对运算符优先级和副作用的理解笔试时加分效果明显但前提是真能解释清楚不然被追问就露馅了。字符串逆序是最容易漏细节的题。如果要求“原地逆序”用双指针从两头交换void reverse(char *s) { if (!s) return; char *l s; char *r s strlen(s) - 1; while (l r) { char t *l; *l *r; *r-- t; } }这里面有个坑如果字符串本身是const char*指向的常量字符串试图修改会触发段错误。笔试考的是“写函数”但很多场景会额外给你一个char *s hello这属于未定义行为要能识别出来。3.2 链表反转与排序算法的现场手写要点链表反转是编程题里出现概率最高的一道。迭代写法struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev NULL, *cur head; while (cur) { struct ListNode *next cur-next; cur-next prev; prev cur; cur next; } return prev; }关键点在于必须先保存cur-next否则一旦断了链后面的节点就找不到了。这个坑很多人在白板上写的时候都会犯。另外笔试可能加条件“每K个节点反转一次”或者“判断链表是否有环”后者用到快慢指针快指针每次走两步慢指针每次走一步相遇即有环。排序算法里出现最多的不是快排而是冒泡和选择。这也合理因为笔试环境下的在线IDE不支持你依赖库函数冒泡好写不容易错void bubble_sort(int *a, int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - i - 1; j) { if (a[j] a[j 1]) { int t a[j]; a[j] a[j 1]; a[j 1] t; swapped 1; } } if (!swapped) break; } }加上swapped标志做提前退出这个优化几乎不会扣分。快排则要求能写出partition和递归的完整结构如果记不清宁可写堆排序或归并也不要写一个到处是边界问题的半吊子快排。4. 系统、网络与嵌入式杂项4.1 进程线程与同步机制的常见问法简答题大概率会出一道进程和线程的区别往细了问会说“进程的地址空间包含哪些部分”“线程为什么共享地址空间但栈独立”。答案要点进程是资源分配的基本单位线程是调度的基本单位进程有独立的地址空间、文件描述符表、信号处理器线程之间共享这些但每个线程有自己的栈、寄存器上下文和线程局部存储TLS。笔试里能画出内存布局说明线程共享堆但独享栈就已经超过很多人了。同步机制方面问得最多的是互斥锁和信号量的区别。那套题里有一道经典题两个线程各自对同一个全局变量做count100万次问最终count是否一定是200万为什么。答案是不一定因为count不是原子操作在底层分为“读-改-写”三步两个线程可能读到同一个旧值。解决方式是用pthread_mutex_t保护或者用__sync_fetch_and_add这类原子操作。更进一步面试官可能追问自旋锁和互斥锁怎么选临界区短、多核环境可以考虑自旋锁避免线程切换开销临界区长或单核环境用互斥锁让出CPU更合理。4.2 Linux命令与网络状态背也要背准确Linux命令属于纯记忆题但考得很具体。经常出现的有查找文件用find / -name *.log 2/dev/null在日志里筛关键词用grep ERROR app.log查看进程和端口用ps -ef和netstat -tlnp查看内存用free -h查看磁盘用df -h。还有gdb启动后设断点跑程序break main、run、print var、next、continue、quit。这些命令没有技术含量但写得全不全体现的是日常熟练度。网络状态机是另一道高频题。三次握手记住四个字“SYN、SYNACK、ACK”四次挥手记住“FIN、ACK、FIN、ACK”。问TCP各状态时特别容易考TIME_WAIT为什么主动关闭方要停留在TIME_WAIT状态2MSL第一保证最后的ACK能到达对方第二让本连接的所有延迟报文段在网络中消失避免干扰后续连接。选择题里会出现“CLOSE_WAIT发生在哪一方”这种细节被动关闭方在收到FIN并回复ACK后进入CLOSE_WAIT如果程序一直不调用close就会出现大量CLOSE_WAIT连接这在实际服务排查里非常常见。4.3 位操作与字节序嵌入式方向的隐藏考点这套题里偶尔会夹一道位操作题比如“用宏定义实现求两个数中较大值”或者“写一个宏计算一个字节中位1的个数”。这类题考察的是逻辑与对编译器的理解。位1个数的经典写法int bit_count(unsigned char c) { int count 0; while (c) { c (c - 1); count; } return count; }c (c - 1)每次消掉最低位的1循环次数等于1的个数这个技巧很实用。字节序的题也偶有出现。大端和小端的判断可以用一个unionunion { int i; char c; } u; u.i 1; printf(%d\n, u.c);如果输出1说明低字节在低地址是小端输出0是大端。这个题如果笔试让你“写程序判断本机字节序”用这个union是最标准答案。不过有些平台不允许把int和char放在同一个union的共享内存其实C标准对union成员读取的规则有具体说明严格来说读非活跃成员属于实现定义行为但实际工程里这个用法非常普遍。5. 实际答题中我踩过的坑5.1 选择题的“感觉对”陷阱我在陪练过程中最深的体会是这套选择题的迷惑性不在选项本身而在“每一题都像是在考你见过的东西”。比如有一道题问const char *p、char const *p、char *const p的区别很多人一看三个都会背前两个是“指向常量的指针”第三个是“指针常量”。但题目变一下问const char * const p就有人蒙了。更好的记忆方式是“只看const修饰谁”——const位于*左侧修饰的是指针指向的变量位于*右侧修饰的是指针本身。这样不管怎么组合都不会错。多选题是重灾区。这套题当年是“不定项选择”我在练习时发现很多人宁愿漏选也不选自己不确定的理由是“选错了倒扣分”。但如果是回忆版或者模拟环境没有倒扣分规则建议把有把握的选项都写上。实际考试时一定要先看题头说明搞清计分规则再做策略调整。5.2 编程题的环境与边界条件在线笔试环境跟本地编译器差别很大几个坑值得特别记没有自动补全函数签名要记牢strlen原型、malloc返回值、fgets参数顺序这些基础库函数不能翻手册。编译错误看不太清楚有时只提示一个符号不显示具体行号所以写代码时命名清晰、结构简单很重要。输入样例很友好但隐藏用例很严格。比如链表题会给head NULL的用例字符串题会给空串排序题会给已经有序的数组。我在复盘时养成了习惯每道题写完先顺手检查空指针、空字符串、单元素数组这三个边界。不需要自己写main函数的情况很多但要求你“补全函数”时会给出函数的声明这时候参数类型必须严格匹配比如(const char *)和(char *)不同容易编译报错。5.3 时间分配与检查清单我朋友第一次模拟考没做完第二次我强制他按固定节奏走效果好很多。我的检查清单大致是这样的选择题先做一遍遇到卡壳超过2分钟的标记跳过填空题严格控制每题5分钟因为字节对齐和指针运算计算量不大超过5分钟说明思路有问题简答题答要点不用写长篇大论把关键词写全就行编程题留40分钟头10分钟读题和定思路25分钟写代码最后5分钟检查边界。检查的重点按优先级排序一是程序有没有编译错误二是函数是否处理了空指针和野指针三是动态内存是否都释放了四是数组下标有没有越界可能五是字符串有没有结尾符。这套清单到现在我在周会上评审代码时还会用其实笔试和真实工程要求的是同一种严谨。6. 准备路径与内推经验分享6.1 复习顺序与资料清单如果你现在离笔试还有两到三周我建议的复习顺序是第一周把《C程序设计语言KR》后面关于指针、数组、结构体的章节仔细看一遍重点做书中习题第二周刷公开的历年笔试题和牛客网上C/C专项练习每天50道选择题错题整理成文档第三周专门练习手写代码链表、字符串、排序每类至少写三遍闭卷默写。资料方面我个人觉得比刷题量更重要的是把本机Linux环境配好。装好gcc、gdb、valgrind把平时练习的代码用gcc -Wall -g编译强制自己解决每一个警告。很多人在笔试里写代码有隐藏bug就是因为平时编译环境太宽松警告全被忽略。真正笔试时编译器可能开-Wall -Werror直接编译失败。6.2 内推笔试前的小建议内推渠道和校招最大的不同是简历会先到用人部门手里笔试虽然也有但通过后立刻有面试官看卷子答题风格会更受关注。所以写了什么代码、注释够不够清晰、解题思路是不是一眼能看懂都直接影响面试官的第一印象。我见过一个同学笔试编程题没完全通过隐藏用例但代码里注释写了“当链表为空时直接返回NULL”面试官反而因为这个细节约了他面试。另外笔试期间如果允许使用本地IDE一定要提前测试好环境把常用的代码片段比如快排、链表反转、字符串匹配模板放在单独的文件里方便临时拷贝参考。不确定允许使用外部文件时最好把常用模板记在脑子里不要冒违规风险。还有一个小经验如果题目要求输出结果多想想“有没有可能要求输出多组数据”然后考虑格式——每个输出后面是否需要换行、行末是否有空格都可能是隐藏用例的考察点。我朋友当年就吃过“多打了一个空格”的亏那种憋屈感真的很难受。我个人在实际操作中的体会是这套2016年的题放在今天并不算难但它非常准确地把“C语言工程师”这个岗位的核心能力圈了出来——你不需要会花哨的模板元编程不需要背一堆STL容器的时间复杂度但你必须对内存、指针、编译链接、操作系统交互这些底层机制做到心中有数。很多做Java或Python入门的同学转手写C的时候容易着急总想靠项目经验撑场面但实际上底层基础不牢固的话项目代码一压并发、一上多线程就到处崩笔试暴露得干干净净。从这个角度看这套题出的其实很良心它没有刁难人只是诚实地告诉你吃这碗饭先把底子打扎实。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门