当前位置: 首页 > news >正文

【C 语言面试】高频考点深度解析

1. 说说 static 关键字的几个用处(基础必问)

static 是 C 语言中非常灵活的关键字,核心作用是 “改变生命周期” 或 “限制作用域”,具体有 3 个场景:

  • 修饰局部变量:普通局部变量出作用域就销毁,static 修饰后,生命周期延长至整个程序结束(相当于 “全局生命周期,局部作用域”)。比如函数内的 static int a=0,每次调用函数 a 不会重置,会保留上次的值,但出了函数仍无法访问。
  • 修饰全局变量:普通全局变量是 “外部链接”,能被其他.c 文件通过 extern 访问;static 修饰后变成 “内部链接”,只能在当前.c 文件可见,避免全局变量命名冲突。
  • 修饰函数:和全局变量类似,static 修饰的函数只能在当前.c 文件中调用,其他文件无法通过声明访问,常用于封装内部工具函数,避免接口暴露。

面试技巧:回答时要区分 “生命周期” 和 “作用域”,这是面试官判断你是否理解透彻的关键。

2. 谈谈你对指针的理解(重中之重)

指针是 C 语言的灵魂,也是面试高频考点,回答要从 “本质 + 分类” 展开:

  • 宏观本质:指针的本质是 “内存地址”—— 内存中每个字节都有唯一编号(地址),指针变量就是用来存储这个地址的变量。简单说:“指针 = 地址,指针变量 = 存地址的变量”。
  • 常见分类
    • 一级指针:直接指向普通变量(如 int* p=&a),最常用;
    • 二级指针:指向指针的指针(如 int** pp=&p),用于函数传参修改指针指向;
    • 数组指针:指向数组的指针(如 int (*p)[5]),注意和指针数组区分;
    • 函数指针:指向函数的指针(如 int (*p)(int,int)),可实现回调函数;
    • 野指针:未初始化或指向非法内存的指针(如 int* p; 直接解引用),是常见 Bug 来源。

面试技巧:可以举一个简单例子(如用指针交换两个变量),证明你不仅懂概念,还会实际使用。

3. 源码、反码、补码的关系(基础理论)

计算机中所有数据都以补码存储,三者关系核心看 “正数 / 负数”:

  • 正数:原码、反码、补码完全相同。比如 + 5(二进制 00000101),三者都是 00000101。
  • 负数
    • 反码 = 原码的 “符号位不变,其余位按位取反”;
    • 补码 = 反码 + 1。例:-5 的原码是 10000101,反码是 11111010,补码是 11111011。

面试延伸:为什么要用补码?—— 统一正负数的加减运算,避免单独处理减法(比如 a-b 等价于 a+(-b) 的补码)。

4. 什么是大小端?如何判断当前机器是大端还是小端?(腾讯一面真题)

大小端是数据在内存中的存储顺序,面试不仅要懂定义,还要会写判断代码:

(1)字面定义

  • 大端存储:数据的 “高位” 存在内存的 “低地址”,低位存在内存的高地址(类似 “按顺序存储”)。比如 0x12345678,大端存储为:低地址→12 34 56 78→高地址。
  • 小端存储:数据的 “低位” 存在内存的 “低地址”,高位存在内存的高地址(更符合计算机读取习惯)。比如 0x12345678,小端存储为:低地址→78 56 34 12→高地址。

5. 结构体内存对齐规则,以及为什么要有内存对齐?(高频考点)

结构体大小计算是面试 “送分题”,但容易踩坑,必须掌握规则和原理:

(1)内存对齐规则(5 点必记)

  1. 第一个成员的偏移量为 0(直接存在结构体起始地址);
  2. 其他成员要对齐到 “对齐数” 的整数倍地址;
  3. 对齐数 = min (编译器默认对齐数,成员变量大小)(VS 默认 8,GCC 默认 4);
  4. 结构体总大小 = 所有成员的 “最大对齐数” 的整数倍;
  5. 可通过#pragma pack(n)修改默认对齐数(n 为 2、4、8 等,取消用#pragma pack())。

