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

嵌入式面试高频(八)!!!C++语言(嵌入式八股文,嵌入式面经)

25届应届毕业生 会持续更新的,大家关注一下 谢谢!!!

一.为什么使用内联函数?需要注意什么?

内联函数的优势

内联函数通过将函数体直接插入调用处来减少函数调用的开销,避免了压栈、跳转和返回等操作,尤其适合频繁调用的小型函数。

  • 性能提升:消除函数调用开销,适合简单且频繁调用的场景(如循环内的操作)。
  • 避免宏的缺陷:相比宏,内联函数支持类型检查、调试和重载,更安全。
  • 编译器优化提示:建议编译器内联展开,但最终由编译器决定是否优化。

使用注意事项

  • 函数体规模:仅适用于短小函数(通常1-5行),复杂函数内联可能导致代码膨胀。
  • 递归与虚函数:递归函数无法内联;虚函数动态绑定,内联可能失效。
  • 调试限制:某些调试器无法跟踪内联展开后的代码。

示例代码

inline int add(int a, int b) { return a + b; 
}
// 调用时可能直接展开为 `result = x + y`

编译器行为差异

  • 显式与隐式内联inline 关键字仅为建议,编译器可能忽略;类内定义的成员函数默认隐式内联。
  • 跨模块可见性:头文件中定义内联函数需确保所有调用单元可见,避免链接错误。

合理使用内联函数可提升性能,但需权衡代码膨胀风险。

内联函数的优势

内联函数通过将函数体直接插入调用处来减少函数调用的开销,避免了压栈、跳转和返回等操作,尤其适合频繁调用的小型函数。

  • 性能提升:消除函数调用开销,适合简单且频繁调用的场景(如循环内的操作)。
  • 避免宏的缺陷:相比宏,内联函数支持类型检查、调试和重载,更安全。
  • 编译器优化提示:建议编译器内联展开,但最终由编译器决定是否优化。

使用注意事项

  • 函数体规模:仅适用于短小函数(通常1-5行),复杂函数内联可能导致代码膨胀。
  • 递归与虚函数:递归函数无法内联;虚函数动态绑定,内联可能失效。
  • 调试限制:某些调试器无法跟踪内联展开后的代码。

示例代码

inline int add(int a, int b) { return a + b; 
}
// 调用时可能直接展开为 `result = x + y`

编译器行为差异

  • 显式与隐式内联inline 关键字仅为建议,编译器可能忽略;类内定义的成员函数默认隐式内联。
  • 跨模块可见性:头文件中定义内联函数需确保所有调用单元可见,避免链接错误。

合理使用内联函数可提升性能,但需权衡代码膨胀风险。

内联函数的优势

内联函数通过将函数体直接插入调用处来减少函数调用的开销,避免了压栈、跳转和返回等操作,尤其适合频繁调用的小型函数。

  • 性能提升:消除函数调用开销,适合简单且频繁调用的场景(如循环内的操作)。
  • 避免宏的缺陷:相比宏,内联函数支持类型检查、调试和重载,更安全。
  • 编译器优化提示:建议编译器内联展开,但最终由编译器决定是否优化。

使用注意事项

  • 函数体规模:仅适用于短小函数(通常1-5行),复杂函数内联可能导致代码膨胀。
  • 递归与虚函数:递归函数无法内联;虚函数动态绑定,内联可能失效。
  • 调试限制:某些调试器无法跟踪内联展开后的代码。

示例代码

inline int add(int a, int b) { return a + b; 
}
// 调用时可能直接展开为 `result = x + y`

编译器行为差异

  • 显式与隐式内联inline 关键字仅为建议,编译器可能忽略;类内定义的成员函数默认隐式内联。
  • 跨模块可见性:头文件中定义内联函数需确保所有调用单元可见,避免链接错误。

合理使用内联函数可提升性能,但需权衡代码膨胀风险。

二.C++从代码到可执行二进制文件的过程

编译过程概述

C++代码从源代码到可执行二进制文件通常需要经历四个主要阶段:预处理、编译、汇编和链接。每个阶段由不同的工具处理,最终生成可执行文件。

预处理阶段

预处理阶段由预处理器(如cpp)完成。主要任务包括:

  • 处理所有以#开头的指令(如#include#define#ifdef等)
  • 展开宏定义
  • 包含头文件内容
  • 删除注释
  • 生成.i.ii预处理文件

示例命令:

g++ -E main.cpp -o main.i

编译阶段

编译器(如g++)将预处理后的代码翻译成汇编代码。主要任务包括:

  • 语法和语义分析
  • 生成中间代码
  • 代码优化
  • 生成平台相关的汇编代码(.s文件)

示例命令:

g++ -S main.i -o main.s

汇编阶段

