(嵌入式八股)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们提供参考,可以关注一下!!!

查看10道真题和解析