当前位置: 首页 > news >正文

嵌入式面试题:C 语言基础重点总结

题目1:

static 全局变量与普通全局变量,static 局部变量与普通局部变量,static 函数与普通函数的区别。

在 C 语言中,static 关键字具有多种作用,深刻影响着变量和函数的特性。以下从 static 全局变量、局部变量、函数三个维度,全面解析其与普通变量、函数的区别,并拓展相关应用场景。

1.1 static 全局变量 vs 普通全局变量

知识点详细解答
  • 作用域
    普通全局变量的作用域是整个工程(多个源文件),通过 extern 关键字可在其他文件中引用。例如:

    c

    // file1.c  
    int global_var = 10;  
    // file2.c  
    extern int global_var; // 正确引用,使用 file1.c 中定义的 global_var  
    


    而 static 全局变量的作用域仅限于定义它的文件,对其他文件不可见。
    // file1.c  
    static int static_global_var = 20;  
    // file2.c  
    extern int static_global_var; // 编译错误,无法引用其他文件的 static 全局变量  
    
  • 链接属性
    普通全局变量具有 外部链接属性,可在多个文件间共享;static 全局变量具有 内部链接属性,仅在本文件内有效。
  • 内存分配
    两者都存储于静态存储区,程序运行期间始终存在。
拓展

在大型工程中,若多个文件定义同名普通全局变量,会引发 “重复定义” 错误。而 static 全局变量能有效避免命名冲突,例如:多个团队开发不同模块时,可使用 static 全局变量隐藏模块内专用的全局数据,防止与其他模块命名冲突。

1.2 static 局部变量 vs 普通局部变量

知识点详细解答
  • 生命周期
    普通局部变量存储在栈中,函数执行结束后内存释放,生命周期随函数调用结束而终止。
    static 局部变量存储在静态存储区,生命周期贯穿程序运行始终。例如:
    void count() {  int normal_var = 0;  static int static_var = 0;  normal_var++;  static_var++;  printf("normal_var: %d, static_var: %d\n", normal_var, static_var);  
    }  
    // 多次调用 count 函数,normal_var 每次从 0 开始计数,而 static_var 持续累加  
    
  • 初始化
    普通局部变量每次函数调用时重新初始化;static 局部变量仅在程序初次执行到定义处时初始化一次。
  • 存储位置
    普通局部变量在栈区,static 局部变量在静态存储区。
拓展

利用 static 局部变量的特性,可实现 “记忆” 功能。例如,统计某个函数被调用的次数:

int func_call_count() {  static int count = 0;  count++;  return count;  
}  

每次调用 func_call_countcount 都会记录调用次数,且无需在函数外部维护计数变量。

1.3 static 函数 vs 普通函数

知识点详细解答
  • 作用域
    普通函数的作用域是整个工程,其他文件通过 extern 声明后可调用。
    static 函数的作用域仅限于定义它的文件,对其他文件不可见。例如:

    c

    // file1.c  
    static void static_func() { printf("This is a static function.\n"); }  
    void normal_func() { static_func(); } // 正确,file1.c 内可调用 static_func  
    // file2.c  
    extern void static_func(); // 编译错误,无法引用 file1.c 的 static 函数  
    
  • 链接属性
    普通函数具有外部链接属性,static 函数具有内部链接属性。
拓展

在模块化编程中,static 函数可隐藏模块内部实现细节。例如,开发一个数学计算模块,内部辅助函数(如某个特定算法的子函数)可声明为 static,避免被外部错误调用或依赖,同时防止与其他模块的函数名冲突,增强代码的封装性和可维护性。

通过对 static 相关特性的深入剖析,不仅能应对面试考查,更能在实际嵌入式开发中合理运用 static,优化代码结构,提升程序的稳定性和可维护性。

题目2:

2、如何引用一个已经定义过的全局变量?全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
2.1 引用已定义的全局变量
  • 知识点extern关键字的作用、变量声明与定义的区别、跨文件引用全局变量的流程。

  • 详细解答
    extern关键字用于声明一个全局变量,它的作用是告知编译器该变量已在其他地方定义,此时编译器不会为其分配内存空间。而变量的定义则是为变量分配内存并赋予初始值的过程。

    步骤与例子

    1. 新建 data.c,定义全局变量:
      // data.c  
      int global_var = 50; // 定义全局变量,为其分配内存并初始化为 50  
      
    2. 新建 main.c,通过 extern 声明后使用:
      // main.c  
      extern int global_var; // 声明 global_var,告知编译器它在其他文件中定义  
      int main() {  printf("global_var: %d\n", global_var); // 使用已定义的全局变量  return 0;  
      }  
      

    这样通过 extern 实现了跨文件引用,main.c 无需重复定义 global_var,避免了内存的重复分配。

