(嵌入式八股)No.1 C语言(关键字)

写在前面,在嵌入式软件面试中,面试官会问你使用哪个编程语言更多一点?C or C++?其实在嵌入式中用C多一点,那么接下来你要重点掌握关键字的作用及其原理,然后就是指针,位运算等,还有内存管理,排序算法最好也了解一下,毕竟有的面试官直接让你手撕快排!

1.1 static(建议不仅要知道用法还要知道原理)

static关键字有多种用途,主要用于控制变量和函数的作用域和存储期。

  它主要有三种常见使用场景:

  1. 修饰局部变量(local variable)

void foo(void) {
    static int count = 0;
    count++;
    printf("count = %d\n", count);
}

  2. 修饰全局变量(global variable)

应用:
限制全局变量的可见性(“封装”全局变量)
避免命名冲突
// file1.c
static int g_val = 10; // 仅 file1.c 内可见

// file2.c
static int g_val = 20; // 与 file1.c 的 g_val 无冲突

   3. 修饰函数(function)

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)

❌ 指针不可改✅ 所指内容可改

const int * const p

指向常量的常指针

❌ 都不可改

1.3 const 和 #define的区别(了解即可)

1.#define 是一个预处理指令,用于定义宏(macro)。宏是一种简单的文本替换机制,它在编译之前由预处理器(preprocessor)处理。#define 可以用于定义常量、函数宏以及条件编译指令。

类别

示例

功能

对象宏

#define PI 3.14

定义常量

数宏

#define MAX(a,b) ((a)>(b)?(a):(b))

实现简单函数替代(注意括号的重要性!!!)

条件宏

#ifdef DEBUG ... #endif

控制编译行为

字符串化

#define STR(x) #x

参数转字符串

拼接

#define JOIN(a,b) a##b

拼接标识符

系统宏

__FILE__, __LINE__

获取编译信息

2.二者区别

特性

#define

const

处理阶段

预处理阶段(文本替换)

编译阶段

是否分配内存

是否有类型

有类型

是否可调试(在符号表中)

是否可作用域控制

否(全局有效)

调试安全性

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表示“易变的”,告诉编译器:这个变量的值随时可能发生变化,不能对它进行优化。

使用场景:

情况

示例

外设寄存器(硬件自动修改)

ADC 状态寄存器

中断服务程序修改的变量

flagcounter

多线程共享变量

RTOS / 多核系统中的全局标志位

内存映射的 I/O 寄存器

#define UART_DR (*(volatile uint32_t *)0x4000C000)

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;
}

使用对象:

对象类型

是否可用 register

说明

局部变量

✅ 支持

常见用法

函数形参

✅ 支持

提示编译器优化访问

全局变量

❌ 不允许

全局变量存在静态存储区

指针

✅ 可用

但不能取地址

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)

⚠️ 不可递归内联

大多数编译器禁止递归内联

⚠️ 调试困难

内联后难以单步进入函数

⚠️ 与 static 联合使用更安全

避免重复定义符号

1.7 extern

extern 关键字用于声明变量或函数的定义位于其他地方。它主要用于在多个源文件之间共享全局变量和函数,或者在头文件中声明全局变量和函数。extern 声明告诉编译器,该变量或函数的定义在其他地方,编译器在链接阶段会找到其定义。

使用场景:

使用场景

作用

示例

外部变量声明

告诉编译器该变量在别的源文件中定义,不在当前文件分配空间

extern int g_count;

外部函数声明

声明一个在其他文件中定义的函数(其实默认函数声明就是 extern)

extern void foo(void);

与全局变量配合

避免多重定义错误,便于模块化开发

多文件工程

示例:

      假设我们有两个文件:

注意事项:

#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的对比:

特性

extern

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:从函数返回值并结束函数。

#牛客创作赏金赛##八股##我的秋招日记##嵌入式软件##秋招白月光#
泻湖花园嵌入式Offer指南 文章被收录于专栏

从入门到上岸,一站式搞定求职! 本硕纯机械,无竞赛无论文,后转行嵌入式软件开发(因为课题组师哥转嵌入式拿到30Woffer之后狠狠心动),秋招最终收获35W+offer可以为27届或者28届的的UU们提供参考,可以关注一下!!!

全部评论

相关推荐

评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务