嵌入式软件面试题目

1.c语言和c++的区别?

        C 是面向过程的语言,注重高效的底层编程,提供简洁的语法和手动内存管理(malloc/free),适用于系统软件和嵌入式开发。而 C++ 在 C 的基础上扩展了面向对象编程(类、继承、多态)、泛型编程(模板)和异常处理,引入智能指针管理内存,并提供标准模板库(STL)大幅提升开发效率。C++ 的编译机制支持函数重载和命名空间,语法更灵活但也更复杂,适合开发大型应用、游戏引擎和高性能系统。两者在内存管理、错误处理和编程范式上差异显著,但 C++ 保持了对 C 的兼容性,许多 C 代码可直接在 C++ 环境中编译。

2.什么是虚函数?

        虚函数是C++中通过动态绑定实现多态的机制。当基类的成员函数被声明为virtual时,允许子类(派生类)可以重写其函数来改变其行为。通过基类指针或引用调用虚函数时,实际执行的是运行时对象类型对应的函数版本,而非编译的静态版本。

3.讲一下你对UART,I2C,SPI的了解

        【1】UART:异步通信方式,没有时钟线;IIC和SPI都是同步通信方式,有时钟线;【2】接线的区别:UART(RX、TX),SPI(SCLK时钟线、CS片选引脚、MOSI主机输出从机输入、MISO主机输入从机输出),IIC(SDA数据线,SCL时钟线);【3】设备数量:UART(一对一通信)、IIC(支撑多主机多从机通信)、SPI(一主多从);【4】传输速率:UART(115200等波特率)、IIC(标准模式100kbps、快速模式400kbps、高速模式3.4Mbps)、SPI(一般是几Mbps到几十Mbps);【5】工作模式:UART(全双工、半双工)、IIC(半双工)、SPI(全双工)。

4.串口传输不稳定怎么办?

        串口传输不稳定时,应从硬件、配置、软件三方面排查:硬件上检查信号干扰(如屏蔽线、差分传输)、电源稳定性(共地或隔离)和物理连接可靠性;配置上确保波特率、数据位、停止位和校验位一致;软件层面可添加数据校验(如 CRC)、超时处理、流量控制(RTS/CTS)和重传机制增强可靠性。协议设计上优化帧结构(帧头 / 帧尾 / 长度字段),并通过示波器或逻辑分析仪辅助调试,定位信号失真或时序问题。综合采用这些措施可显著提升串口通信稳定性。

5.线程和进程的区别?

        【1】定义:进程是操作系统中资源分配的最小单元,线程是cpu调度和执行的最小单元;【2】资源占用:每一个进程都有一个独立的内存空间,线程是共享进程的地址空间;【3】容错性:当一个进程出现问题不会影响其他进程的执行,当线程出现问题可能会导致程序崩溃从而影响其他的线程;【4】调度和切换:进程是一个独立的单位,需要恢复上下文内容比较多,消耗资源比较多,线程消耗的资源比较少。

维度进程(Process)线程(Thread)
资源占用独立地址空间、PCB、文件描述符等共享进程的地址空间,仅占独立栈、寄存器
切换开销大(需切换地址空间、刷新 TLB)小(仅切换栈和寄存器)
通信复杂度需 IPC 机制(如共享内存、管道)直接读写共享内存(需同步)
独立性高(一个进程崩溃不影响其他)低(一个线程崩溃可能导致进程崩溃)
核心描述操作系统资源分配的基本单位操作系统调度执行的基本单位

6. 进程间的通信方式?

        常见的进程间通信(IPC)方式有 6 种:

  • 管道(Pipe):半双工,仅用于父子进程或兄弟进程间,通过内核缓冲区传递字节流。
  • 命名管道(FIFO):突破管道的 “亲缘进程” 限制,任何进程可通过文件名访问,支持双向通信。
  • 信号量(Semaphore):用于进程 / 线程间的同步与互斥,本质是 “计数器”,不传递数据,仅控制资源访问。
  • 消息队列(Message Queue):按 “消息类型” 存储数据,进程可按类型读取,避免管道的 “字节流无边界” 问题。
  • 共享内存(Shared Memory):进程间共享同一块物理内存,是最快的 IPC 方式,需配合信号量等机制实现同步。
  • 套接字(Socket):支持跨主机进程通信,是网络通信的基础,也可用于本地进程间通信(如 Unix 域套接字)。