示例计算

struct Test {char a;   // 大小1,对齐数1,偏移0int b;    // 大小4,对齐数4(min(8,4)),偏移4(0+1后补3字节)char c;   // 大小1,对齐数1,偏移8(4+4)
};
// 最大对齐数是4,总大小=12(8+1后补3字节,凑4的倍数)

(2)为什么需要内存对齐?

  • 平台兼容性:不是所有硬件都能访问任意地址的数据(比如某些 CPU 只能读取 4 的倍数地址),不对齐会直接报错;
  • 性能优化:对齐后 CPU 只需 1 次内存访问就能读取数据,未对齐可能需要 2 次(比如读取 int 时跨了两个 4 字节块)。

6. 谈一谈位段、联合体、枚举的区别

三者都是 C 语言的 “自定义类型”,但用途和特性完全不同:

(1)位段(struct + 位宽)

  • 定义:和结构体类似,但成员后加 “: 数字”,表示该成员占用的 “二进制位数”;
  • 要求:成员必须是 int、unsigned int、signed int(char 也可,本质是 int);
  • 用途:节省内存(比如存储状态标志,只需 1 位即可)。

struct Flag {unsigned int is_ok : 1;  // 1位,0或1unsigned int type : 3;   // 3位,0-7
};

(2)联合体(union)

  • 定义:成员共享同一块内存空间,大小 = 最大成员的大小;
  • 特性:修改一个成员会覆盖其他成员的值;
  • 用途:判断大小端、节省内存(比如同一空间存储不同类型数据)。

(3)枚举(enum)

  • 定义:用于表示 “有限个可选值”,{} 内是枚举常量;
  • 特性:枚举常量默认从 0 开始递增(可手动赋值),本质是 int 类型;
  • 用途:替代 #define,增强可读性和类型安全(比如表示状态码、选项)。

enum Status {SUCCESS,  // 0ERROR,    // 1PENDING   // 2
};

7. 程序从编译到运行的整个过程(美团二面真题)

C 语言程序从源码到可执行文件,需经历 4 个阶段,要明确每个阶段的输入输出:

  1. 预处理阶段:处理#include(头文件展开)、///* */(删注释)、#if(条件编译)、#define(宏替换),生成.i文件(仍为 C 语言代码);
  2. 编译阶段:将 C 语言代码翻译为汇编语言,进行语法检查、优化,生成.s文件(汇编代码);
  3. 汇编阶段:将汇编代码转换为二进制指令,生成.o文件(可重定位目标文件);
  4. 链接阶段:将多个.o文件 + 系统库(如 printf 所在的 libc 库)合并,解决符号引用(比如函数调用),生成可执行程序(如 Linux 下的 elf 文件)。

面试技巧:可以举例 “调用 printf”—— 编译时 printf 是未定义的符号,链接阶段会从 libc 库中找到 printf 的实现并关联。

8. extern 关键字的作用(腾讯一面真题)

extern 的核心作用是 “声明外部符号”,分两种场景:

  • 声明外部变量:表示该变量在其他.c 文件中定义,提醒编译器去其他文件找定义(不能重复定义);
  • 声明外部函数:表示该函数在其他文件中实现,常用于多文件编程(比如把函数声明放在.h 文件,实现放在.c 文件)。

示例

// a.c(定义变量)
int g_val = 10;// b.c(声明并使用)
extern int g_val; // 声明,不是定义
printf("%d", g_val); // 正确,会去a.c找定义

注意:extern 不能初始化变量(如extern int g_val=20是错误的,变成了定义)。

9. volatile 关键字的作用(腾讯一面真题)

volatile 的核心是 “禁止编译器优化”,回答要讲清原理和场景:

  • 作用:告诉编译器,修饰的变量可能被 “意外修改”(比如硬件中断、多线程),编译器不能将变量缓存到寄存器,每次访问必须从内存中读取;
  • 常见场景:硬件寄存器操作、多线程共享变量、信号处理函数中的变量。