汇编器(如as)将汇编代码转换为机器码,生成目标文件(.o.obj)。主要任务包括:

  • 将汇编指令翻译为机器指令
  • 生成可重定位目标文件
  • 包含符号表信息

示例命令:

g++ -c main.s -o main.o

链接阶段

链接器(如ld)将多个目标文件和库文件合并为可执行文件。主要任务包括:

  • 符号解析(解决未定义符号)
  • 重定位(调整地址引用)
  • 合并不同目标文件
  • 链接静态库
  • 处理动态库依赖
  • 生成可执行文件(如.exe或ELF格式)

示例命令:

g++ main.o -o main

现代编译器优化

现代编译器(如GCC、Clang)通常将这些步骤合并执行:

g++ main.cpp -o main

编译器会自动处理整个流程,但可以通过选项控制各阶段:

  • -save-temps:保留所有中间文件
  • -v:显示详细编译过程
  • -O1/-O2/-O3:设置优化级别

重要概念

静态链接:在编译时将所有库代码复制到最终可执行文件中。使用-static选项。

动态链接:运行时加载共享库(.so.dll)。默认行为,减小可执行文件体积。

目标文件格式

  • Linux:ELF(Executable and Linkable Format)
  • Windows:PE(Portable Executable)
  • macOS:Mach-O

理解这些阶段有助于调试编译错误、优化代码性能和解决链接问题。

编译过程概述

C++代码从源代码到可执行二进制文件通常需要经历四个主要阶段:预处理、编译、汇编和链接。每个阶段由不同的工具处理,最终生成可执行文件。

预处理阶段

预处理阶段由预处理器(如cpp)完成。主要任务包括:

  • 处理所有以#开头的指令(如#include#define#ifdef等)
  • 展开宏定义
  • 包含头文件内容
  • 删除注释
  • 生成.i.ii预处理文件

示例命令:

g++ -E main.cpp -o main.i

编译阶段

编译器(如g++)将预处理后的代码翻译成汇编代码。主要任务包括:

  • 语法和语义分析
  • 生成中间代码
  • 代码优化
  • 生成平台相关的汇编代码(.s文件)

示例命令:

g++ -S main.i -o main.s

汇编阶段

汇编器(如as)将汇编代码转换为机器码,生成目标文件(.o.obj)。主要任务包括:

  • 将汇编指令翻译为机器指令
  • 生成可重定位目标文件
  • 包含符号表信息

示例命令:

g++ -c main.s -o main.o

链接阶段

链接器(如ld)将多个目标文件和库文件合并为可执行文件。主要任务包括:

  • 符号解析(解决未定义符号)
  • 重定位(调整地址引用)
  • 合并不同目标文件
  • 链接静态库
  • 处理动态库依赖
  • 生成可执行文件(如.exe或ELF格式)

示例命令:

g++ main.o -o main

现代编译器优化

现代编译器(如GCC、Clang)通常将这些步骤合并执行:

g++ main.cpp -o main

编译器会自动处理整个流程,但可以通过选项控制各阶段:

  • -save-temps:保留所有中间文件
  • -v:显示详细编译过程
  • -O1/-O2/-O3:设置优化级别

重要概念

静态链接:在编译时将所有库代码复制到最终可执行文件中。使用-static选项。

动态链接:运行时加载共享库(.so.dll)。默认行为,减小可执行文件体积。

目标文件格式

  • Linux:ELF(Executable and Linkable Format)
  • Windows:PE(Portable Executable)
  • macOS:Mach-O

理解这些阶段有助于调试编译错误、优化代码性能和解决链接问题。

编译过程概述

C++代码从源代码到可执行二进制文件通常需要经历四个主要阶段:预处理、编译、汇编和链接。每个阶段由不同的工具处理,最终生成可执行文件。

预处理阶段

预处理阶段由预处理器(如cpp)完成。主要任务包括:

  • 处理所有以#开头的指令(如#include#define#ifdef等)
  • 展开宏定义
  • 包含头文件内容
  • 删除注释
  • 生成.i.ii预处理文件

示例命令:

g++ -E main.cpp -o main.i

编译阶段

编译器(如g++)将预处理后的代码翻译成汇编代码。主要任务包括:

  • 语法和语义分析
  • 生成中间代码
  • 代码优化
  • 生成平台相关的汇编代码(.s文件)

示例命令:

g++ -S main.i -o main.s

汇编阶段

汇编器(如as)将汇编代码转换为机器码,生成目标文件(.o.obj)。主要任务包括:

  • 将汇编指令翻译为机器指令
  • 生成可重定位目标文件
  • 包含符号表信息

示例命令:

g++ -c main.s -o main.o

链接阶段

