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

1042

积分

0

好友

152

主题
发表于 15 小时前 | 查看: 2| 回复: 0

在嵌入式软件开发岗位的面试中,C语言基础是必考的核心环节。本文将深入解析30个经典且高频的C语言面试问题,涵盖从关键字特性到内存模型的各个方面,旨在帮助开发者系统性地梳理知识,从容应对技术考察。

1. C语言中static关键字的作用

static关键字根据其修饰对象的不同,具有不同的语义:

  • 修饰局部变量:使变量具有静态存储期。该变量在程序运行期间只初始化一次,内存不会随函数调用结束而释放,其值会得以保留。
  • 修饰全局变量:将变量的作用域限制在定义它的源文件内,使其成为该文件的“私有”全局变量,防止被其他文件访问。
  • 修饰函数:将函数的作用域限制在定义它的源文件内,使其成为该文件的内部函数,对外部文件不可见。

2. extern关键字的作用

extern用于声明(而非定义)一个在其他文件中已定义的全局变量或函数。它告知编译器该标识符的存在,链接器会在其他模块中寻找其定义。这是实现多文件编程中共享全局变量和函数的常用手段。

3. const与#define的区别

  • const:用于定义一个只读变量,具备类型安全检查,且在内存中拥有存储空间。它可用于修饰普通变量、指针、函数参数和返回值等。
  • #define:是预处理指令,用于宏替换,属于文本层面的替换,不进行类型检查,也无内存分配。

结论:在定义常量时,const因其类型安全性和更符合现代C/C++编程规范而更受推荐。

4. 字节对齐及其作用

字节对齐是指数据在内存中存放时,其起始地址必须是某个值(通常是其自身类型大小或编译器指定对齐模数)的整数倍。
作用:现代CPU通常以对齐的方式访问内存效率更高。不对齐的数据访问可能导致性能下降,甚至在部分硬件平台上引发硬件异常。合理使用对齐可以优化内存访问速度,减少CPU访存周期。例如:

struct Example {
    char a;     // 占用1字节
    // 编译器通常会插入3字节填充(padding)
    int b;      // 对齐到4字节边界
};

5. struct与union的区别及应用场景

  • struct(结构体):每个成员拥有独立的内存空间,结构体总大小至少为所有成员大小之和(考虑对齐)。
  • union(联合体):所有成员共享同一段内存,其大小由最大成员决定,任一时刻只有一个成员有效。

union应用场景

  1. 协议解析:当同一段内存需要根据不同的协议类型被解释为不同的数据结构时。
  2. 类型转换/节省内存:需要在几种类型间切换,且同一时刻只使用一种类型时,可以有效节省内存。
    union Data {
    int i;
    float f;
    char str[20];
    };

6. volatile关键字的使用场景

volatile告知编译器该变量的值可能被程序之外的未知因素(如硬件寄存器、多线程共享变量、信号处理函数)改变。编译器会禁止对此变量进行某些可能影响其可见性的优化(如将变量值缓存在寄存器中),确保每次访问都从内存中读取最新值。在嵌入式系统的硬件编程中尤为重要。

7. inline内联函数的特性

inline是一个建议性关键字,提示编译器尝试将函数调用处用函数体本身替换,以消除函数调用开销(压栈、跳转、返回)。
特点:适用于短小、频繁调用的函数。但编译器有权决定是否真正内联,对于复杂的函数,内联可能导致代码膨胀,反而降低性能。

8. 内存泄漏

内存泄漏是指程序动态申请(如使用malloc, calloc)的内存,在使用完毕后未能正确释放(使用free)。这会导致可用内存逐渐减少,长期运行可能引发程序崩溃或系统性能下降。

9. 大小端模式及判断方法

  • 大端模式:数据的高字节保存在内存的低地址处。
  • 小端模式:数据的低字节保存在内存的低地址处。
    判断方法(利用联合体):
    #include <stdio.h>
    int checkEndian() {
    union {
        int i;
        char c;
    } u;
    u.i = 1;
    return (u.c == 1); // 返回1为小端,0为大端
    }