反例:没有 volatile 时,编译器可能优化代码:

// 没有volatile,编译器可能认为flag一直是0,死循环
int flag = 0;
// 中断服务函数可能修改flag为1
void interrupt_func() { flag = 1; }int main() {while (!flag); // 编译器优化后,可能一直读寄存器中的0return 0;
}// 加volatile后,每次都从内存读flag,能退出循环
volatile int flag = 0;

10. #define 和 const int 定义常量的区别?你更倾向于哪种?(小米一面真题)

这是 “预处理” 和 “编译” 阶段的核心区别,回答要分点清晰:

(1)核心区别

特性#define 宏const int 常量
处理阶段预处理阶段(文本替换)编译 / 运行阶段
类型检查无,仅文本替换有,严格类型检查
调试无法调试(替换后消失)可调试(真正的变量)
内存占用可能重复替换,占用更多内存仅占一份内存

(2)更倾向于 const int

原因:const 有类型安全检查,能避免宏替换导致的隐藏 Bug(比如#define N 1+2N*3会变成1+2*3=7,而非 9),且便于调试和维护。

例外场景:需要定义 “字符串常量” 或 “复杂表达式” 时,可用宏(如#define MAX(a,b) ((a)>(b)?(a):(b))),但要注意加括号避免优先级问题。

11. 指针常量和常量指针(滴滴一面真题)

这两个概念容易混淆,核心看 “const 修饰的是指针还是指向的内容”:

(1)常量指针(const int* p /int const* p)

  • 口诀:“内容不可改,指向可改”;
  • 解释:指针 p 指向的内容是常量,不能通过 p 修改,但 p 可以指向其他变量;
  • 示例:

const int* p = &a;
// *p = 20; 错误,不能修改指向的内容
p = &b;     // 正确,可以修改指向

(2)指针常量(int* const p)

  • 口诀:“指向不可改,内容可改”;
  • 解释:指针 p 的指向是常量,不能指向其他变量,但可以通过 p 修改指向的内容;
  • 示例:

int* const p = &a;
// p = &b; 错误,不能修改指向
*p = 20;     // 正确,可以修改内容

记忆技巧:const 靠近谁,谁就不能改 —— 靠近*是内容不可改,靠近 p 是指向不可改。

12. malloc、calloc 和 realloc 的区别

三者都是 C 语言动态内存分配函数,核心区别在 “初始化” 和 “用途”:

(1)malloc vs calloc

  • 初始化:malloc 不初始化,分配的空间是随机值;calloc 会将空间初始化为 0;
  • 传参:malloc 只传 “总字节数”(如malloc(4*10));calloc 传 “元素个数” 和 “每个元素大小”(如calloc(10,4));
  • 用途:需要初始化的场景用 calloc(如数组初始化),不需要则用 malloc(效率更高)。

(2)realloc 的特殊用途

  • 作用:修改已分配内存的大小(扩大或缩小);
  • 传参:第一个参数是已分配的指针,第二个参数是 “期望的总字节数”;
  • 注意:扩大时可能会分配新地址(并拷贝原数据),缩小则直接截断,原数据保留。

示例

int* p1 = malloc(4*5);   // 20字节,随机值
int* p2 = calloc(5,4);   // 20字节,全0
int* p3 = realloc(p1, 4*10); // 扩大到40字节

13. malloc 的原理(深度考点)

malloc 的实现和操作系统相关,但核心逻辑一致,回答要分 “小内存” 和 “大内存”:

  • 小内存(<128KB):malloc 会维护一个 “空闲链表”,记录当前可用的内存块。申请时先遍历链表,找到足够大的块分配;若没有则调用sbrk()系统调用,扩大堆空间后分配。
  • 大内存(≥128KB):直接调用mmap()系统调用,在文件映射区分配内存(无需通过空闲链表)。
  • 虚拟内存特性:malloc 分配的是 “虚拟地址空间”,此时并未真正分配物理内存;只有当程序访问该内存时,操作系统才会触发 “缺页中断”,分配物理内存并建立虚拟内存和物理内存的映射。
  • 内存分配粒度:操作系统按 “页” 分配物理内存(比如 4KB / 页),即使申请 1 字节,也会分配 1 页物理内存,但虚拟内存仍显示 1 字节。

14. 按行和按列遍历二维数组的性能差异?(快手二面真题)

核心原因是 “CPU 缓存机制”,直接影响访问效率:

  • 按行遍历:C 语言二维数组的存储是 “行优先”(比如 int a [3][4],存储顺序是 a [0][0]→a [0][1]→...→a [0][3]→a [1][0]),内存地址连续。CPU 会预读连续内存到缓存,后续访问直接命中缓存,效率高。
  • 按列遍历:访问的内存地址不连续(比如 a [0][0]→a [1][0]→a [2][0]),每次访问的地址跨度大,缓存无法命中,需要频繁从内存读取数据,效率低。

示例

int a[1000][1000];
// 按行遍历(高效)
for (int i=0; i<1000; i++)for (int j=0; j<1000; j++)a[i][j] = 0;// 按列遍历(低效)
for (int j=0; j<1000; j++)for (int i=0; i<1000; i++)a[i][j] = 0;

面试延伸:可以提 “空间局部性”——CPU 缓存倾向于缓存连续的内存区域,按行遍历正好符合这一特性。

总结:C 语言面试备考技巧

  1. 抓核心:static、指针、内存对齐、编译过程是必考点,必须吃透原理 + 实战;
  2. 记口诀:比如指针常量和常量指针的 “const 靠近谁谁不可改”,方便快速回忆;
  3. 写代码:面试时可能让你手写判断大小端、结构体大小计算、malloc 使用等代码,平时要多练;
  4. 联场景:比如 volatile 关联 “硬件中断”,extern 关联 “多文件编程”,结合场景记忆更深刻。
http://www.dtcms.com/a/577406.html

相关文章:

  • 【AI】拆解神经网络“技术高墙”:一条基于“根本原理-补丁理论-AI部署”哲学的学习路径
  • 让 Elasticsearch Delete By Query 请求立即生效
  • HarmonyOS开发-系统AI能力-语音转文字
  • 巨鹿企业做网站儋州网站建设培训学校
  • 建站优化收费下载网页图片
  • Docker搭建Ngnix、php5.6、php8、postgresql、redis
  • php基础-系统函数-第15天
  • CSP-J教程——第一阶段——第五课:程序流程控制 - 选择结构
  • 【Go微服务框架深度对比】Kratos、Go-Zero、Go-Micro、GoFrame、Sponge五大框架
  • 基于FPGA实现Mini-LVDS转LVDS
  • 做网站的是如何赚钱的哪个小说网站版权做的好处
  • Cache的基本原理
  • 如何提高外贸网站排名南京高端定制网站建设
  • 建网站需要多久网站模板怎么制作
  • 计算机网络:基于TCP协议的自定义协议实现网络计算器功能
  • SpringBoot3+ApolloClient2.3.0集成Apollo2.4.0示例
  • UDP的recvfrom会返回一个完整的数据报
  • Rust实战教程:做一个UDP聊天软件
  • 基于遥感解译与GIS技术生态环境影响评价图件制作
  • 用asp制作一个简单的网站零基础学电脑培训班
  • 广东如何进行网站制作排名做网站在哪里买空间域名
  • 数据结构(长期更新)第6讲:双向链表
  • Debian系统的多内核共存
  • HTTPS 请求抓包,从原理到落地排查的工程化指南(Charles / tcpdump / Wireshark / Sniffmaster)
  • Debian 12 笔记本合盖不休眠设置指南
  • 线性代数 - 奇异值分解(SVD Singular Value Decomposition)- 奇异值在哪里
  • 商城网站开发的完整流程图视频制作价格明细
  • 如何保证Redis和Mysql数据缓存一致性?
  • 八股-Mysql 基础篇(1)
  • 建设公司网站需要准备什么科目苏州建网站的公司