【嵌入式八股7】基础语法
一、static关键字
在C语言中,static
关键字的使用场景和作用如下:
1. 修饰函数体内的局部变量
当static
用于修饰函数体内的局部变量时,该变量的访问权限仅限于该函数内部。不过,它与普通局部变量不同,仅会被初始化一次,并且存储在静态存储区。这意味着在函数的多次调用过程中,该静态局部变量会保留上一次调用结束时的值。示例代码如下:
#include <stdio.h>
void test_static_local() {
static int count = 0;
count++;
printf("Static local variable count: %d\n", count);
}
int main() {
test_static_local(); // 输出 1
test_static_local(); // 输出 2
return 0;
}
2. 修饰模块内、函数体外的全局变量
若static
修饰模块内(.c
文件)、函数体外的全局变量,会将该全局变量的作用域限制在当前模块内部,即该变量只能在当前.c
文件中使用,不能被其他文件通过extern
关键字跨文件共享。这样可以避免不同模块间全局变量的命名冲突。例如:
// file1.c
static int static_global = 10;
// file2.c
// 无法通过 extern int static_global; 来引用 static_global
3. 修饰模块内的函数
当static
修饰模块内的函数时,该函数仅可被本模块调用,不能作为接口暴露给其他模块。这有助于隐藏模块内部的实现细节,提高代码的封装性和安全性。示例如下:
// file1.c
static void static_function() {
printf("This is a static function.\n");
}
void call_static_function() {
static_function();
}
// file2.c
// 无法直接调用 static_function()
注意:static
与extern
不可同时修饰一个变量。因为static
限制变量或函数的作用域为当前模块,而extern
用于声明外部可访问的变量或函数,二者语义冲突。
二、const关键字
const
关键字用于声明常量,变量一旦被const
修饰并初始化后,其值就无法再被修改。下面详细介绍const
在指针中的应用:
1. 常量指针与指针常量
在指针的使用中,*
(指针)和const
(常量)的位置决定了指针和指针所指向的值是否可以被修改,遵循“谁在前先读谁”的原则,*
象征着地址,const
象征着内容,谁在前面谁就不允许改变。
- 指针常量:
int * const p;
// 这里的 const 修饰指针 p,意味着 p 是一个指向整型数的常指针,
// 指针指向不可以修改,但指针指向的整型数可以修改
int a = 10, b = 20;
p = &a; // 初始化指针指向
// p = &b; // 错误,指针指向被限定
*p = 20; // 指针指向的值可以修改
- 常量指针:
const int *p;
// const 修饰 *p,表明 p 是一个指向常整型数的指针,
// 指针指向可以修改,但指针指向的整型数不可修改
int a = 10, b = 20;
p = &a;
// *p = 20; // 错误,指针指向的值被限定
p = &b; // 指针指向可以修改
- 指向常整形数的常指针:
const int * const a;
// 这种情况下,指针指向和指针指向的值都不可修改
2. 在函数参数和返回值中的应用
const
还常用于函数参数和返回值,以防止对传入参数或返回值的意外修改。
void printArray(const int *arr, int size) {
// 防止修改入参 arr 所指向的数组内容
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
const char* getString() {
// 防止修改返回值,返回值为指针的时候
static char str[] = "Hello, World!";
return str;
}
三、volatile关键字
1. 作用
volatile
关键字的作用是告诉编译器,每次对该变量的访问都要从内存或对应外设寄存器中取值放入CPU通用寄存器后再进行操作,防止编译器对代码进行优化。
2. 详解
CPU在读取数据时,会从指定地址处取值并搬运到CPU通用寄存器中进行处理。在不加volatile
关键字时,对于频繁的操作,编译器可能会对代码的汇编指令进行优化,从而导致一些预期之外的结果。例如:
// 比如要往某一地址送两指令:
int *ip = (int *)0x12345678; // 设备地址
*ip = 1; // 第一个指令
*ip = 2; // 第二个指令
// 编译器可能优化为:
int *ip = (int *)0x12345678; // 设备地址
*ip = 2; // 第二个指令
// 造成第一条指令被忽略
// 使用 volatile 关键字
volatile int *ip = (volatile int *)0x12345678; // 设备地址
*ip = 1; // 第一个指令
*ip = 2; // 第二个指令
3. 使用场合
volatile
关键字通常用于以下场合:
- 寄存器:在访问硬件寄存器时,由于寄存器的值可能会被硬件随时修改,因此需要使用
volatile
关键字确保每次访问都能获取到最新的值。 - 临界区访问的变量:在多线程或多任务环境中,临界区的变量可能会被其他线程或任务修改,使用
volatile
可以保证数据的一致性。 - 中断函数访问的全局或static变量:中断函数可能会在任何时候打断主程序的执行,对全局或静态变量进行修改,使用
volatile
可以确保主程序能够及时感知到这些变化。
4. 与Cache的区别
volatile
是对编译器的约束,它可以控制每次从RAM读取数据到通用寄存器,但无法控制从RAM到通用寄存器的过程(从RAM到寄存器要经过cache)。若两次被volatile
修饰的读取指令过快,即使RAM中的值改变了,但由于读取过快没有更新cache,那么实际上搬运到通用寄存器的值来自于cache,此类情况下需要禁用cache。- 编译器优化是针对于LDR命令的,从内存中读取数据到寄存器时不允许优化这一过程,而None-cache保护的是对内存数据的访问(
volatile
无法控制LDR命令执行后是否刷新cache)。
四、#define 与 const区别
#define | 编译的预处理阶段展开替换 | 低,只是简单的文本替换,没有类型检查 | 占用代码段空间(.text ) |
无法调试,因为在预处理阶段就已经被替换掉了 |
const | 编译、运行阶段 | 有数据类型检查,能在编译时发现类型不匹配的错误 | 占用数据段空间(.data 常量区) |
可调式,可以在调试器中查看常量的值 |
五、防止头文件重复引用
在C语言中,为了防止头文件被重复引用,通常使用、和预处理指令。其原理是:当程序中第一次该文件时,由于尚未定义,所以会定义并执行“头文
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
一些八股模拟拷打Point,万一有点用呢