7. 信号量的通信用法?

信号量核心是通过 “P 操作”(申请资源)和 “V 操作”(释放资源)控制进程行为,步骤如下:

  1. 初始化信号量:确定信号量的初始值(如控制 1 个资源时初始值为 1,即互斥信号量)。
    • 例:sem_init(&sem, 0, 1)(第二个参数 0 表示 “线程间共享”,非 0 表示 “进程间共享”)。
  2. 进程 A 申请资源(P 操作):调用sem_wait(&sem),信号量值减 1。
    • 若减后值≥0:进程 A 继续执行,占用资源。
    • 若减后值 < 0:进程 A 阻塞,进入等待队列。
  3. 进程 A 释放资源(V 操作):调用sem_post(&sem),信号量值加 1。
    • 若加后值≤0:唤醒等待队列中一个阻塞的进程(如进程 B),让其占用资源。
  4. 销毁信号量:所有进程使用完后,调用sem_destroy(&sem)释放资源。

8. 什么是共享内存?

  • 定义:操作系统在物理内存中开辟一块 “共享区域”,多个进程通过 “内存映射” 将该区域映射到各自的虚拟地址空间,实现 “直接读写同一块物理内存”。
  • 特点:无需数据拷贝(其他 IPC 如管道需内核 / 用户空间拷贝),是最快的 IPC 方式,但需配合信号量、互斥锁等机制避免 “并发读写冲突”。
  • 用法步骤
    1. shmget()创建 / 获取共享内存标识符;
    2. shmat()将共享内存映射到进程虚拟地址空间;
    3. 进程直接读写映射后的地址;
    4. shmdt()解除映射,shmctl()删除共享内存。

9. 堆和栈的区别?

维度栈(Stack)堆(Heap)
分配方式编译器自动分配 / 释放(如局部变量)程序员手动分配 / 释放(如malloc
生长方向从高地址向低地址生长从低地址向高地址生长
大小限制固定(操作系统预设,如 8MB)灵活(受物理内存和虚拟地址限制)
分配效率高(仅需修改栈指针)低(需遍历空闲链表,可能产生碎片)
存储内容局部变量、函数参数、返回地址动态分配的数据(如数组、结构体)

10. TCP 和 UDP 的区别

维度TCP(传输控制协议)UDP(用户数据报协议)
连接性面向连接(三次握手建立连接,四次挥手断开)无连接(直接发送数据,不确认)
可靠性可靠(重传、确认、流量控制、拥塞控制)不可靠(丢包不重传,无确认)
数据边界无边界(字节流,需应用层定义分隔符)有边界(一个数据报是一个完整单元)
速度慢(连接和控制开销大)快(无额外开销,适合实时场景)
适用场景文件传输、网页加载(如 HTTP)视频通话、游戏、DNS 查询

11. TCP 客户端断开后服务端的表现

分 “正常断开” 和 “异常断开” 两种情况:

  • 正常断开:客户端发送FIN包(四次挥手第一步),服务端:
    1. 收到FIN后,回复ACK包,进入CLOSE_WAIT状态;
    2. 服务端处理完剩余数据后,主动发送FIN包,进入LAST_ACK状态;
    3. 收到客户端ACK后,进入CLOSED状态,释放连接资源。
  • 异常断开(如客户端断电、网络中断):
    1. 服务端未收到FIN包,会一直等待客户端数据,进入 “半连接” 状态;
    2. 若服务端开启了SO_KEEPALIVE(心跳机制),会定期发送探测包:
      • 若多次探测无响应,判定客户端断开,关闭连接,释放资源;
      • 若未开启SO_KEEPALIVE,连接会长期占用端口,导致 “资源泄漏”。

12. UDP 完整的通信流程

UDP 无连接,流程简单,分为客户端和服务端:

  1. 服务端流程
    • 创建 UDP 套接字:socket(AF_INET, SOCK_DGRAM, 0)SOCK_DGRAM表示 UDP 类型);
    • 绑定 IP 和端口:bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr))
    • 接收客户端数据:recvfrom(sockfd, buf, buf_len, 0, (struct sockaddr*)&cli_addr, &cli_len)(同时获取客户端地址);
    • (可选)发送数据给客户端:sendto(sockfd, resp_buf, resp_len, 0, (struct sockaddr*)&cli_addr, cli_len)
    • 关闭套接字:close(sockfd)
  2. 客户端流程
    • 创建 UDP 套接字:socket(AF_INET, SOCK_DGRAM, 0)
    • (无需绑定,系统自动分配临时端口)设置服务端地址;
    • 发送数据给服务端:sendto(sockfd, send_buf, send_len, 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr))
    • (可选)接收服务端响应:recvfrom(sockfd, recv_buf, recv_len, 0, NULL, NULL)
    • 关闭套接字:close(sockfd)