2.2 全局变量不可定义在头文件
  • 知识点:头文件的包含特性、C 语言的 “单一定义原则”(One Definition Rule, ODR)、在头文件定义全局变量导致编译错误的原因、正确的全局变量定义与声明方式。

  • 详细解答
    头文件的特性是会被多个 .c 源文件包含。C 语言的 “单一定义原则” 规定,一个变量在整个程序中只能有一个定义。如果在头文件中定义全局变量,当多个 .c 文件包含该头文件时,每个 .c 文件中都会有该全局变量的定义,这就违反了 “单一定义原则”,在链接阶段会导致重复定义的编译错误。

    正确步骤与例子

    1. 定义头文件 global_def.h,放置 extern 声明:
      // global_def.h  
      extern int common_global; // 仅声明,不分配内存  
      
    2. 新建 global_impl.c,定义全局变量:
      // global_impl.c  
      #include "global_def.h"  
      int common_global = 0; // 唯一的定义,分配内存并初始化  
      
    3. 其他文件(如 use_global.c)包含头文件后使用:
      // use_global.c  
      #include "global_def.h"  
      #include <stdio.h>  
      int main() {  common_global = 10;  printf("common_global: %d\n", common_global);  return 0;  
      }  
      

    错误示例步骤

    1. 新建 bad_header.h,定义全局变量:
      // bad_header.h  
      int error_global = 10; // 错误:在头文件中定义全局变量  
      
    2. file1.c 和 file2.c 都包含它:
      // file1.c  
      #include "bad_header.h"  
      
       
      // file2.c  
      #include "bad_header.h"  
      

    编译时,file1.c 和 file2.c 中都有 error_global 的定义,链接时会报错 “重复定义”。

拓展

在团队协作开发大型项目时,严格遵循 “头文件声明、.c 文件定义” 的规范管理全局变量至关重要。例如上述 global_def.h(声明)和 global_impl.c(定义)的组合,能确保多人开发不同模块时,全局变量的使用规范统一,避免命名冲突。若违反规范,如 bad_header.h 的错误示例,会导致编译错误,大幅增加调试成本。遵循该规范可有效提升代码的可维护性与团队协作效率,是嵌入式开发等场景中必须遵守的基本准则。

题目3:

#include <stdio.h>
int main(void) {int a, b, c, d;a = 10;b = a++; // 先赋值b=10,a自增到11c = ++a; // a先自增到12,赋值c=12d = 10 * a++; // 先计算10*12=120赋给d,a自增到13printf("b, c, d: %d, %d, %d", b, c, d); // 输出:10, 12, 120return 0;
}

知识点

后置自增运算符(a++)与前置自增运算符(++a)的区别、赋值运算顺序。

详细解答

  1. 首先看 int a, b, c, d; a = 10;,这一步是定义整型变量 abcd,并给 a 赋初始值 10
  2. 执行 b = a++;
    • 后置自增运算符 a++ 的特点是 “先取值,后自增”。
    • 先将 a 的当前值 10 赋给 b,然后 a 自增 1,此时 a 的值变为 11。所以 b = 10a = 11
  3. 执行 c = ++a;
    • 前置自增运算符 ++a 的特点是 “先自增,后取值”。
    • a 先自增 1,值变为 12,再将这个值赋给 c。所以 c = 12a = 12
  4. 执行 d = 10 * a++;
    • 先进行乘法运算,此时使用 a 的当前值 12,计算 10 * 12 = 120 赋给 d
    • 然后 a 自增 1,值变为 13。所以 d = 120a = 13
  5. 最后 printf("b, c, d: %d, %d, %d", b, c, d); 输出 bcd 的值,即 10, 12, 120

题目4:

#include <stdio.h>
int inc(int a) {return (++a); // a自增后返回
}
int multi(int* a, int* b, int* c) {return (*c = *a * *b); // *c赋值为*a乘*b,同时返回该值
}
typedef int(FUNC1)(int);
typedef int(FUNC2)(int*, int*, int*);
void show(FUNC2 fun, int arg1, int* arg2) {FUNC1 p = &inc;int temp = p(arg1); // 等价于temp = inc(arg1),arg1自增1后赋给tempfun(&temp, &arg1, arg2); // 调用multi,*arg2 = temp * arg1printf("%d\n", *arg2);
}
// 假设调用示例(需补充main函数):
// int x = 2, y;
// show(multi, x, &y); 
// 执行过程:temp = inc(2) → temp=3;multi中*arg2 = 3*2=6 → 输出6
涉及知识点
  1. 函数指针的定义与使用
  2. 值传递与指针传递的本质区别
  3. 前置自增运算符(++a)的特性
  4. 指针解引用(*)与内存操作
  5. 回调函数的实现逻辑

1. 函数指针的定义与使用

