【面试笔记】嵌入式软件工程师,汽车电子软件相关

文章目录

  • [1. C语言基础](#1. C语言基础)
    • [1.1 const](#1.1 const)
    • [1.2 static](#1.2 static)
    • [1.3 回调函数的用法](#1.3 回调函数的用法)
    • [1.4 宏定义](#1.4 宏定义)
    • [1.5 编译、链接过程](#1.5 编译、链接过程)
    • [1.6 堆与栈的区别?](#1.6 堆与栈的区别?)
    • [1.7 简单的字符串算法题,C语言实现](#1.7 简单的字符串算法题,C语言实现)
      • [1.7.1 给定一个字符串,按顺序筛选出不重复的字符组成字符串,输出该字符串](#1.7.1 给定一个字符串,按顺序筛选出不重复的字符组成字符串,输出该字符串)
      • [1.7.2 给定4*4矩阵,回文打印输出](#1.7.2 给定4*4矩阵,回文打印输出)
    • [1.8 字节对其](#1.8 字节对其)
  • [2. MCU相关](#2. MCU相关)
    • [2.1 MCU的启动过程描述](#2.1 MCU的启动过程描述)
    • [2.2 MCU的内存布局](#2.2 MCU的内存布局)
    • [2.3 使用volatile关键字的作用?](#2.3 使用volatile关键字的作用?)
  • [3. 汽车电子软件](#3. 汽车电子软件)
    • [3.1 CAN/CANFD相关](#3.1 CAN/CANFD相关)
      • [3.1 概述一个CAN消息如何被发送和接收的](#3.1 概述一个CAN消息如何被发送和接收的)
      • [3.2 CAN和FIFO CAN](#3.2 CAN和FIFO CAN)
      • [3.3 CANFD的知识点](#3.3 CANFD的知识点)
    • [3.2 概述bootloader实现要点](#3.2 概述bootloader实现要点)
      • [3.2.1 跳转前要做什么?](#3.2.1 跳转前要做什么?)
    • [3.3 简述ASPICE在项目研发中的应用](#3.3 简述ASPICE在项目研发中的应用)
    • [3.4 举例说明某个功能安全需求的实现过程](#3.4 举例说明某个功能安全需求的实现过程)
    • [3.5 概述14229协议](#3.5 概述14229协议)
    • [3.6 概述15765协议](#3.6 概述15765协议)

1. C语言基础

1.1 const

修饰变量

只可访问,不可重新赋值。

c 复制代码
const int MAX_VALUE = 100;

void printValue(const int value);

修饰指针

  • 限制指向位置
c 复制代码
const int *ptr;
  • 限制指向数据
c 复制代码
const int *const ptr;

1.2 static

静态变量

使用 static 关键字声明静态变量时,变量的生命周期会延长到整个程序运行期间,而不仅仅局限在其定义的作用域内。静态变量在第一次被赋值时初始化,并且保留其值直到程序结束。

静态全局变量

使用 static 关键字在全局作用域中声明的变量具有静态存储持续时间,但是其作用域被限制在声明该变量的源文件内。这使得该变量对其他源文件不可见,可以防止命名冲突。

静态函数

使用 static 关键字声明静态函数时,该函数仅在声明所在的源文件中可见,即它具有内部链接性。静态函数的作用域仅限于声明所在的源文件。这种方式可以避免与其他源文件中的同名函数产生冲突。


1.3 回调函数的用法

用于在函数执行过程中调用另一个函数。回调函数允许我们向一个函数传递另一个函数的地址,从而在需要时执行特定的操作。回调函数通常用于事件处理、异步编程、库函数的扩展等场景。

  1. 定义回调函数
    首先定义一个函数作为回调函数,其函数原型应与回调的要求相匹配。例如:
c 复制代码
void callbackFunction(int result) {
    printf("Callback result: %d\n", result);
}
  1. 在函数中注册回调函数
    在需要的地方将回调函数注册进目标函数中,通常通过函数指针实现。例如:
c 复制代码
void performOperation(void (*callback)(int)) {
    int result = 100; // 模拟操作结果

    // 执行操作...

    // 调用回调函数
    callback(result);
}
  1. 调用包含回调函数的函数
    最后调用包含回调函数的函数,将回调函数的地址传递给要调用的函数。例如:
c 复制代码
int main() {
    performOperation(callbackFunction); // 注册回调函数
    return 0;
}

在这个示例中,performOperation 函数执行某个操作后调用了注册的回调函数 callbackFunction,并将结果传递给回调函数进行处理。

通过回调函数,我们可以实现灵活的程序设计,允许函数根据不同情况来调用不同的操作,增加了程序的可扩展性和可重用性。当需要在函数执行过程中动态切换功能时,回调函数是一个非常有用的工具。

使用回调函数有以下一些好处:

  1. 灵活性和可扩展性
    回调函数提供了一种灵活的机制,使得代码可以在不同的场景中进行定制和扩展。通过将特定的功能封装在回调函数中,可以根据需要动态地更改或添加行为,而无需修改主函数的逻辑。
  2. 解耦和模块化
    回调函数有助于将不同的功能模块分离,使代码更具有模块化和可维护性。主函数可以专注于其核心逻辑,而将特定的任务委托给回调函数来处理。这样可以提高代码的可读性和可重用性。
  3. 异步处理和事件驱动
    回调函数常用于异步操作或事件驱动的场景中。例如,在异步 I/O 操作完成或特定事件发生时,可以通过回调函数来处理相应的逻辑。这有助于提高程序的并发性和响应性。
  4. 定制性和扩展性
    回调函数允许用户提供自己的自定义逻辑,以满足特定的需求。这使得程序可以更好地适应各种不同的用例和业务逻辑。
  5. 代码复用
    回调函数可以作为可复用的模块,在多个地方被调用,从而减少代码冗余。

需要注意的是,在使用回调函数时,要确保正确处理回调函数的参数和返回值,并注意线程安全等问题。合理使用回调函数可以提高代码的灵活性和扩展性,但也需要谨慎设计和管理,以避免引入复杂度过高或难以调试的问题。


1.4 宏定义

在 C 语言中,宏定义是一种预处理器指令,用于在编译阶段进行文本替换。它允许你定义一个标识符(通常是一个宏名),并将其与一个特定的文本表达式或代码块关联起来。当在代码中使用该宏名时,编译器会将其替换为相应的文本。

宏定义的常见用法和好处包括:

  1. 常量定义
    使用宏定义可以创建常量,例如定义一些具有特定值的常量,以增强代码的可读性和可维护性。
  2. 代码简化和抽象
    宏定义可以用于简化复杂的表达式或代码块,使其更易于阅读和理解。例如,将常用的计算或操作封装在宏中,以便在多个地方重复使用。
  3. 条件编译
    通过宏定义可以实现条件编译,根据不同的条件编译不同的代码块。这对于处理不同平台、版本或配置的情况非常有用。
  4. 代码移植性
    宏定义可以帮助提高代码的可移植性。例如,可以使用宏来定义平台特定的代码或处理不同编译器的差异。
  5. 提高性能
    在一些情况下,宏定义可以提供一定的性能优势,特别是对于一些简单的计算或操作。
    例如,以下是一个简单的宏定义示例:
c 复制代码
#define MAX_SIZE 100

在上面的示例中,MAX_SIZE 是一个宏名,100 是它关联的文本。在代码中使用 MAX_SIZE 时,它将被替换为 100。

需要注意的是,宏定义也有一些潜在的问题和限制:

  1. 宏展开问题
    宏在编译时会进行文本替换,可能会导致一些意外的副作用,例如嵌套宏展开、参数求值顺序等问题。
  2. 缺乏类型检查
    宏不进行类型检查,可能会导致在使用时出现类型不匹配或其他错误。
  3. 可读性问题
    过度使用宏可能会使代码变得难以理解,特别是当宏的定义和使用变得复杂时。

因此,在使用宏定义时,应该谨慎考虑,并确保其使用不会导致代码的可读性和可维护性下降。在一些情况下,使用函数或其他语言特性可能是更好的选择。


1.5 编译、链接过程


预处理

根据以字符#开头的命令修饰的main.c的C源文件,生成预处理后的C源文件 main.i。

该过程主要进行文本替换、宏展开、删除注释等工作。

对应的gcc命令:

bash 复制代码
gcc -E main.c main.i

编译

编译器将文本文件main.i翻译(编译)成汇编文件main.s

对应的gcc命令:

bash 复制代码
gcc -S main.i mian.s

汇编

编译器将main.s翻译成机器语言指令,并把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件main.o中

把一个源文件翻译成目标程序的工作过程分为五个阶段:词法分析、语法分析、语义检查和中间代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现语法错误并给出提示信息。

对应的gcc命令:

bash 复制代码
gcc -c main.s mian.o

链接

该过程编译器将静态库和动态库的库函数链接到可执行程序中。

静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时就不在需要库文件了,其后缀一般为.a。

动态库则是在程序运行时被链接加载,这样可以节省系统的开销,其后缀一般为.so,gcc在编译时默认使用动态库。


1.6 堆与栈的区别?

  1. 栈空间是系统自动分配和回收,堆的空间是用户手动分配回收的;
  2. 栈空间较小,堆空间较大;
  3. 栈的地址空间向下生长,堆则向上生长;
  4. 栈的存储效率更高。
    参考:栈和堆,以STM32为例说明

1.7 简单的字符串算法题,C语言实现

1.7.1 给定一个字符串,按顺序筛选出不重复的字符组成字符串,输出该字符串

参考示例:

c 复制代码
#include <stdio.h>
#include <string.h>

void removeDuplicates(char *str) {
    int len = strlen(str);
    if (len < 2) return;

    int tail = 1;
    for (int i = 1; i < len; ++i) {
        int j;
        for (j = 0; j < tail; ++j) {
            if (str[i] == str[j]) break;
        }
        if (j == tail) {
            str[tail] = str[i];
            ++tail;
        }
    }
    str[tail] = '\0'; //此处是关键
}

int main() {
    char str[100];
    printf("Enter a string: ");
    scanf("%s", str);

    removeDuplicates(str);

    printf("String with duplicates removed: %s\n", str);

    return 0;
}

测试结果:

bash 复制代码
Enter a string: asbdssjikSNjs78137!@#ssa00smk
String with duplicates removed: asbdjikSN7813!@#0m

1.7.2 给定4*4矩阵,回文打印输出

参考示例:

c 复制代码
#include <stdio.h>

#define ROWS 4
#define COLS 4

void printClockwise(int matrix[ROWS][COLS]) {
    int top = 0, bottom = ROWS - 1, left = 0, right = COLS - 1;

    while (top <= bottom && left <= right) {
        // Print top row
        for (int i = left; i <= right; ++i)
            printf("%d ", matrix[top][i]);
        top++;

        // Print right column
        for (int i = top; i <= bottom; ++i)
            printf("%d ", matrix[i][right]);
        right--;

        // Print bottom row
        if (top <= bottom) {
            for (int i = right; i >= left; --i)
                printf("%d ", matrix[bottom][i]);
            bottom--;
        }

        // Print left column
        if (left <= right) {
            for (int i = bottom; i >= top; --i)
                printf("%d ", matrix[i][left]);
            left++;
        }
    }
}

int main() {
    int matrix[ROWS][COLS] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
        {13, 14, 15, 16}
    };

    printf("Clockwise printing of the matrix:\n");
    printClockwise(matrix);

    return 0;
}

测试结果:

bash 复制代码
Clockwise printing of the matrix:
1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10

参考:算法11:顺时针转圈打印矩阵


1.8 字节对其

问题 】32位系统,一个结构体中,成员依次是char、short、int、char类型,问这个结构体总共占多少字节?

回答这个问题需要深刻理解结构体所占空间的分布:

bash 复制代码
|char |-----|short|short|4字节
|int  |int  |int  |int  |4字节
|char |-----|-----|-----|4字节

所以,该结构体共占12字节

测试代码:

C 复制代码
#include <stdio.h>

struct TMP{
    char a;
    short b;
    int c;
    char d;
};

int main(void) {
    printf("size = %d", sizeof(struct TMP));
    return 0;
}

总结:

  • 结构体成员占位是其自身类型长度的整数倍
  • 结构体整体需要对齐,目标对齐长度的整数倍

2. MCU相关

2.1 MCU的启动过程描述

参考STM32的启动过程 --- startup_xxxx.s文件解析(MDK和GCC双环境)


2.2 MCU的内存布局


参考:

  1. 内存布局:深度剖析应用程序中的内存布局
  2. stm32的内存分布

2.3 使用volatile关键字的作用?

  1. 硬件寄存器操作
    单片机通常与硬件设备交互,硬件寄存器的值可能会在硬件事件的触发下发生改变。通过将访问硬件寄存器的变量声明为 volatile,可以告诉编译器不要对该变量进行优化,以确保每次访问都能获取到最新的寄存器值。
  2. 共享变量
    在多线程或中断处理程序中,多个执行路径可能同时访问和修改同一个变量。将这样的共享变量声明为 volatile,可以确保编译器生成的代码正确地处理变量的读和写,避免出现竞态条件等问题。
  3. 中断服务程序
    中断服务程序可能会修改一些全局变量,而这些变量在主程序中也会被访问。将这些变量声明为 volatile,可以保证中断服务程序对变量的修改能及时反映到主程序中。
  4. 实时性要求高的代码
    在一些对实时性要求较高的场景中,使用 volatile 可以确保关键变量的访问不会被编译器优化掉,从而保证代码的实时性。

通过使用 volatile,可以帮助编译器生成更准确的代码,避免一些由于变量的不确定性导致的问题。然而,具体的应用场景和使用方法可能会因单片机的类型、编译器的特性以及项目的需求而有所不同。在实际编程中,还需要根据具体情况进行适当的测试和调试。


3. 汽车电子软件

3.1 CAN/CANFD相关

3.1 概述一个CAN消息如何被发送和接收的

TBD.


3.2 CAN和FIFO CAN

TBD.


3.3 CANFD的知识点

TBD.


3.2 概述bootloader实现要点

3.2.1 跳转前要做什么?

  • 禁止所有外设时钟;
  • 禁止使用的 PLL;
  • 禁止所有中断;
  • 清除所有中断挂起标志。

3.3 简述ASPICE在项目研发中的应用

TBD.


3.4 举例说明某个功能安全需求的实现过程

TBD.


3.5 概述14229协议

TBD.


3.6 概述15765协议

TBD.

相关推荐
Y.O.U..1 小时前
美团AI面试总结
网络·面试·职场和发展
ylfhpy4 小时前
Java面试黄金宝典1
java·开发语言·算法·面试·职场和发展
我是大咖4 小时前
c语言笔记 结构体内嵌套结构体的表示方式
笔记
每次的天空6 小时前
Android第四次面试总结(基础算法篇)
android·算法·面试
海姐软件测试6 小时前
接口和压测工具都有哪些,是如何选择的?
测试工具·面试·职场和发展
Long_poem6 小时前
【自学笔记】MongoDB基础知识点总览-持续更新
数据库·笔记·mongodb
拉不动的猪7 小时前
刷刷题39(同一组件中的不同的标签页如何实现通信)
前端·javascript·面试
拉不动的猪7 小时前
刷刷题37(vue3的优化点)
前端·javascript·面试
uhakadotcom7 小时前
使用阿里云PyODPS3和MaxFrame构建高效本地开发环境
后端·面试·github