13. 多串口数据的同步性和完整性保证

项目中多串口同时工作时,需从 “接收” 和 “处理” 两方面设计:

  • 保证完整性
    1. 固定数据帧格式:定义 “帧头 + 数据长度 + 数据内容 + 校验位(如 CRC、异或)+ 帧尾”,避免数据粘包 / 丢包;
    2. 超时重传机制:若接收端超时未收到完整帧,向发送端请求重传;
    3. 缓冲区设计:为每个串口分配独立环形缓冲区,避免数据覆盖(如接收中断中仅存数据,主线程解析,减少中断占用时间)。
  • 保证同步性
    1. 时间戳标记:每个串口数据帧携带系统时间戳,后续处理时按时间戳排序;
    2. 信号量 / 互斥锁:多串口数据若需共享处理资源(如同一数据库),用信号量控制访问顺序,避免并发冲突;
    3. 主从同步:指定一个 “主串口” 触发同步信号,其他 “从串口” 收到信号后再发送 / 接收数据。

14. CAN 总线介绍

  • 定义:Controller Area Network(控制器局域网),是一种面向汽车、工业控制的 “串行通信总线”,支持多节点(最多 110 个)、高可靠性。
  • 核心特点
    1. 差分信号传输:用 CAN_H 和 CAN_L 两根线传输差分信号,抗干扰能力强;
    2. 非破坏性总线仲裁:节点发送数据时,ID 越小优先级越高,优先级低的节点自动退出发送,避免冲突;
    3. 错误检测与处理:支持位错误、 CRC 错误等检测,错误节点会自动关闭,不影响总线其他节点。
  • 帧类型:主要分数据帧(传递数据)、远程帧(请求数据)、错误帧(报告错误)、过载帧(通知节点延迟接收)。

15. ADC-DMA 介绍

  • ADC:模拟 - 数字转换器,将传感器输出的模拟信号(如电压、电流)转换为数字信号,供 MCU 处理。
  • DMA:Direct Memory Access(直接内存访问),是一种 “无需 CPU 干预,直接在外设(如 ADC)和内存间传输数据的技术”。
  • ADC-DMA 工作流程
    1. 配置 ADC:设置采样率、通道、触发方式(如定时器触发、软件触发);
    2. 配置 DMA:指定 ADC 数据寄存器(源地址)、内存缓冲区(目的地址)、传输长度、传输方向(外设→内存);
    3. 启动 ADC 和 DMA:ADC 开始采样,每次采样完成后,DMA 自动将数据从 ADC 寄存器搬运到内存缓冲区;
    4. 传输完成中断:DMA 传输完指定长度数据后,触发中断,CPU 再处理内存中的数据(无需全程参与搬运)。