知识点解析
  • 函数指针:是指向函数的指针变量,本质是函数在内存中的入口地址。
  • 定义格式typedef 返回值类型(函数名)(参数列表);,用于简化函数指针的声明。
举例说明
// 定义函数指针类型 FUNC1:指向参数为 int、返回值为 int 的函数  
typedef int(FUNC1)(int);  
// 定义函数指针类型 FUNC2:指向参数为三个 int*、返回值为 int 的函数  
typedef int(FUNC2)(int*, int*, int*);  // 具体函数实现  
int inc(int a) { return ++a; }          // 符合 FUNC1 类型  
int multi(int* a, int* b, int* c) {     // 符合 FUNC2 类型  return (*c = *a * *b);  // 解引用指针,操作指向的变量  
}  

  • 使用函数指针
    void show(FUNC2 fun, int arg1, int* arg2) {  FUNC1 p = &inc;  // 指针 p 指向 inc 函数(&可省略,函数名本身是地址)  int temp = p(arg1);  // 等价于 temp = inc(arg1)  fun(&temp, &arg1, arg2);  // 调用传入的函数指针 fun(此处为 multi)  
    }  
    

2. 值传递与指针传递的区别

知识点解析
  • 值传递:函数参数是原始变量的副本,函数内修改不影响外部变量。
  • 指针传递:函数参数是变量的地址,通过解引用(*)直接操作原始变量的内存。
举例对比
  • 值传递示例(inc 函数)
    int inc(int a) {  a = a + 1;  // 仅修改副本 a,外部变量不变  return a;  
    }  
    int x = 2;  
    inc(x);  // x 仍为 2,因为传递的是副本  
    
  • 指针传递示例(multi 函数)
    int multi(int* a, int* b, int* c) {  *c = *a * *b;  // 通过指针修改 c 指向的变量(如 y)  return *c;  
    }  
    int y;  
    multi(&temp, &arg1, &y);  // y 的值会被修改为 temp * arg1  
    

3. 前置自增运算符(++a)的特性

知识点解析
  • 前置自增++a 先将变量自增 1,再使用新值(先增后用)。
  • 与后置自增(a++)的区别:后置是先使用原值,再自增(先用后增)。
举例说明
int a = 2;  
int b = ++a;  // a 先变为 3,b = 3(前置)  
int c = a++;  // c = 3(取原值),a 变为 4(后置)  

在 inc 函数中:

int inc(int a) {  return ++a;  // 等价于 a = a+1; return a;  
}  
int temp = inc(2);  // temp = 3(a 先自增到 3,再返回)  

4. 指针解引用与内存操作

