一、C语言面试题大汇总:为什么它仍是技术面试的基石?

尽管现代开发中高级语言(如Python、Go、Rust)日益普及,但C语言在系统编程、嵌入式开发、操作系统、驱动开发、高性能计算等领域仍不可替代。根据Stack Overflow 2025开发者调查,C语言在“最常用语言”中稳居前五,且在“最想学习语言”中位列第三——这说明它既是企业刚需,又是开发者能力跃升的关键跳板。

本C语言面试题大汇总专为技术求职者打造,覆盖:

  • • 基础语法(变量、运算符、控制流)
  • • 指针与内存管理(堆栈、野指针、内存泄漏)
  • • 预处理与宏定义(#define、#include、条件编译)
  • • 结构体与联合体(内存对齐、位域)
  • • 文件操作(文本/二进制I/O、错误处理)
  • • 算法与数据结构基础(链表、排序、递归)
  • • 企业真题还原(腾讯、华为、字节、Intel嵌入式岗)

所有内容均依据2020—2026年真实校招/社招真题整理,每题附详细解析、易错点标注、延伸思考,助你从“背题”进阶到“理解原理”。

💡 本汇总特色

  • ✅ 按题型分层:基础→进阶→实战三阶递进
  • ✅ 真题来源标注:明确企业/岗位/年份
  • ✅ 原理图解:配合内存模型、流程图说明
  • ✅ 代码可运行:所有示例经GCC 12.2验证
  • ✅ 扩展阅读:推荐《C陷阱与缺陷》《高质量编程》等经典

二、高频核心考点解析(附真题)

⚡ 1. 指针基础与进阶

高频问题:“请解释指针与数组的区别,并说明以下代码输出”:

// 题目来自华为2024秋招笔试题 void func(int *arr, int n) { arr[0] = 999; int *p = arr; p[1] = 888; } int main() { int a[] = {1, 2, 3, 4}; func(a, 4); printf("%d %d %d %d\n", a[0], a[1], a[2], a[3]); return 0; }

答案与解析:输出为 999 888 3 4。原因:数组名在函数参数中退化为指针,`arr`指向原数组首地址,因此`arr[0]`和`a[0]`是同一内存单元。

延伸陷阱:若函数内执行`arr = NULL;`,是否影响`main`中`a`?→ 否!因为`arr`是形参副本,仅改变副本指针值,不改变原数组地址。

⚙️ 2. 野指针与内存泄漏

腾讯2025嵌入式岗真题:

char *get_str() { char str[] = "Hello"; return str; // 问题:返回局部数组地址 }

错误原因:局部数组`str`存储于栈区,函数返回后其内存被回收,返回指针指向无效区域,构成野指针。正确做法:使用`static`或`malloc`分配堆内存。

⚡ 1. 宏定义与运算符优先级

字节跳动2024后端开发真题:

// #define MUL(x,y) x*y int a = 3; int b = 4; int c = 5; int result = MUL(a + b, c); // 结果是多少?

陷阱:若定义为`x*y`,则展开为`a + b * c = 3 + 4 * 5 = 23`,而非预期`(3+4)*5=35`。

正确写法:

#define MUL(x,y) ((x)*(y))

原因:双括号确保整体作为表达式参与运算,避免优先级错误。

⚙️ 2. 条件编译与头文件保护

常见问题:“为何头文件要用`#ifndef HEADER_H`?”

答案:防止头文件被重复包含导致重定义错误。例如:

// utils.h #ifndef UTILS_H #define UTILS_H void log_msg(const char *msg); #endif // UTILS_H

当多个源文件包含该头文件时,预处理器仅处理第一次`#define UTILS_H`,后续`#ifndef UTILS_H`为假,跳过内容,避免重复定义。

⚡ 1. 内存对齐与sizeof

Intel嵌入式2025笔试题:

typedef struct { char a; // 1 byte int b; // 4 bytes short c; // 2 bytes } MyStruct;

问题:`sizeof(MyStruct)`是多少?

答案:12字节(非7字节)。原因:内存对齐规则——成员偏移需为自身大小的整数倍,结构体总大小为最大成员(`int`为4)的整数倍。

偏移 成员 字节 对齐要求
0 `a` 1 1
1–3 填充 3 —
4–7 `b` 4 4
8–9 `c` 2 2
10–11 填充 2 —

⚙️ 2. 联合体(union)的应用

华为2024嵌入式真题:如何用联合体判断CPU字节序(大端/小端)?

typedef union { int i; char c; } endian_test; int is_little_endian() { endian_test u; u.i = 1; return (u.c == 1); // 小端:最低字节存低地址 }

原理:小端模式下,`int`值`1`(0x00000001)的最低字节(0x01)存储在低地址,`c`读取到`1`;大端则`c`为`0`。

⚡ 1. 文本 vs 二进制文件

常见误区:文本文件和二进制文件在读写时有何区别?

以Windows为例:

  • • 文本模式:读取时将`\r\n`转换为`\n`;写入时将`\n`转换为`\r\n`
  • • 二进制模式:不转换,逐字节读写

若用文本模式读取图片/可执行文件,会导致数据损坏!正确做法:始终用`fopen("file.bin", "rb")`。

⚙️ 2. 错误处理与EOF

以下代码为何可能陷入死循环?

int c; while ((c = fgetc(fp)) != EOF) { printf("%c", c); }

问题:若文件含字节`0xFF`(即-1),`fgetc`返回`EOF`(-1),循环提前结束,但实际未到文件尾!

正确写法:使用`feof(fp)`检查文件结束标志,或判断返回值类型为`int`(而非`char`):

int c; while ((c = fgetc(fp)) != EOF) { printf("%c", c); } // 或更安全: if (ferror(fp)) { perror("Read error"); }

⚡ 1. 递归 vs 迭代

腾讯2025校招题:用递归和迭代分别实现斐波那契数列,并分析性能。

// 递归(低效) long long fib_recursive(int n) { if (n <= 1) return n; return fib_recursive(n-1) + fib_recursive(n-2); } // 迭代(高效) long long fib_iterative(int n) { if (n <= 1) return n; long long a = 0, b = 1, c; for (int i = 2; i <= n; i++) { c = a + b; a = b; b = c; } return b; }

分析:递归时间复杂度O(2ⁿ),存在大量重复计算;迭代为O(n),空间O(1)。面试官常追问:如何优化递归?→ 引入缓存(记忆化)或改尾递归(C不支持自动尾递归优化)。

⚙️ 2. 函数指针与回调

华为嵌入式真题:如何用函数指针实现状态机?

typedef void (*state_func)(void); void state_idle(void) { printf("IDLE\n"); } void state_run(void) { printf("RUN\n"); } state_func states[] = { state_idle, state_run }; int current_state = 0; void run_cycle() { states[current_state](); current_state = (current_state + 1) % 2; }

优势:状态切换只需修改索引,无需`switch`分支,代码更简洁、易扩展。

三、企业真题实战演练(精选5道)

〔1〕华为嵌入式开发岗(2025)

题目:编写`memcpy`函数(不调用库函数),要求处理内存重叠情况。

解析:标准`memcpy`不处理重叠,`memmove`才处理。若源和目标区域重叠,需从后向前拷贝避免覆盖。

void *my_memmove(void *dest, const void *src, size_t n) { unsigned char *d = (unsigned char *)dest; const unsigned char *s = (const unsigned char *)src; if (d < s && d + n > s) { // 重叠:目标在源之后 for (size_t i = n; i > 0; i--) d[i-1] = s[i-1]; } else { for (size_t i = 0; i < n; i++) d[i] = s[i]; } return dest; }

考点:指针算术、内存重叠判断、边界处理、返回值设计。

〔2〕字节跳动后端开发岗(2024)

题目:解释以下代码输出:`printf("%d\n", sizeof("a"));`

答案:2。字符串字面量`"a"`包含字符`'a'`和终止符`'\0'`,共2字节。

陷阱:若写为`sizeof('a')`,结果为4(`int`类型,因字符常量在C中为`int`)。

〔3〕腾讯后台开发岗(2025)

题目:用位运算实现两数交换,不借助第三方变量。

int a = 10, b = 20; a ^= b; b ^= a; a ^= b; // 结果:a=20, b=10

原理:异或运算满足交换律与自反性(`x^x=0`, `x^0=x`)。

注意:若`a`和`b`指向同一内存(如`a ^= a`),结果为0!因此需加判断:`if (&a != &b)`。

〔4〕阿里云嵌入式(2024)

题目:以下代码存在什么问题?如何修复?

char *p = "hello"; p[0] = 'H';

错误:字符串字面量存储于只读区,修改导致未定义行为(可能崩溃)。

修复:`char p[] = "hello"; p[0] = 'H';` 或 `char *p = malloc(6); strcpy(p, "hello");`

〔5〕美团基础平台(2025)

题目:分析以下代码的内存泄漏风险。

void func() { char *buf = malloc(1024); if (strlen(buf) > 10) { return; // 未释放buf! } free(buf); }

解决方案:统一出口释放,或使用`goto cleanup`模式:

void func() { char *buf = malloc(1024); if (!buf) return; if (strlen(buf) > 10) { goto cleanup; } // 正常逻辑... cleanup: free(buf); }

四、C语言面试常见陷阱(附避坑指南)

⚠️ 陷阱1:`==` vs `=`

经典错误:`if (n = 1)` 写成赋值而非比较,导致条件恒真!

防御技巧:将常量写在左侧:`if (1 == n)`,若误写为`if (1 = n)`,编译器报错(常量不可赋值)。

⚠️ 陷阱2:`scanf`的安全风险

以下代码可能缓冲区溢出:

char buf[10]; scanf("%s", buf); // 无长度限制!

正确做法:指定宽度:`scanf("%9s", buf);`,或改用`fgets(buf, sizeof(buf), stdin);`

⚠️ 陷阱3:有符号 vs 无符号比较

unsigned int a = 5; int b = -1; if (a > b) printf("a > b\n"); else printf("a <= b\n");

输出:`a > b`?→ 错误! 因`b`被提升为`unsigned`,`-1`变为`UINT_MAX`(如4294967295),故`a < b`。

⚠️ 陷阱4:`static`在函数内与全局的差异

位置 作用域 生命周期 初始化
函数内 仅函数内可见 整个程序运行期 默认0初始化
全局(文件内) 当前文件可见 整个程序运行期 默认0初始化