16. 为什么用 DMA?DMA 的问题及解决

  • 用 DMA 的原因
    1. 减轻 CPU 负担:CPU 无需频繁执行 “读取 ADC 数据→存入内存” 的循环,可专注于数据处理、控制逻辑等核心任务;
    2. 提高传输效率:DMA 传输速度快,避免 CPU 上下文切换开销,尤其适合高频采样(如 ADC 每秒采样 10 万次)。
  • DMA 的常见问题及解决
    • 问题 1:ARM 缓存覆盖(你提到的场景):ARM CPU 有缓存(如 L1、L2),若 DMA 将数据写入 “被缓存的内存区域”,CPU 可能读取到缓存中的旧数据(未同步 DMA 的新数据)。
      • 解决:使用 “非缓存内存区域”(如 MCU 中指定的 DMA 专用缓冲区),或在 CPU 读取前执行 “缓存刷新 / 无效化指令”(如__DSB()__ISB()),同步缓存与内存数据。
    • 问题 2:数据传输不完整:DMA 传输中若 CPU 修改目的地址,会导致数据错误。
      • 解决:传输期间禁止 CPU 访问 DMA 目的缓冲区,或用 DMA 传输完成中断作为 “数据可用” 的标志。

17. ARM 架构介绍

  • 定义:Advanced RISC Machine(高级精简指令集机器),是一种基于 RISC 架构的处理器设计方案,广泛用于手机、嵌入式设备、服务器。
  • 核心版本
    1. ARMv7:支持 32 位,分 A(应用处理器,如 Cortex-A9)、R(实时处理器,如 Cortex-R4)、M(微控制器,如 Cortex-M3)三类;
    2. ARMv8:支持 64 位(AArch64 架构)和 32 位(AArch32 兼容模式),如 Cortex-A53、A57(手机 SoC)、Cortex-A72(服务器)。
  • 特点:指令集精简(单周期执行)、低功耗、支持流水线(如三级流水线:取指→译码→执行)、多寄存器设计(31 个通用寄存器)。

18. ARM 寄存器

ARM 架构中寄存器分 “通用寄存器” 和 “状态寄存器”,以 32 位 ARMv7-A 为例:

  • 通用寄存器(共 31 个,32 位):
    • R0~R12:通用数据寄存器,R0 常用于函数返回值,R13 是栈指针(SP),R14 是链接寄存器(LR,存储函数返回地址),R15 是程序计数器(PC,存储下一条执行指令地址);
    • 注:不同模式(如用户模式、内核模式)下,部分寄存器有影子寄存器(如 SP 有用户 SP、内核 SP),切换模式时自动切换。
  • 状态寄存器
    • CPSR:Current Program Status Register(当前程序状态寄存器),存储当前 CPU 状态(如条件标志位 N/Z/C/V、中断禁止位 I/F、模式位 M [4:0]);
    • SPSR:Saved Program Status Register(保存程序状态寄存器),仅在特权模式下存在,用于保存切换模式前的 CPSR 值(如进入中断时,SPSR 保存用户模式的 CPSR)。

19. CPSR 和 SPSR 的区别与使用场景

维度CPSR(当前程序状态寄存器)SPSR(保存程序状态寄存器)
存在模式所有模式都存在仅特权模式(如中断模式、内核模式)存在
作用记录当前 CPU 的运行状态(标志、模式、中断开关)保存 “进入当前模式前” 的 CPSR 值,用于返回原模式
使用场景1. 判断指令执行结果(如 Z 位为 1 表示结果为 0);2. 控制中断(置位 I 位禁止 IRQ 中断);3. 切换 CPU 模式中断处理完成后,将 SPSR 的值恢复到 CPSR,回到原模式(如从中断模式返回用户模式)

20. Uboot 启动过程