10. typedef与#define的区别

  • typedef:为已有的数据类型创建一个新的别名,发生在编译阶段,有类型检查。
  • #define:进行简单的文本替换,发生在预处理阶段,无类型检查。
    结论typedef在创建类型别名时更为安全可靠。

11. if-else与switch-case的选择

  • if-else:适用于条件判断复杂、区间判断或条件数量不固定的情况。
  • switch-case:适用于对同一变量进行多个离散值(整型或枚举)的等值判断,结构更清晰,可读性更好。

12. 值传递与地址传递

  • 值传递:函数接收参数的一个副本。在函数内修改形参,不会影响调用处的实参。
  • 地址传递:通过指针传递变量的地址。函数内通过指针可以修改原变量的值。

13. 常用的位操作宏

嵌入式开发中,对寄存器或标志位的操作常涉及位运算。

#define SET_BIT(value, n)    ((value) | (1U << (n)))    // 将第n位置1
#define CLEAR_BIT(value, n)  ((value) & ~(1U << (n)))   // 将第n位置0
#define TOGGLE_BIT(value, n) ((value) ^ (1U << (n)))    // 将第n位取反
#define CHECK_BIT(value, n)  (((value) >> (n)) & 1U)    // 检查第n位是否为1

注意:使用1U(无符号整型)可避免对负值左移的未定义行为。

14. 枚举(enum)

枚举用于定义一组相关的具名整型常量,提高代码的可读性和可维护性。

enum Week { MON = 1, TUE, WED, THU, FRI, SAT, SUN };

15. sizeof与strlen的区别

  • sizeof运算符,计算数据类型或变量所占用的内存字节数,包括字符串末尾的\0
  • strlen库函数,计算字符串\0之前的字符个数。

16. 指针与数据的关系

指针是一个变量,其存储的值是另一个变量的内存地址。通过指针(解引用操作*),可以间接访问和操作该地址上存储的数据。指针是C语言实现高效内存操作和数据结构的基石。

17. 函数指针及应用

函数指针是指向函数的指针变量。它存储函数的入口地址。
应用场景

  1. 回调函数:将函数指针作为参数传递给另一个函数,实现灵活的调用策略。
  2. 函数表/跳转表:将多个函数指针放入数组,通过索引动态调用,常用于状态机或命令解析。
    int (*funcPtr)(int, int); // 声明一个函数指针
    funcPtr = &add; // 指向add函数
    int result = (*funcPtr)(3, 4); // 通过指针调用函数

18. C程序的编译过程

  1. 预处理:处理#开头的指令(如#include, #define),进行宏替换、文件包含,生成.i文件。
  2. 编译:进行语法和语义分析、优化,将预处理后的代码翻译成汇编代码(.s文件)。
  3. 汇编:将汇编代码转换成机器指令,生成目标文件(.o.obj文件)。
  4. 链接:将多个目标文件以及所需的库文件链接在一起,解析符号引用,生成最终的可执行文件。

19. 堆(Heap)与栈(Stack)的区别

  • :由程序员手动管理(malloc/free, new/delete)。申请空间较大,分配速度相对慢,使用不当易产生内存泄漏或碎片。
  • :由编译器自动管理。用于存放局部变量、函数参数和调用上下文。分配释放速度快,空间有限,函数结束自动回收。

20. #error预处理指令

#error用于在预处理阶段强制产生一个编译错误,并输出自定义的错误信息。常用于条件编译中,检查不满足的编译条件。

#ifndef __cplusplus
#error This project requires a C++ compiler.
#endif

21. 可变参数函数

可变参数函数允许传入不定数量的参数,如printf。需使用<stdarg.h>中的宏:

  • va_list:定义参数列表变量。
  • va_start:初始化参数列表。
  • va_arg:获取下一个参数。
  • va_end:清理参数列表。
    #include <stdarg.h>
    int sum(int count, ...) {
    va_list args;
    va_start(args, count);
    int total = 0;
    for(int i=0; i<count; i++) {
        total += va_arg(args, int);
    }
    va_end(args);
    return total;
    }

