找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2688

积分

0

好友

375

主题
发表于 4 小时前 | 查看: 1| 回复: 0

STM32 等嵌入式系统开发中,扎实的C语言功底是基石。以下梳理了面试或实际工作中高频出现的C语言问题,并附上代码示例与解析,帮助开发者查漏补缺。

1. 预处理指令 #define 的运用

如何用预处理指令#define声明一个常量,用以表示1年中的秒数(忽略闰年)?

#define  SECONDS_PER_YEAR  (60 * 60 * 24 * 365)UL

解析:计算过程使用预处理器,而非运行时计算,效率更高。末尾的UL表示无符号长整型,确保常量在特定平台上的类型正确性。

如何编写一个标准宏MIN,用于返回两个参数中的较小值?

#define  MIN(A,B) ((A) <= (B) ? (A):(B))

解析:宏定义中的每个参数都必须用括号括起来,以防止运算符优先级导致的意外错误。整个表达式也需要括号,确保宏作为一个整体使用时的正确性。

预处理器标识#error的目的是什么?

#error : 停止编译并显示错误信息

解析:当预处理器遇到#error指令时,会强制中止编译过程,并输出其后的错误信息。常用于检查不满足的编译条件,如特定的宏定义或版本要求。

2. 嵌入式系统中的无限循环

嵌入式系统中经常需要无限循环,如何使用C语言编写死循环?

C语言死循环的三种写法代码示例

如上图所示,常见的三种写法为:

  • while(1) {…}
  • do {…} while(1)
  • for(;;) {…}

3. 变量与指针的定义

请用变量a给出以下定义:

  • 整型数:int a;
  • 指向整型数的指针:int *a;
  • 指向指针的指针,它指向的指针指向整型数:int **a;
  • 有10个整型数的数组:int a[10];
  • 有10个指针的数组,它的指针指向整型数:int *a[10];
  • 指向有10个整型数的数组的指针:int (*a)[10];
  • 指向函数的指针,该函数有一个整型参数,并返回一个整型数:int (*a)(int);

4. static关键字的作用

static关键字在C语言中主要有三个作用:

C语言static关键字作用说明

  1. 在函数体内:修饰局部变量,使其生命周期变为整个程序运行期,但作用域不变(仅函数内可见),实现函数调用间的状态保持。
  2. 在模块内(.c文件内):修饰全局变量,使其作用域仅限于本模块(文件),无法被其他模块的文件访问。
  3. 在模块内:修饰函数,使其作用域仅限于本模块,成为该模块的“私有”函数。

5. const关键字的作用

const关键字用于定义只读变量,其主要作用包括:

const关键字的核心作用

  1. 定义只读常量:明确告知编译器和其他程序员,此变量的值不应被修改。
  2. 提高代码灵活性:配合指针使用,可以更精细地控制数据的可修改性。
  3. 提供编译期保护:编译器会阻止试图修改const修饰参数的代码,防止无意的误操作。

const与指针结合的几种经典定义:

const与指针结合使用的定义与效果

  • const int a; / int const a;a是整型常量。
  • const int *a;a是指向整型常量的指针(指针可变,指向的数据不可变)。
  • int * const a;a是指向整型的常量指针(指针不可变,指向的数据可变)。
  • const int * const a;a是指向整型常量的常量指针(指针和指向的数据都不可变)。

6. volatile关键字的作用与实例

volatile用于修饰变量,告知编译器该变量的值可能会被程序未知的因素改变(如硬件寄存器、操作系统中断、多线程共享)。优化器在每次使用该变量时,都必须从内存中重新读取其值,而不是使用寄存器中暂存的副本。

需要定义为volatile的变量场景示例:

volatile变量的典型应用场景

  1. 并行设备的硬件寄存器(如状态寄存器)。
  2. 一个中断服务程序中会访问到的非自动变量(即全局变量)。
  3. 多任务或多线程应用中,被多个任务共享的变量。

7. 嵌入式系统位操作

嵌入式系统经常需要对硬件寄存器或变量进行位操作。例如,如何置位和清零一个变量的某一位?

#define BIT3 (0x08 << 3) // 定义第3位为1
static int a;

void set_bit3() {
    a |= BIT3; // 将a的第3位置1
}

void clear_bit3() {
    a &= ~BIT3; // 将a的第3位清零
}

嵌入式位操作代码示例

8. 访问特定内存地址

有时需要直接访问特定的物理内存地址。例如,如何设置绝对地址0x67a9处的整型变量的值为0xaa66

int *ptr = NULL;
ptr = (int *) 0x67a9; // 将指针指向绝对地址
*ptr = 0xaa66; // 向该地址写入数据

访问特定内存地址代码示例

9. 中断服务程序(ISR)注意事项

中断是嵌入式系统的核心机制之一。当中断事件发生时,CPU暂停当前程序,转去执行中断服务程序(ISR),执行完毕后再返回。

在编写ISR时,需遵循以下规范:

中断服务程序(ISR)编写注意事项

  1. 不能有返回值:ISR应被声明为void类型。
  2. 不能传递参数:ISR没有参数列表。
  3. 应保持短小精悍:避免在ISR中进行复杂的浮点运算,因为可能涉及上下文保存,效率低下且行为不确定。
  4. 避免调用不可重入函数:例如printf(),它通常有重入性和性能问题,不应当在ISR中调用。

10. 有符号与无符号整型的隐式转换

下面这段代码的输出结果是什么?

void foo(void) {
    unsigned int a = 6;
    int b = -20;
    (a + b > 6) ? puts(" > 6 ") : puts(" <= 6 ");
}

有符号与无符号数混合运算代码

解析:当表达式中同时存在有符号和无符号类型时,C语言标准规定,所有操作数会被自动转换为无符号类型。因此,-20会被转换成一个很大的无符号整数。(a + b)的结果远大于6,所以最终输出为 “ > 6 ”

11. 动态内存分配

在支持动态内存的嵌入式环境中,基本的分配与释放操作如下:

int *p = NULL;
p = (int *)malloc(sizeof(int) * 128); // 申请128个int大小的内存
free(p); // 使用完毕后释放内存
p = NULL; // 建议将指针置NULL,防止野指针

动态内存分配与释放代码示例

注意:在资源受限的嵌入式系统中,动态内存分配需谨慎,需注意内存泄漏和碎片问题。

12. typedef 与 #define 定义指针类型的区别

typedef用于为现有类型创建别名。在定义指针类型时,它与#define有显著区别。

#define dPS struct s *
typedef struct s * tPS;

dPS p1, p2; // 等价于 struct s * p1, p2;  (p1是指针,p2是结构体变量)
tPS p3, p4; // 正确定义了两个结构体指针 p3 和 p4

typedef与#define定义指针类型的区别

解析#define是简单的文本替换,dPS p1, p2;被展开为struct s * p1, p2;,这导致只有p1是指针,p2是一个结构体变量。而typedef创建了一个完整的新类型别名tPS,因此tPS p3, p4;明确定义了两个指针。

掌握这些C语言核心知识点,是进行高效、稳定嵌入式开发的前提。希望这份梳理能帮助大家巩固基础。如果你想深入探讨更多嵌入式或预处理器相关的技术细节,欢迎来云栈社区交流分享。




上一篇:机器人核心运动机构盘点:从机械原理到行走步态实现
下一篇:1940年代美军制式装备:Signal Corps Test Set 1-77-J 军用万用表拆解评测
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-26 18:43 , Processed in 0.399809 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表