链接器(如ld)将多个目标文件和库文件合并为可执行文件。主要任务包括:

  • 符号解析(解决未定义符号)
  • 重定位(调整地址引用)
  • 合并不同目标文件
  • 链接静态库
  • 处理动态库依赖
  • 生成可执行文件(如.exe或ELF格式)

示例命令:

g++ main.o -o main

现代编译器优化

现代编译器(如GCC、Clang)通常将这些步骤合并执行:

g++ main.cpp -o main

编译器会自动处理整个流程,但可以通过选项控制各阶段:

  • -save-temps:保留所有中间文件
  • -v:显示详细编译过程
  • -O1/-O2/-O3:设置优化级别

重要概念

静态链接:在编译时将所有库代码复制到最终可执行文件中。使用-static选项。

动态链接:运行时加载共享库(.so.dll)。默认行为,减小可执行文件体积。

目标文件格式

  • Linux:ELF(Executable and Linkable Format)
  • Windows:PE(Portable Executable)
  • macOS:Mach-O

理解这些阶段有助于调试编译错误、优化代码性能和解决链接问题。

三.继承和虚继承

继承的概念

继承是面向对象编程的重要特性,允许子类(派生类)复用父类(基类)的成员变量和成员函数。子类可以扩展或重写父类的功能,形成层次化的类结构。

语法示例

class Base {
public:int baseVar;void baseFunc() {}
};class Derived : public Base {  // 公有继承
public:int derivedVar;
};

虚继承的作用

虚继承用于解决多重继承中的“菱形继承”问题(即一个派生类通过多条路径继承同一个基类)。虚继承确保基类在派生类中只保留一份实例,避免数据冗余和二义性。

菱形继承问题示例

class A { public: int a; };
class B : public A {};
class C : public A {};
class D : public B, public C {};  // D中包含两份A的成员

虚继承解决方案

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};  // D中仅保留一份A的成员

继承与虚继承的区别

  1. 普通继承

    • 每个派生类独立包含基类的成员副本。
    • 多重继承时可能导致基类成员重复。
  2. 虚继承

    • 虚基类在派生类中共享同一份实例。
    • 通过虚基类指针(vbptr)和虚基类表(vbtable)实现,可能增加内存开销。

虚继承的实现机制

虚继承通过虚基类表(vbtable)管理基类成员的偏移量,确保派生类能正确访问共享的基类成员。以下代码演示虚继承的内存布局:

class A { public: int a; };
class B : virtual public A { public: int b; };
class C : virtual public A { public: int c; };
class D : public B, public C { public: int d; };// D对象的内存布局可能包含:
// - B的成员(b)
// - C的成员(c)
// - D的成员(d)
// - 共享的A成员(a,通过vbptr访问)

使用场景建议

  1. 普通继承

    • 单继承或明确不需要共享基类实例的多重继承。
    • 性能敏感场景,避免虚继承的额外开销。
  2. 虚继承

    • 解决菱形继承问题,确保基类唯一性。
    • 需共享基类状态的设计(如接口类)。

注意事项

  • 虚继承可能增加对象大小和访问间接性。
  • 构造函数调用顺序:虚基类优先于非虚基类。
  • 避免过度使用虚继承,优先考虑组合或单一继承设计。

继承的概念

继承是面向对象编程的重要特性,允许子类(派生类)复用父类(基类)的成员变量和成员函数。子类可以扩展或重写父类的功能,形成层次化的类结构。

语法示例

class Base {
public:int baseVar;void baseFunc() {}
};class Derived : public Base {  // 公有继承
public:int derivedVar;
};

虚继承的作用

虚继承用于解决多重继承中的“菱形继承”问题(即一个派生类通过多条路径继承同一个基类)。虚继承确保基类在派生类中只保留一份实例,避免数据冗余和二义性。

菱形继承问题示例

class A { public: int a; };
class B : public A {};
class C : public A {};
class D : public B, public C {};  // D中包含两份A的成员

虚继承解决方案

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};  // D中仅保留一份A的成员

继承与虚继承的区别

  1. 普通继承

    • 每个派生类独立包含基类的成员副本。
    • 多重继承时可能导致基类成员重复。
  2. 虚继承

    • 虚基类在派生类中共享同一份实例。
    • 通过虚基类指针(vbptr)和虚基类表(vbtable)实现,可能增加内存开销。

虚继承的实现机制

虚继承通过虚基类表(vbtable)管理基类成员的偏移量,确保派生类能正确访问共享的基类成员。以下代码演示虚继承的内存布局:

class A { public: int a; };
class B : virtual public A { public: int b; };
class C : virtual public A { public: int c; };
class D : public B, public C { public: int d; };// D对象的内存布局可能包含:
// - B的成员(b)
// - C的成员(c)
// - D的成员(d)
// - 共享的A成员(a,通过vbptr访问)

