(嵌入式八股)No.1 C语言(关键字)
写在前面,在嵌入式软件面试中,面试官会问你使用哪个编程语言更多一点?C or C++?其实在嵌入式中用C多一点,那么接下来你要重点掌握关键字的作用及其原理,然后就是指针,位运算等,还有内存管理,排序算法最好也了解一下,毕竟有的面试官直接让你手撕快排!
1.1 static(建议不仅要知道用法还要知道原理)
static关键字有多种用途,主要用于控制变量和函数的作用域和存储期。
它主要有三种常见使用场景:
void foo(void) {
static int count = 0;
count++;
printf("count = %d\n", count);
}
应用: 限制全局变量的可见性(“封装”全局变量) 避免命名冲突 // file1.c static int g_val = 10; // 仅 file1.c 内可见 // file2.c static int g_val = 20; // 与 file1.c 的 g_val 无冲突
static void helper(void) {
// 仅在本文件可见
}
总结:
a.修饰局部变量
b.修饰全局变量
c.修饰函数
1.2 const(C中的变量:局部,函数形成,返回值,指针)
const 是 C 语言中一个非常常见但容易混淆的关键字,它用于定义只读对象(read-only object),也常与指针结合使用。
Const的基本用法:
1.修饰变量
const int a = 100; // a 是常量,值不能被修改 // a = 200; // 错误:a 是常量,不能被修改 说明:const 修饰的变量在初始化后,其值不能被修改。
2.修饰函数参数
void func(const int x) {
// x = 200; // 错误:x 是常量,不能被修改
}
说明:const 修饰函数参数,表示函数体内不能修改该参数的值。这通常用于防止函数意外修改参数值。
3.修饰函数返回值
const char* getstr() {
return "Hello, World!"; // 返回的是字符串常量
}
说明:const 修饰函数返回值,表示返回值不能被修改。这通常用于返回字符串常量或其他不可修改的对象。
4.修饰指针(务必掌握)
1.常量指针:指针指向的内容不能修改,但指针的地址可以修改。 const int* a; // 等同于 int const* a; const int b = 100; const int* a = &b; // *a = 200; // 错误:a 指向的内容不能修改 a = &b; // 正确:a 的地址可以修改 说明:int* const a 表示 a 的地址是常量,不能修改 a 的地址。 2.指针常量:指针的地址不能修改,但指针指向的内容可以修改。 int* const a; int b = 100; int* const a = &b; *a = 200; // 正确:a 指向的内容可以修改 // a = &b; // 错误:a 的地址不能修改 说明:int* const a 表示 a 的地址是常量,不能修改 a 的地址。 3.常量指针常量:指针的地址和指向的内容都不能修改。 const int* const a; const int b = 100; const int* const a = &b; // *a = 200; // 错误:a 指向的内容不能修改 // a = &b; // 错误:a 的地址不能修改 说明:const int* const a 表示 a 的地址和指向的内容都是常量,都不能被修改。
形式 | 含义 | 可改内容 |
const int *p 或 int const *p | 常量指针(pointer to const) | ✅ 指针可改❌ 所指内容不可改 |
int * const p | 指针常量(const pointer) | ❌ 指针不可改✅ 所指内容可改 |
| 指向常量的常指针 | ❌ 都不可改 |
1.3 const 和 #define的区别(了解即可)
1.#define 是一个预处理指令,用于定义宏(macro)。宏是一种简单的文本替换机制,它在编译之前由预处理器(preprocessor)处理。#define 可以用于定义常量、函数宏以及条件编译指令。
类别 | 示例 | 功能 |
对象宏 |
| 定义常量 |
函数宏 |
| 实现简单函数替代(注意括号的重要性!!!) |
条件宏 |
| 控制编译行为 |
字符串化 |
| 参数转字符串 |
拼接 |
| 拼接标识符 |
系统宏 |
| 获取编译信息 |
2.二者区别
特性 |
|
|
处理阶段 | 预处理阶段(文本替换) | 编译阶段 |
是否分配内存 | 否 | 是 |
是否有类型 | 否 | 有类型 |
是否可调试(在符号表中) | 否 | 是 |
是否可作用域控制 | 否(全局有效) | 是 |
调试安全性 | 低 | 高 |
3.使用 #define 定义常量
#include <stdio.h>
#define MAX_VALUE 100 // 使用宏定义一个常量 MAX_VALUE,值为 100
// 这是一个简单的文本替换,没有类型,作用域是整个文件
int main() {
int value = MAX_VALUE; // 将 MAX_VALUE 的值赋给变量 value
// 在预处理阶段,MAX_VALUE 会被替换为 100
printf("The value is: %d\n", value); // 打印变量 value 的值
return 0; // 程序正常结束
}
4.使用const定义常量
#include <stdio.h>
const int MAX_VALUE = 100; // 使用 const 定义一个常量 MAX_VALUE,值为 100
// 这是一个有类型的常量,类型为 int,作用域取决于定义位置
// 如果定义在函数外部,它是全局常量;如果定义在函数内部,它是局部常量
void MofConst()
{
const int MIN_VALUE = 10; //注意如果在函数内部定义的局部变量后面是无法访问到的。
}
int main() {
int value = MAX_VALUE; // 将 MAX_VALUE 的值赋给变量 value
// MAX_VALUE 是一个实际的变量,有内存分配
printf("The value is: %d\n", value); // 打印变量 value 的值
return 0; // 程序正常结束
}
5.const修饰的变量存放位置
对于const修饰的局部变量:存放在栈中,代码结束就会释放,在C语言中可以通过指针修改里面的值。
对于const修饰的全局变量(已初始化的)存放在只读数据段,不可以通过指针修改里面的值,未出示化的存放在.bss。
1.4 volatile(编译优化阶段)
它在嵌入式开发、驱动编程、RTOS 等场景中尤为关键,因为它直接影响编译器优化行为。volatile表示“易变的”,告诉编译器:这个变量的值随时可能发生变化,不能对它进行优化。
使用场景:
情况 | 示例 |
外设寄存器(硬件自动修改) |
|
中断服务程序修改的变量 |
|
多线程共享变量 | RTOS / 多核系统中的全局标志位 |
内存映射的 I/O 寄存器 |
|
volatile int flag = 0;
void ISR_Handler(void)
{
flag = 1; // 中断修改
}
int main(void)
{
while (!flag) {
// 等待中断置位
}
}
1.5 Register
register 关键字用于建议编译器将变量存储在寄存器中,以提高访问速度。寄存器是CPU内部的高速存储单元,访问速度比普通内存快得多。然而,寄存器的数量有限,因此编译器并不总是会遵循 register 的建议。
register int counter; 编译器尽量将变量 counter 存放在寄存器中,以便频繁访问时提高速度。
特性 | 说明 |
存储位置 | CPU 寄存器(如果可用) |
生命周期 | 与普通局部变量相同(函数调用期间有效) |
作用域 | 局部变量或函数形参 |
初始化 | 必须在定义时初始化(不能像全局变量一样默认置零) |
地址访问 | 不能对其取地址(因为寄存器没有内存地址) |
#include <stdio.h>
int main(void)
{
register int i;
for (i = 0; i < 1000000; i++)
{
// 假设 i 存放在寄存器中,加快循环速度
}
// printf("%p", &i); // ❌ 错误:不能对 register 变量取地址
return 0;
}
使用对象:
对象类型 | 是否可用 | 说明 |
局部变量 | ✅ 支持 | 常见用法 |
函数形参 | ✅ 支持 | 提示编译器优化访问 |
全局变量 | ❌ 不允许 | 全局变量存在静态存储区 |
指针 | ✅ 可用 | 但不能取地址 |
1.6 inline
inline 表示“内联函数”,建议编译器在调用处直接插入函数代码本体,而不是进行普通的函数调用。
也就是说:
- 调用普通函数 → 会有压栈、跳转、返回等开销
- 调用 inline 函数 → 编译器可将函数体直接展开(类似宏替换,但更安全)
示例对比:
int add(int a, int b) {
return a + b;
}
int main(void) {
int x = add(2, 3); // 实际调用过程:压栈 → 跳转 → 返回
}
inline int add(int a, int b) {
return a + b;
}
int main(void) {
int x = add(2, 3); // 编译器可能直接展开为:int x = (2) + (3);
}
💡 编译器会根据函数复杂度、优化等级、调试信息等综合考虑是否真正“内联”。
即:inline 是建议(hint),不是强制命令。
核心特性:
特性 | 含义 |
① 减少函数调用开销 | 无需栈操作、跳转、返回 |
② 提高执行效率 | 特别是频繁调用的小函数(如数值计算) |
③ 保持类型安全 | 相比宏函数更安全、有作用域检查 |
注意事项:
注意点 | 说明 |
⚠️ inline 不是强制的 | 编译器可能忽略内联请求 |
⚠️ 不适合大函数 | 展开会增加代码体积(Code Size) |
⚠️ 不可递归内联 | 大多数编译器禁止递归内联 |
⚠️ 调试困难 | 内联后难以单步进入函数 |
⚠️ 与 | 避免重复定义符号 |
1.7 extern
extern 关键字用于声明变量或函数的定义位于其他地方。它主要用于在多个源文件之间共享全局变量和函数,或者在头文件中声明全局变量和函数。extern 声明告诉编译器,该变量或函数的定义在其他地方,编译器在链接阶段会找到其定义。
使用场景:
使用场景 | 作用 | 示例 |
外部变量声明 | 告诉编译器该变量在别的源文件中定义,不在当前文件分配空间 |
|
外部函数声明 | 声明一个在其他文件中定义的函数(其实默认函数声明就是 extern) |
|
与全局变量配合 | 避免多重定义错误,便于模块化开发 | 多文件工程 |
示例:
假设我们有两个文件:
注意事项:
#include <stdio.h>
int g_count = 10; // 定义全局变量
void print_count(void)
{
printf("g_count = %d\n", g_count);
}
#include <stdio.h>
extern int g_count; // 声明外部变量
void print_count(void); // 声明外部函数
int main(void)
{
g_count = 20; // 直接使用外部变量
print_count(); // 调用外部函数
return 0;
}
- g_count 在 file1.c 中定义并分配内存;
- file2.c 中的 extern int g_count; 告诉编译器:“这个变量在别处定义,别重复分配空间”;
- 链接阶段,链接器会将 file2.o 中的 g_count 引用与 file1.o 中的定义关联起来。
和static的对比:
特性 |
|
|
作用域 | 全局(跨文件可见) | 文件内部(仅本文件可见) |
存储位置 | 静态存储区 | 静态存储区 |
链接属性 | 外部链接 | 内部链接 |
定义行为 | 不分配空间,仅声明 | 分配空间、隐藏符号 |
1.8 typedef
typedef 是 C 语言和 C++ 语言中的一个关键字,用于为已有的数据类型创建一个新的名字(别名)。
常见用法:
typedef 现有类型名 新类型名;
#include <stdio.h>
typedef int Integer; // 为基本类型创建别名
typedef int* IntPtr; //为指针类型创建别名
typedef int Array[10]; //为数组类型创建别名
typedef struct { //为结构体创建别名
int x;
int y;
} Point;
typedef int (*Func)(int, int); //为函数指针创建别名
int add(int a, int b) {
return a + b;
}
int main() {
Integer a = 10;
IntPtr p = &a;
Array arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Point pt = {1, 2};
Func f = add;
printf("a = %d\n", a);
printf("p = %p, *p = %d\n", p, *p);
printf("arr = {");
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("}\n");
printf("Point: (%d, %d)\n", pt.x, pt.y);
printf("f(3, 4) = %d\n", f(3, 4));
return 0;
}
注意事项:
- 类型别名和原始类型等价:typedef 只是为类型创建了一个别名,本质上还是原始类型。例如,typedef int Integer; 后,Integer 和 int 是完全等价的。
- 不能创建新的类型:typedef 不会创建一个新的数据类型,它只是为已有的类型提供了一个别名。
- 与 #define 的区别:#define 是预处理器指令,用于简单的文本替换,而 typedef 是编译器级别的类型定义。typedef 更安全,因为它会进行类型检查。
1.9 Continue、break、return
1.continue
作用:
- 跳过当前迭代:当执行到 continue 语句时,循环体中 continue 之后的代码将被跳过,直接进入下一次循环迭代。
- 只能用于循环:continue 只能在 for、while 或 do-while 循环中使用,否则会导致编译错误。
#include <stdio.h>
int main() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数的输出
}
printf("%d\n", i);
}
return 0;
}
输出:
1
3
5
7
9
2.break
break 语句用于终止当前的循环或 switch 语句。
作用:
- 终止循环:当执行到 break 语句时,循环将立即终止,控制权转移到循环体之外的下一条语句。
- 退出 switch:在 switch 语句中,break 用于防止代码的“穿透”(fall-through)。
- 只能用于循环或 switch:break 只能在 for、while、do-while 循环或 switch 语句中使用,否则会导致编译错误。
#include <stdio.h>
int main() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 当 i == 5 时,终止循环
}
printf("%d\n", i);
}
printf("Loop ended.\n");
return 0;
}
输出:
0
1
2
3
4
Loop ended.
3. return
return 语句用于从函数中返回值,并结束函数的执行。
作用:
- 返回值:将指定的值返回给函数的调用者。
- 结束函数:无论函数执行到哪里,return 语句都会立即结束函数的执行,并返回到调用点。
- 适用于所有函数:return 可以用于任何函数,包括 main 函数。
#include <stdio.h>
int add(int a, int b) {
return a + b; // 返回 a 和 b 的和
}
void printMessage() {
printf("Hello, World!\n");
return; // 早期返回,结束函数
}
int main() {
int result = add(3, 4);
printf("Result: %d\n", result);
printMessage();
return 0; // 返回 0 表示程序正常结束
}
输出:
Result: 7
Hello, World!
总结:
- continue:跳过当前迭代,进入下一次迭代。
- break:终止循环或 switch。
- return:从函数返回值并结束函数。
从入门到上岸,一站式搞定求职! 本硕纯机械,无竞赛无论文,后转行嵌入式软件开发(因为课题组师哥转嵌入式拿到30Woffer之后狠狠心动),秋招最终收获35W+offer可以为27届或者28届的的UU们提供参考,可以关注一下!!!