知识点解析
  • 解引用运算符(*:通过指针访问其指向的变量,格式为 *指针变量
  • 指针传递的本质:传递变量的内存地址,允许函数直接操作该地址上的数据。
举例说明
  • 指针定义与解引用
    int x = 10;  
    int* ptr = &x;  // ptr 存储 x 的地址  
    *ptr = 20;  // 通过指针修改 x 的值,此时 x = 20  
    
  • multi 函数中的指针操作
    int multi(int* a, int* b, int* c) {  // *a 是 a 指向的值(如 temp),*b 是 b 指向的值(如 arg1)  // *c = *a * *b:将乘积存入 c 指向的变量(如 y)  return *c;  
    }  
    

5. 完整代码执行流程(含 main 函数)

#include <stdio.h>  // 1. inc 函数:值传递,前置自增  
int inc(int a) {  return ++a;  // 先自增,再返回新值(3)  
}  // 2. multi 函数:指针传递,计算乘积并存储  
int multi(int* a, int* b, int* c) {  return (*c = *a * *b);  // *c = 3 * 2 = 6,返回 6  
}  // 3. 定义函数指针类型  
typedef int(FUNC1)(int);  
typedef int(FUNC2)(int*, int*, int*);  // 4. show 函数:通过函数指针实现回调  
void show(FUNC2 fun, int arg1, int* arg2) {  FUNC1 p = inc;  // 函数名直接作为指针,无需取地址符 &  int temp = p(arg1);  // 调用 inc(2),temp = 3(arg1 仍为 2,值传递不修改原值)  fun(&temp, &arg1, arg2);  // 调用 multi(&temp, &arg1, &y)  printf("%d\n", *arg2);  // 输出 y 的值:6  
}  // 5. main 函数:程序入口  
int main() {  int x = 2, y;  // x=2(作为 arg1),y 用于存储结果  show(multi, x, &y);  // 传入 multi 函数和参数  return 0;  
}  
分步执行解析
  1. 调用 show(multi, 2, &y)

    • fun 指向 multi 函数,arg1=2(值传递,副本),arg2=&y(指针,指向 y 的地址)。
  2. FUNC1 p = inc;

    • p 是函数指针,指向 inc 函数(无需 &,函数名即地址)。
  3. int temp = p(arg1);

    • 等价于 temp = inc(2)
      • inc 函数内 a=2,执行 ++a 后 a=3,返回 3
      • 由于 arg1 是值传递,外部的 x=2 未被修改,temp=3
  4. fun(&temp, &arg1, arg2);

    • 等价于 multi(&temp, &arg1, &y)
      • *a 是 temp 的值(3),*b 是 arg1 的值(2,因为 arg1 未被修改)。
      • 计算 3 * 2 = 6,通过 *c = 6 将结果存入 y 的内存地址(arg2 指向 y)。
  5. 输出结果

    • y 的值为 6,最终打印 6

易错点与核心区别总结

特性值传递(如 inc 的 a指针传递(如 multi 的 *a
内存操作操作副本,不影响原始变量直接操作原始变量的内存地址
参数传递内容变量的值变量的地址
修改是否影响外部是(通过解引用)

  • 函数指针的核心作用:实现回调机制,使函数可接受不同逻辑的子函数(如 show 函数可传入 multi 或其他符合 FUNC2 类型的函数),提高代码灵活性。

拓展:函数指针在嵌入式开发中的典型应用

1. 回调函数(事件驱动场景)

当外设(如传感器)触发中断时,通过函数指针调用用户自定义的处理函数:

// 定义中断处理函数指针类型  
typedef void (*IRQ_Handler)(void);  // 注册中断时传入回调函数  
void register_irq(IRQ_Handler handler) {  irq_handler = handler;  // 保存函数指针  
}  // 中断发生时调用回调函数  
void irq_triggered() {  irq_handler();  // 调用用户自定义的处理逻辑  
}  // 用户代码:注册自定义处理函数  
void user_irq_process() {  // 处理传感器数据  
}  
register_irq(user_irq_process);  // 注册回调  
2. 状态机设计(简化逻辑跳转)

用函数指针数组表示不同状态的处理逻辑,状态切换时直接调用对应函数:

// 定义状态函数指针类型  
typedef void (*StateFunc)();  // 定义各状态的处理函数  
void state_init();  
void state_run();  
void state_stop();  // 状态机数组  
StateFunc states[] = {state_init, state_run, state_stop};  // 切换状态时调用对应函数  
int current_state = 0;  
states[current_state]();  // 调用初始化状态函数  
3. 注意事项
  • 指针有效性:确保指针指向合法的内存地址,避免野指针(如未初始化的指针)。
  • 类型匹配:函数指针的参数列表和返回值必须与目标函数完全一致,否则会编译报错。

通过本例,不仅能掌握函数指针、指针操作等值传递与指针传递的核心区别,还能理解如何通过这些特性实现灵活的代码架构。这些知识在嵌入式开发的驱动编程、接口设计中至关重要,是理解底层硬件交互的基础。

通过这些题目,深入理解 C 语言的内存管理、作用域、运算符优先级等核心概念,为嵌入式开发打下坚实基础。希望本文能帮助新手系统掌握这些关键知识,在面试中脱颖而出。

相关文章:

  • Flutter 图标和按钮组件
  • 基于RV1126开发板下的WIFI的AP模式配置
  • 【vue3】vue3+express实现图片/pdf等资源文件的下载
  • 埃文科技助力山西公共数据运营新发展
  • 关于 人工智能(AI)发展简史 的详细梳理,按时间阶段划分,涵盖关键里程碑、技术突破、重要人物及挑战
  • QuickAPI 全生命周期管理:从开发到退役的闭环实践​
  • 告别繁琐,拥抱简洁:初识 Pytest 与环境搭建 (Pytest系列之一)
  • vue3 elementPlus中el-tree-select封装和自定义模糊搜索
  • 大数据学习(108)-子查询
  • #苍穹外卖#(day3-4)
  • Verilog的整数除法
  • 【FPGA】——DDS信号发生器设计
  • 单位门户网站被攻击后的安全防护策略
  • P12130 [蓝桥杯 2025 省 B] 移动距离
  • Python Django基于协同过滤算法的招聘信息推荐系统【附源码、文档说明】
  • 深入解析TCP Keep-Alive机制:原理、作用与最佳实践
  • SSM考研助手管理系统
  • 基于MFC 的链接库
  • vue项目打包部署到maven仓库
  • 如何针对游戏、金融行业定制CC攻击防护规则?
  • 山西省援疆前方指挥部总指挥刘鹓已任忻州市委副书记
  • 国家卫健委:坚决反对美国白宫网站翻炒新冠病毒“实验室泄漏”
  • 灰鹦鹉爆粗口三年未改?云南野生动物园:在持续引导
  • 对话地铁读书人|媒体人Echo:读书使人远离“班味”
  • 外交部答澎湃:愿同阿曼在国际和地区事务中加强沟通协调
  • 解密帛书两千年文化传承,《帛书传奇》央视今晚开播