Uboot(Universal Bootloader)是嵌入式系统常用的引导程序,核心是 “初始化硬件,加载内核并启动”,步骤如下:

  1. 第一阶段(汇编阶段,依赖硬件)
    • 初始化 CPU 核心:禁用中断、设置 CPU 工作模式(如 SVC 模式)、初始化 Cache(若有);
    • 初始化关键硬件:配置时钟(如 PLL)、内存(DDR)、串口(用于打印日志);
    • 复制 Uboot 第二阶段代码(从 Flash/NAND 到 DDR 内存);
    • 跳转到第二阶段 C 代码(如start_armboot()函数)。
  2. 第二阶段(C 语言阶段,硬件无关)
    • 初始化更多硬件:如网卡、SD 卡、Flash;
    • 初始化环境变量:读取 Flash 中的环境变量(如bootcmdbootargs);
    • 执行自举命令:若bootcmd存在,执行命令(如从 SD 卡读取内核到 DDR 指定地址);
    • 启动内核:通过bootmbootz命令,将内核入口地址和设备树地址传给 CPU,跳转到内核入口(如kernel_entry)。

21. Uboot 引导内核启动的核心步骤

  1. 加载内核镜像:Uboot 通过网卡(TFTP)、SD 卡、Flash 等方式,将内核镜像(如uImagezImage)加载到 DDR 内存的指定地址(如 0x80008000);
  2. 加载设备树(DTS/DTB):将设备树文件(dtb)加载到 DDR 的另一地址(如 0x80800000),设备树用于描述硬件信息(如 CPU、外设地址),内核通过设备树识别硬件;
  3. 设置启动参数(bootargs):Uboot 将bootargs环境变量的值(如 “root=/dev/mmcblk0p2 rw console=ttyS0,115200”)传入内核,内核根据参数挂载根文件系统、配置串口等;
  4. 跳转到内核入口:执行bootm <内核地址> - <设备树地址>命令,Uboot 设置好 CPU 寄存器(如 R0=0,R1 = 机器 ID 或设备树地址,R2 = 内核参数地址),最终跳转到内核入口函数,完成引导。

22. 深拷贝与浅拷贝

  • 浅拷贝:仅复制 “指针变量本身”,不复制指针指向的内存内容,拷贝后两个指针指向同一块内存。
    • 例:int* p1 = malloc(4); int* p2 = p1;(p1 和 p2 都指向同一块 4 字节内存,修改p2 会影响p1);
    • 问题:若两个指针都调用free,会导致 “重复释放”,引发内存错误。
  • 深拷贝:不仅复制指针变量,还复制指针指向的内存内容,拷贝后两个指针指向独立的内存块。
    • 例:int* p1 = malloc(4); *p1 = 10; int* p2 = malloc(4); *p2 = *p1;(p1 和 p2 指向不同内存,修改p2 不影响p1);
    • 适用场景:结构体中包含指针成员(如struct { char* name; }),拷贝时需深拷贝避免内存共享问题。

23. 形参和实参的区别