22. 递归函数

递归函数直接或间接调用自身。它通常包含基线条件(终止条件)和递归步骤
应用:树/图的遍历(如二叉树遍历)、分治算法(如快速排序、归并排序)、解决具有自相似性的问题(如汉诺塔、斐波那契数列)。

23. 哈希表

哈希表(散列表)通过哈希函数将键(Key)映射到表中的一个位置来访问记录,从而实现接近O(1)时间复杂度的查找、插入和删除。
应用:编译器符号表、数据库索引、缓存系统(如Redis)、拼写检查器等。

24. 函数指针与指针函数

  • 函数指针:本质是指针,指向一个函数。如:int (*pFunc)(int);
  • 指针函数:本质是函数,返回一个指针。如:int* func(int);

25. 野指针及其避免

野指针是指向“垃圾”内存或已释放内存的指针。访问野指针会导致未定义行为(程序崩溃、数据错误)。
避免方法

  1. 指针定义时立即初始化为NULL
  2. 指针指向的内存被freedelete后,立即将指针置为NULL
  3. 避免返回局部变量的地址。
  4. 确保指针在其有效作用域内使用。

26. 安全的MIN/MAX宏定义

定义宏时,参数需用括号包裹,以避免运算符优先级导致的错误。

#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
// 使用示例
int a=10, b=20;
int min_val = MIN(a, b); // 10

27. 预处理操作符#和

  • #(字符串化):将宏参数转换为字符串常量。
    #define STRINGIFY(x) #x
    printf(STRINGIFY(hello)); // 输出 "hello"
  • ##(连接):将两个标记(token)连接成一个新的标记。
    #define CONCAT(a, b) a##b
    int xy = CONCAT(3, 5); // 等价于 int xy = 35;

28. 判断浮点数是否为零(或接近零)

由于浮点数存在精度误差,不能直接用==与0比较。应判断其绝对值是否小于一个极小的阈值(epsilon)。

#include <math.h>
#define EPSILON 1e-6
float f = 0.0f;
if (fabs(f) < EPSILON) {
    // f可以认为是0
}

29. 如何避免内存泄漏

  1. 配对管理:确保每次malloc都有对应的freenew有对应的delete
  2. 所有权清晰:明确代码中哪部分负责内存的释放。
  3. 使用智能指针(C++):如std::unique_ptr, std::shared_ptr,利用RAII机制自动管理内存。
  4. 静态分析工具:使用Valgrind、Clang Static Analyzer等工具检测内存问题。

30. 交换两个变量的方法

  1. 使用临时变量(最安全、最通用):
    void swap(int *a, int *b) {
        int temp = *a;
        *a = *b;
        *b = temp;
    }
  2. 不使用临时变量(位运算):适用于整型,利用异或^的特性。
    void swap(int *a, int *b) {
        *a = *a ^ *b;
        *b = *a ^ *b; // 等价于 (*a ^ *b) ^ *b = *a
        *a = *a ^ *b; // 等价于 (*a ^ *b) ^ *a = *b
    }
  3. 不使用临时变量(加减法):可能发生溢出,不推荐。
    void swap(int *a, int *b) {
        *a = *a + *b;
        *b = *a - *b;
        *a = *a - *b;
    }

    掌握以上C语言核心知识点,是构建扎实嵌入式开发技能的第一步。在理解原理的基础上,通过实际编码和数据结构与算法的应用练习,方能融会贯通,应对复杂的系统开发挑战。




上一篇:龙芯中科首款GPGPU 9A1000交付流片,集成图形与AI,定位入门级独显
下一篇:iPhone 20设计前瞻:真全面屏与四曲弯折OLED面板技术解析
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2025-12-17 16:31 , Processed in 0.154222 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

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