使用场景建议

  1. 普通继承

    • 单继承或明确不需要共享基类实例的多重继承。
    • 性能敏感场景,避免虚继承的额外开销。
  2. 虚继承

    • 解决菱形继承问题,确保基类唯一性。
    • 需共享基类状态的设计(如接口类)。

注意事项

  • 虚继承可能增加对象大小和访问间接性。
  • 构造函数调用顺序:虚基类优先于非虚基类。
  • 避免过度使用虚继承,优先考虑组合或单一继承设计。

继承的概念

继承是面向对象编程的重要特性,允许子类(派生类)复用父类(基类)的成员变量和成员函数。子类可以扩展或重写父类的功能,形成层次化的类结构。

语法示例

class Base {
public:int baseVar;void baseFunc() {}
};class Derived : public Base {  // 公有继承
public:int derivedVar;
};

虚继承的作用

虚继承用于解决多重继承中的“菱形继承”问题(即一个派生类通过多条路径继承同一个基类)。虚继承确保基类在派生类中只保留一份实例,避免数据冗余和二义性。

菱形继承问题示例

class A { public: int a; };
class B : public A {};
class C : public A {};
class D : public B, public C {};  // D中包含两份A的成员

虚继承解决方案

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};  // D中仅保留一份A的成员

继承与虚继承的区别

  1. 普通继承

    • 每个派生类独立包含基类的成员副本。
    • 多重继承时可能导致基类成员重复。
  2. 虚继承

    • 虚基类在派生类中共享同一份实例。
    • 通过虚基类指针(vbptr)和虚基类表(vbtable)实现,可能增加内存开销。

虚继承的实现机制

虚继承通过虚基类表(vbtable)管理基类成员的偏移量,确保派生类能正确访问共享的基类成员。以下代码演示虚继承的内存布局:

class A { public: int a; };
class B : virtual public A { public: int b; };
class C : virtual public A { public: int c; };
class D : public B, public C { public: int d; };// D对象的内存布局可能包含:
// - B的成员(b)
// - C的成员(c)
// - D的成员(d)
// - 共享的A成员(a,通过vbptr访问)

使用场景建议

  1. 普通继承

    • 单继承或明确不需要共享基类实例的多重继承。
    • 性能敏感场景,避免虚继承的额外开销。
  2. 虚继承

    • 解决菱形继承问题,确保基类唯一性。
    • 需共享基类状态的设计(如接口类)。

注意事项

  • 虚继承可能增加对象大小和访问间接性。
  • 构造函数调用顺序:虚基类优先于非虚基类。
  • 避免过度使用虚继承,优先考虑组合或单一继承设计。
http://www.dtcms.com/a/391197.html

相关文章:

  • Spring AI开发指导-工具调用
  • Linux 基本命令超详细解释第二期 | touch | cat | more | cp | mv | rm | which | find
  • [x-cmd] 安装指南
  • Altium Designer(AD24)原理图Move移动功能详细介绍图文教程
  • 部署java程序,服务器报403 Forbidden 问题的终极解决方案
  • 【LeetCode】链表经典问题解析:环形、回文与相交
  • 电磁超材料及其领域应用优势
  • STM32与Modbus RTU协议实战开发指南-fc3ab6a453
  • ArrayList 与 LinkedList 深度对比:从原理到场景的全方位解析
  • Ubuntu和windows复制粘贴互通
  • 银行回单 OCR 识别:财务自动化的 “数据入口“
  • 深兰科技陈海波的AI破局之道:打造软硬一体综合竞争力|《中国经营报》专访
  • 面试经验之mysql高级问答深度解析
  • 高质量票据识别数据集:1000张收据图像+2141个商品标注,支持OCR模型训练与文档理解研究
  • 嵌入式音视频开发——FFmpeg入门
  • MySQL索引篇---B+树在索引中的工作原理
  • 强化学习训练-数据处理
  • VirtualBox为ubuntu系统设置共享文件夹
  • Python实战进阶》No.41: 使用 Streamlit 快速构建 ML 应用
  • Salesforce 执行顺序(Order of Execution)详解
  • Linux内核进程管理子系统有什么第五十七回 —— 进程主结构详解(53)
  • Vue 记账凭证模块组件
  • ORACLE-数据库闪回
  • 【Python】集合
  • 【Leetcode hot 100】437.路径总和 Ⅲ
  • 神经网络学习笔记16——高效卷积神经网络架构汇总(SqueezeNet、MobileNet、ShuffleNet、EfficientNet、GhostNet)
  • 解码阳光电源技术壁垒:以IPD和数字化驱动模块化创新的研发体系
  • ARM体系结构—架构—指令集—寄存器—工作模式
  • 自适应全变分模型的图像平滑去噪与边缘保留算法
  • 主流前端框架比较