C 语言面试 100 题精选(2024 版):核心考点、标准答案与实战解析
C 语言面试 100 题精选(2024 版)
本文精选高频、高价值的 100 道 C 语言面试题,涵盖基础语法、指针内存、数据结构、系统编程、未定义行为、现代 C 标准等核心考点。每题附标准答案、深度解析、易错点提示与实战变体,助你高效备战大厂面试。
基础语法与预处理(1-15)
- ++i 与 i++ 的区别? 前置返回增后值,后置返回增前值;前置无临时变量开销,优先用前置。
- C 语言参数传递机制? 仅支持值传递。指针传递实为“地址值传递”,可间接修改实参。
- 如何用宏定义安全的 MAX?
#define MAX(a,b) ((a)>(b)?(a):(b))—— 参数加括号、整体加括号、避免副作用(仍存副作用风险,C23 建议用泛型函数)。 - #include <file.h> 与 #include "file.h" 区别? 前者搜索系统路径,后者先搜当前目录再搜系统路径。
- 条件编译常用指令?
#ifdef/#ifndef/#if/#elif/#else/#endif,配合defined()实现跨平台编译。 - const 与 #define 常量区别? const 有类型、作用域、可调试、可取地址;宏纯文本替换,无类型安全。
- inline 关键字作用与限制? 建议编译器内联展开,减少调用开销;不可包含循环/递归/静态变量;定义需在头文件可见。
- static 的三大作用域含义? 函数内:静态存储期、保持值;文件级:内部链接、隐藏符号;类/结构体成员(C++):共享单份存储。
- extern 作用? 声明外部链接符号,跨文件共享变量/函数;不分配存储。
- volatile 关键字语义? 告诉编译器“值可能被外部不可预知地修改”,禁止优化缓存寄存器,每次读写必访存。用于寄存器映射、信号处理、多线程共享标志。
- restrict 关键字(C99)? 告知编译器指针为访问对象的唯一路径,允许激进优化(向量化、重排)。违反约定导致 UB。
- 结构体对齐与填充字节? 成员按声明序排列,每个成员偏移为其对齐数倍数;结构体总大小为最大对齐数倍数。
#pragma pack可压缩但可能降低性能/触发总线错误。 - 柔性数组成员(C99)? 结构体末尾不定长数组
type name[];,配合malloc(sizeof(Struct)+n*sizeof(type))实现变长对象,单次分配、局部性好。 - 位域语法与端序陷阱?
unsigned int a:3; b:5;分配位序实现定义(通常低位优先),跨平台网络协议禁用位域,改用位运算显式打包。 - C 语言如何实现泛型编程?
void*+ 函数指针(qsort/bsearch)、宏模板、_Generic(C11 类型泛型选择)、C23 真泛型(草案)。
指针与内存管理(16-35)
- 指针与数组的区别? 指针是存地址的变量(可修改指向、sizeof 为指针大小);数组是连续存储区域(地址固定、sizeof 为全数组字节数、可退化为指针)。
- 野指针、悬垂指针、空指针区别? 未初始化=野;free 后未置 NULL=悬垂;显式赋 NULL=空。防御:声明即初始化、free 后置 NULL、检查 NULL 再解引用。
- malloc/free 实现原理? 堆管理器维护空闲链表/树,首次适配/最佳适配/伙伴系统分配;free 合并相邻空闲块防碎片。ptmalloc/jemalloc/tcmalloc 为多线程优化引入线程缓存、arena。
- 栈与堆核心差异? 栈:编译期自动管理、向下增长、大小受限(~8MB)、速度快、存局部变量/调用帧。堆:运行时手动管理、向上增长、虚拟地址空间大、存动态对象。
- 内存泄漏分类与检测? 直接泄漏(无指针指向)、间接泄漏(指针链断裂)、可能泄漏(内部指针指向块中间)。工具:Valgrind、ASan、mtrace、Heap Profiler。
- 缓冲区溢出攻击原理? 栈溢出覆盖返回地址/帧指针劫持控制流;堆溢出破坏堆元数据实现任意写。防护:栈保护、ASLR、DEP/NX、Fortify Source、边界检查函数(strncpy_s 等)。
- 如何安全复制字符串?
strncpy不保证终止符;strlcpy(BSD)/strcpy_s(C11 Annex K) 保证终止符;手工snprintf(dst, n, "%s", src)最通用。 - 深拷贝与浅拷贝? 浅拷贝复制指针值,共享底层数据;深拷贝递归分配新内存复制内容。含指针成员的结构体必须深拷贝。
- 函数指针声明与回调模式?
int (*cmp)(const void*, const void*);qsort、事件循环、状态机、插件架构核心机制。 - void* 通用指针用法与风险? 可隐式转换任意对象指针;解引用前必须强转回原类型;无类型安全,易误用。C23 引入
typeof改善。 - 指针算术规则?
ptr + n移动n * sizeof(*ptr)字节;同一数组内指针可相减得ptrdiff_t;跨数组比较/相减为 UB。 - const 指针四种组合?
const int *p(指向常量)、int *const p(常量指针)、const int *const p(常量指针指向常量)、int const *p同第一种。右左规则(时钟规则)读声明。 - C 语言如何实现多态? 结构体首字段放函数指针表(vtable 模拟),或用
_Generic编译期分发,或标签联合 + switch 分发。 - 内存对齐为什么重要? CPU 访问对齐地址单指令完成,非对齐需多指令甚至触发异常(SIGBUS);向量指令(SSE/AVX/NEON)要求严格对齐;缓存行对齐减少伪共享。
数据结构与算法(36-55)
- 链表反转(迭代/递归)? 三指针迭代 O(1) 空间;递归 O(n) 栈空间。
- 检测链表环(Floyd 判圈)? 快慢指针,相遇证明有环;入口找法:相遇后一指针归头,同速前进再次相遇即入口。
- 栈与队列的 C 实现? 数组/链表实现;环形队列避免数据搬移;无锁环形缓冲区用原子操作实现生产者-消费者。
- 哈希表冲突解决? 链地址法(Java HashMap)、开放寻址(线性/二次探测、双重哈希)、Robin Hood、Cuckoo 哈希。
- 红黑树五条性质? 红/黑、根黑、叶黑、红节点子必黑、根到叶黑高相等。保证 O(log n) 最坈情况。
- 堆排序 vs 快排 vs 归并? 堆排序原地 O(n log n) 最坈;快排平均 O(n log n) 缓存友好但最坈 O(n²);归并稳定 O(n log n) 需额外空间。
- 二分查找模板(左闭右闭/左闭右开)? 左闭右开
[l, r)统一语义:while (l < r) { m = l + (r-l)/2; if (check(m)) r = m; else l = m+1; } - 位运算技巧:判断 2 的幂、交换变量、取模优化?
x & (x-1) == 0、a ^= b ^= a ^= b(仅整数)、x & (n-1)替代x % n(n 为 2 的幂)。 - 大数乘法/加法字符串实现? 逆序逐位模拟竖式,进位处理,结果反转。
- 回溯/递归/动态规划经典题:全排列、子集、最长公共子序列、背包问题? 状态定义、转移方程、边界、空间压缩(滚动数组)。
- Trie 树(前缀树)应用? 词频统计、自动补全、IP 路由最长前缀匹配、敏感词过滤。
- 并查集(Disjoint Set Union)路径压缩+按秩合并? 几乎 O(1) 动态连通性,Kruskal MST、网络连通分量核心。
- LRU 缓存 O(1) 实现? 双向链表维护访问序 + 哈希表映射键到节点,get/put 均 O(1)。
- 一致性哈希解决分布式缓存扩容倾斜? 环形哈希空间 + 虚拟节点,仅迁移少量键。
- 布隆过滤器原理与误判率控制? 多哈希函数位图,空间极省,允许假阳性无假阴性,适合爬虫去重、缓存穿透防护。
系统编程与并发(56-75)
- 进程与线程区别? 进程独立地址空间、资源单位;线程共享堆/全局/代码段、栈独立、调度单位。线程创建/切换开销小。
- Linux 进程创建:fork/vfork/clone? fork 复制页表(COW)、vfork 共享地址空间(仅用于 exec 前)、clone 细粒度控制共享资源(线程库基础)。
- 僵尸进程与孤儿进程? 僵尸:子进程退出父未 wait,保留 PCB;孤儿:父先死,子被 init(1) 收养。
waitpid(-1, &status, WNOHANG)非阻塞回收。 - 信号处理机制? 异步通知,内核投递,进程捕获执行处理函数。
sigaction替代signal;仅 async-signal-safe 函数可在处理函数中调用。 - 管道、FIFO、Unix Domain Socket、共享内存、消息队列对比? 管道:亲缘单向;FIFO:非亲缘单向;UDS:双向、支持文件描述符传递;共享内存:最快、需同步;消息队列:带类型、持久化。
- select/poll/epoll/kqueue/IOCP 对比? select:FD_SETSIZE 1024 限制、O(n) 扫描;poll:无上限但 O(n);epoll:红黑树+就绪链表 O(1) 事件通知、水平/边缘触发;kqueue:BSD 统一事件框架;IOCP:Windows 完成端口、主动式。
- 互斥锁、读写锁、自旋锁、条件变量、屏障、信号量区别? 互斥:独占;读写锁:读共享写独占;自旋:忙等适合极短临界区;条件变量:等待条件满足;屏障:多线程同步点;信号量:计数同步、进程间可用。
- 死锁四必要条件与预防/避免/检测? 互斥、持有并等待、不可剥夺、循环等待。预防:破坏任一条件(按序加锁、try_lock);避免:银行家算法;检测:等待图找环。
- 线程池设计要点? 任务队列、工作线程集、关闭协议、动态伸缩、拒绝策略、异常隔离、
thread_local存储。 - 无锁编程:CAS、原子操作、内存序? CAS 比较交换原语;C11
_Atomic、atomic_*、memory_order(relaxed/acquire/release/acq_rel/seq_cst);ABA 问题用版本号/双字 CAS 解决。 - RCU(Read-Copy-Update)原理? 读端无锁、写端拷贝修改+回调延迟释放,适合读多写少、内核链表/哈希表。
- 内存屏障与 happens-before? 编译器/CPU 重排序限制;
atomic_thread_fence、volatile 不提供屏障;C11 内存模型定义同步序。 - 协程/用户态线程实现原理? 栈切换(setjmp/longjmp、汇编 swapcontext、汇编手工保存寄存器)、调度器管理、栈内存池、IO 多路复用集成。libco、libgo、uring 框架。
- 零拷贝技术:mmap、sendfile、splice、copy_file_range? 减少用户态/内核态拷贝,提升网络/文件传输吞吐,nginx/Kafka 核心依赖。
- 定时器精度:timerfd、timer_create、POSIX 定时器? 高精度定时器基于高精度事件定时器(HPET)、TSC;
timerfd可融入 epoll 事件循环。
未定义行为(UB)与安全编码(76-90)
- 常见 UB 类别? 有符号溢出、解引用空/野指针、数组越界、移位超宽、除零、未初始化读、违反严格别名规则、修改字符串字面量、重复无副作用表达式、无序列点修改同一对象多次。
- 严格别名规则? 编译器假设不同类型指针不指向同一内存(char* 除外)。
int *p = (int*)&f;违反规则导致优化错误。用memcpy、union、__attribute__((may_alias))规避。 - 如何检测 UB?
-fsanitize=undefined(UBSan)、-fsanitize=address(ASan)、-fanalyzer(GCC 静态分析)、Clang Static Analyzer、Cppcheck、Coverity。 - 安全编码规范:CERT C、MISRA C、CWE Top 25? 规避 UB、边界检查、整数溢出检查、输入验证、最小权限、防御性编程。
- 整数溢出检测技巧? 加法前检查
a > MAX - b;乘法a > MAX / b;编译器-ftrapv、-fsanitize=signed-integer-overflow;C23ckd_add等检查函数。 - 格式化字符串漏洞?
printf(user_input)允许%n写内存。永远用printf("%s", user_input)。 - 栈保护机制:Canary、RELRO、PIE、FORTIFY_SOURCE? 编译选项
-fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wl,-z,relro,-z,now -fPIE -pie全开。 - C 语言如何实现面向对象? 结构体封装数据、函数指针模拟方法、不透明指针隐藏实现(PIMPL)、单继承用结构体嵌入、多继承用偏移计算、虚表模拟多态。
- 模块化设计:头文件只声明、实现隐藏在 .c、不透明指针 API? 头文件仅含前向声明、API 原型;.c 定义完整结构体,编译成静态/动态库。
- 构建系统:CMake 现代用法(Target-based、Presets、FetchContent)? 目标导向
target_link_libraries传递编译选项/包含目录;Presets 统一 CI/本地配置;FetchContent 替代 git submodule。
现代 C 标准新特性(91-100)
- C11:_Generic、_Static_assert、_Alignas/_Alignof、_Atomic、匿名结构体/联合、线程库(threads.h)、边界检查接口(Annex K)
- C18:仅修正缺陷,无新特性
- C23(即将发布):真泛型、typeof/typeof_unqual、lambda 表达式、属性语法 [[...]]、二进制字面量 0b、数字分隔符 '、标准库增强(字符串、数学、位操作)、
nullptr关键字、auto类型推导、constexpr、模块系统(实验性) - 编译器扩展:GCC/Clang 语句表达式 ({})、零长度数组、标号值 &&label、构造器/析构器属性、向量类型、内联汇编
- 静态分析集成:Clang-Tidy 检查清单、Cppcheck 规则、MISRA 检查器、CodeSonar、Coverity
- 模糊测试:libFuzzer、AFL++、Honggfuzz,结合 Sanitizers 发现深层 Bug
- 形式化验证:Frama-C、CBMC、VeriFast,关键模块证明无 UB
- 包管理:Conan v2、vcpkg、Build2、系统包管理器(apt/dnf/brew/pacman)集成
- CI/CD 流水线:编译警告即错误、Sanitizers 门禁、静态分析门禁、覆盖率门禁、SBOM 生成
- 供应链安全:再现性构建、签名验证、依赖扫描(OSV/Grype/Trivy)、最小化容器镜像(distroless/scratch)
面试加分项:开放性问题
- 设计一个线程安全的内存池分配器
- 实现一个无锁环形缓冲区(SPSC/MPMC)
- 分析某开源项目(Redis/NGINX/Linux kernel)的内存管理策略
- 解释编译器优化如何利用 UB 提升性能(如循环不变量外提、严格别名假设)
- 对比 C 与 Rust/Zig/C++ 在内存安全、并发、互操作上的权衡
备战建议
- 通读 C11/C18 标准草案(N1570/N2176) 核心条款
- 精读 《C 陷阱与缺陷》《C 专家编程》《Effective C》
- 在 LeetCode/Codeforces 刷 C 语言题,强制不用 C++ STL
- 阅读 musl libc / glibc 核心源码(malloc、stdio、pthread)
- 配置
clangd + clang-tidy + compile_commands.json日常开发环境,养成零警告习惯 - 模拟面试:白板写代码、口头解释内存布局、现场 Debug 核心转储
C语言