维度形参(形式参数)实参(实际参数)
定义位置函数定义时的参数列表(如void fun(int a)中的a函数调用时传入的参数(如fun(5)中的5
内存分配函数调用时才分配内存,函数结束后释放调用前已存在(如变量、常量),内存由定义处管理
传递方式C 语言中仅 “值传递”(实参值拷贝给形参,形参修改不影响实参)传递的是 “值”(变量的值、常量值、表达式结果)
示例void add(int x) { x++; }(x 是形参,修改 x 不影响实参)int a=3; add(a);(a 是实参,调用后 a 仍为 3)

24. static 关键字的作用和用法

static 在 C 语言中有 3 种核心用法:

  • 1. 修饰局部变量
    • 作用:改变局部变量的存储位置(从栈→静态数据区),生命周期与程序一致(函数调用结束后不释放,下次调用仍保留上次值);
    • 例:void fun() { static int a=0; a++; printf("%d",a); }(第一次调用输出 1,第二次输出 2,以此类推)。
  • 2. 修饰全局变量
    • 作用:限制全局变量的作用域(仅当前.c 文件可见,其他文件用extern也无法访问);
    • 例:static int g_val=10;(在 A.c 中定义,B.c 无法通过extern int g_val;访问 g_val)。
  • 3. 修饰函数
    • 作用:限制函数的作用域(仅当前.c 文件可见,其他文件无法调用);
    • 例:static void fun() { ... }(A.c 中定义,B.c 无法调用 fun ())。

25. static class 类(C++)

  • 定义:static 修饰的类成员(变量或函数),属于 “类本身”,而非类的实例对象,所有实例共享同一 static 成员。
  • 核心特点
    1. 静态成员变量:需在类外初始化(如int MyClass::static_val=0;),存储在静态数据区;
    2. 静态成员函数:无this指针,只能访问静态成员变量 / 函数,不能访问非静态成员(非静态成员依赖实例对象);
    3. 访问方式:可通过 “类名::成员名”(如MyClass::static_fun())或 “实例对象。成员名” 访问。
  • 示例
    class MyClass {
    public:
        static int count; // 静态成员变量
        static void addCount() { count++; } // 静态成员函数
    };
    int MyClass::count = 0; // 类外初始化
    // 调用
    MyClass::addCount(); // 类名访问
    MyClass obj;
    obj.addCount(); // 实例访问(仍操作同一count)
    

26. 字节对齐的作用及结构体大小计算

  • 字节对齐的作用

    1. 提高 CPU 访问效率:CPU 按 “对齐字节”(如 4 字节、8 字节)读取内存,非对齐数据需多次读取,对齐后一次即可;
    2. 保证硬件兼容性:部分硬件(如 ARM、x86)不支持非对齐访问,强行访问会触发硬件错误。
  • 对齐规则(默认规则,编译器可通过#pragma pack(n)修改):

    1. 结构体成员的偏移量是 “成员自身大小” 的整数倍;
    2. 结构体总大小是 “所有成员中最大大小” 的整数倍。
  • 结构体大小计算示例(默认 4 字节对齐,32 位系统):

    1. 结构体 1:struct A { char a; int b; char c; };
      • a(1 字节):偏移 0(1 的整数倍);
      • b(4 字节):偏移需是 4 的整数倍,故 a 后补 3 字节,b 偏移 4;
      • c(1 字节):偏移 8(1 的整数倍);
      • 总大小:需是 4 的整数倍,c 后补 3 字节,总大小 12 字节。
    2. 结构体 2:struct B { char a; char* b; char c; };(char * 是 4 字节,32 位系统)
      • a 偏移 0,b 偏移 4(补 3 字节),c 偏移 8;
      • 总大小:4 的整数倍,总 12 字节。

27. 多态的底层实现原理(编译器角度)

多态是 C++ 的核心特性,底层通过 “虚函数表(vtable)” 和 “虚表指针(vptr)” 实现,步骤如下:

  1. 编译期
    • 若类中包含虚函数(如virtual void fun()),编译器为该类生成一个 “虚函数表”(vtable),表中存储所有虚函数的地址;
    • 编译器在类的内存布局中,添加一个 “虚表指针(vptr)”(通常在类的第一个位置,占 4/8 字节),vptr 指向该类的 vtable。
  2. 运行期
    • 当创建类的实例对象时,对象的 vptr 会被初始化,指向所属类的 vtable;
    • 当通过 “基类指针 / 引用调用虚函数” 时(如Base* p = new Derived(); p->fun();),CPU 通过 p 指向的对象的 vptr,找到对应的 vtable,再调用 vtable 中存储的虚函数地址(Derived::fun ()),实现 “动态绑定”(运行期确定调用哪个函数)。

28. 函数重载的判定规则

函数重载是指 “同一作用域内,函数名相同但参数不同”,判定规则如下:

  • 1. 传入参数个数不同:会发生重载。
    • 例:void fun(int a)void fun(int a, int b),参数个数不同,属于重载。
  • 2. 返回值不同:不会发生重载。
    • 例:int fun(int a)void fun(int a),仅返回值不同,编译器无法通过调用方式(如fun(5);)区分,会报 “函数重定义” 错误。
  • 核心判定依据:函数的 “参数列表”(参数个数、参数类型、参数顺序),与返回值、函数体无关。

29. 局部变量和全局变量的默认初始化

  • 全局变量(包括静态全局变量、静态局部变量):系统会自动初始化,默认值为 0(或 NULL)。
    • 例:int g_val;(全局变量,默认 0);static int s_val;(静态变量,默认 0);char* p;(全局指针,默认 NULL)。
  • 局部变量(非静态):系统不会自动初始化,初始值是 “随机值”(内存中残留的旧数据)。
    • 例:void fun() { int a; printf("%d",a); }(a 的初始值随机,可能是任意整数);
    • 风险:未初始化的局部变量使用时,会导致程序行为不可预测(如逻辑错误、崩溃),需手动初始化。

30. 死循环的几种写法

死循环是指 “无限执行的循环”,常见写法有 3 种:

  1. while(1) { ... }:最常用,1 表示条件恒为真,循环永不退出;
  2. for(;;) { ... }:for 循环的初始化、条件、增量表达式都省略,条件默认为真,等价于 while (1);
  3. do { ... } while(1);:do-while 循环先执行一次循环体,再判断条件(恒为真),适合 “至少执行一次” 的场景。
  • 注意:死循环中需有退出逻辑(如break语句),否则程序会一直占用 CPU,无法退出。

31. 左值和右值的区别

  • 左值(Lvalue):指 “可以放在赋值运算符左边的表达式”,代表 “一块可修改的内存地址”,有持久性。
    • 例:变量(int a; a=5;,a 是左值)、数组元素(arr[0]=10;,arr [0] 是左值)、解引用指针(*p=3;,*p 是左值)。
  • 右值(Rvalue):指 “只能放在赋值运算符右边的表达式”,代表 “一个临时值”,无持久性(使用后会销毁)。
    • 例:常量(a=5;,5 是右值)、表达式结果(a=3+2;,3+2 是右值)、函数返回值(a=fun();,若 fun () 返回 int,返回值是右值)。
  • 核心区别:左值有可修改的内存地址,右值是临时值,不可修改(如5=3;会报错)。

32. 数组名和指针的区别

数组名和指针在某些场景下表现相似(如都可通过[]*访问元素),但本质不同:

维度数组名(如int arr[5]指针(如int* p
本质数组首元素的 “常量地址”(不可修改)变量(存储地址,可修改指向)
大小计算sizeof(arr)返回数组总字节数(如 5*4=20)sizeof(p)返回指针大小(32 位 4 字节,64 位 8 字节)
赋值操作不可赋值(如arr=p;报错,arr 是常量地址)可赋值(如p=arr;,p 指向数组首元素)
自增 / 自减不可自增(如arr++报错,常量地址不能修改)可自增(如p++,p 指向数组下一个元素)
示例int arr[5]; arr[0]=1;(arr 是常量地址)int* p=arr; p[0]=1; p++;(p 是变量指针)

33.汇编的常用参数(以 ARM 汇编为例)?

汇编中的 “参数” 主要指 “寄存器参数” 和 “栈参数”,ARM 汇编遵循 ATPCS(ARM-Thumb Procedure Call Standard)调用规范,常用参数传递方式:

  1. 寄存器参数(优先使用,效率高):
    • R0~R3:用于传递函数前 4 个参数,超出 4 个的参数通过栈传递;
    • 例:调用fun(a,b,c,d)时,a→R0,b→R1,c→R2,d→R3;
    • 返回值:函数返回值通过 R0 传递(如返回 int 时,R0 存返回值;返回指针时,R0 存指针地址)。
  2. 栈参数(参数个数 > 4 时使用):
    • 超出 4 个的参数,按 “从右到左” 的顺序压入栈中;
    • 例:调用fun(a,b,c,d,e)时,先压 e 到栈,再压 d(但 d 实际用 R3 传递,此处仅 e 用栈);
    • 栈清理:通常由调用者清理栈(ATPCS 规范),避免内存泄漏。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小程同学>o<

你的鼓励将是小程创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值