嵌入式软件工程师面试题——2025校招社招通用(十)

news2024/9/30 11:31:18

说明:

  • 面试题来源于网络书籍,公司题目以及博主原创或修改(题目大部分来源于各种公司);
  • 文中很多题目,或许大家直接编译器写完,1分钟就出结果了。但在这里博主希望每一个题目,大家都要经过认真思考,答案不重要,重要的是通过题目理解所考知识点,好应对题目更多的变化;
  • 博主与大家一起学习,一起刷题,共同进步;
  • 写文不易,麻烦给个三连!!!

1.在main执行之前和之后执行的代码可能是什么?

答案:
main函数执行之前,主要就是初始化系统相关资源:

  • 设置栈指针
  • 初始化静态static变量和global全局变量,即.data段的内容
  • 将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL等等,即.bss段的内容
  • 全局对象初始化,在main之前调用构造函数,这是可能会执行前的一些代码
  • 将main函数的参数argc,argv等传递给main函数,然后才真正运行main函数
  • attribute((constructor))

main函数执行之后:

  • 全局对象的析构函数会在main函数之后执行;
  • 可以用 atexit 注册一个函数,它会在main 之后执行;
  • attribute((destructor))

2.结构体内存对齐问题?

  • 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
  • 未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)
    c++11以后引入两个关键字 alignas (opens new window)与 alignof (opens new window)。其中alignof可以计算出类型的对齐方式,alignas可以指定结构体的对齐方式。
    但是alignas在某些情况下是不能使用的,具体见下面的例子:
// alignas 生效的情况

struct Info {
  uint8_t a;
  uint16_t b;
  uint8_t c;
};

std::cout << sizeof(Info) << std::endl;   // 6  2 + 2 + 2
std::cout << alignof(Info) << std::endl;  // 2

struct alignas(4) Info2 {
  uint8_t a;
  uint16_t b;
  uint8_t c;
};

std::cout << sizeof(Info2) << std::endl;   // 8  4 + 4
std::cout << alignof(Info2) << std::endl;  // 4
// alignas 失效的情况

struct Info {
  uint8_t a;
  uint32_t b;
  uint8_t c;
};

std::cout << sizeof(Info) << std::endl;   // 12  4 + 4 + 4
std::cout << alignof(Info) << std::endl;  // 4

struct alignas(2) Info2 {
  uint8_t a;
  uint32_t b;
  uint8_t c;
};

std::cout << sizeof(Info2) << std::endl;   // 12  4 + 4 + 4
std::cout << alignof(Info2) << std::endl;  // 4

若alignas小于自然对齐的最小单位,则被忽略。

  • 如果想使用单字节对齐的方式,使用alignas是无效的。应该使用#pragma pack(push,1)或者使用__attribute__((packed))。
#if defined(__GNUC__) || defined(__GNUG__)
  #define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
  #define ONEBYTE_ALIGN
  #pragma pack(push,1)
#endif

struct Info {
  uint8_t a;
  uint32_t b;
  uint8_t c;
} ONEBYTE_ALIGN;

#if defined(__GNUC__) || defined(__GNUG__)
  #undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
  #pragma pack(pop)
  #undef ONEBYTE_ALIGN
#endif

std::cout << sizeof(Info) << std::endl;   // 6 1 + 4 + 1
std::cout << alignof(Info) << std::endl;  // 1
  • 确定结构体中每个元素大小可以通过下面这种方法:
#if defined(__GNUC__) || defined(__GNUG__)
  #define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
  #define ONEBYTE_ALIGN
  #pragma pack(push,1)
#endif

/**
* 0 1   3     6   8 9            15
* +-+---+-----+---+-+-------------+
* | |   |     |   | |             |
* |a| b |  c  | d |e|     pad     |
* | |   |     |   | |             |
* +-+---+-----+---+-+-------------+
*/
struct Info {
  uint16_t a : 1;
  uint16_t b : 2;
  uint16_t c : 3;
  uint16_t d : 2;
  uint16_t e : 1;
  uint16_t pad : 7;
} ONEBYTE_ALIGN;

#if defined(__GNUC__) || defined(__GNUG__)
  #undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
  #pragma pack(pop)
  #undef ONEBYTE_ALIGN
#endif

std::cout << sizeof(Info) << std::endl;   // 2
std::cout << alignof(Info) << std::endl;  // 1

这种处理方式是alignas处理不了的。
https://interviewguide.cn/notes/03-hunting_job/02-interview/01-01-01-basic.html

3.在传递函数参数时,什么时候该使用指针,什么时候该使用引用呢?

答案:

  • 需要返回函数内局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的
  • 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小
  • 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式

4.你觉得堆快一点还是栈快一点?

答案:
毫无疑问是栈快一点。

因为操作系统会在底层对栈提供支持,会分配专门的寄存器存放栈的地址,栈的入栈出栈操作也十分简单,并且有专门的指令执行,所以栈的效率比较高也比较快。

而堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。

5.被free回收的内存是立即返还给操作系统吗?

答案:
不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片

6.宏定义和typedef区别?

答案:

  • 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名
  • 宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。
  • 宏不检查类型;typedef会检查数据类型。
  • 宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。
  • 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。

7.C++中const和static的作用

答案:
static

  • 不考虑类的情况
    • 隐藏。所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用,加了之后只能在该文件所在的编译模块中使用
    • 默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区
    • 静态变量在函数内定义,始终存在,且只进行一次初始化,具有记忆性,其作用范围与局部变量相同,函数退出后仍然存在,但不能使用
  • 考虑类的情况
    • static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,必须在类定义体外部初始化,初始化时不需要标示为static;可以被非static成员函数任意访问。
    • static成员函数:不具有this指针,无法访问类对象的非static成员变量和非static成员函数;不能被声明为const、虚函数和volatile;可以被非static成员函数任意访问

const

  • 不考虑类的情况
    • const常量在定义时必须初始化,之后无法更改
    • const形参可以接收const和非const类型的实参,例如// i 可以是 int 型或者 const int 型void fun(const int& i){ //…}
  • 考虑类的情况
    • const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化
    • const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化
    • const修饰变量是也与static有一样的隐藏作用。只能在该文件中使用,其他文件不可以引用声明使用。 因此在头文件中声明const变量是没问题的,因为即使被多个文件包含,链接性都是内部的,不会出现符号冲突

8.拷贝初始化和直接初始化

答案:

  • 当用于类类型对象时,初始化的拷贝形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数。拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个临时对象拷贝到正在创建的对象。举例如下:
string str1("I am a string");//语句1 直接初始化
string str2(str1);//语句2 直接初始化,str1是已经存在的对象,直接调用拷贝构造函数对str2进行初始化
string str3 = "I am a string";//语句3 拷贝初始化,先为字符串”I am a string“创建临时对象,再把临时对象作为参数,使用拷贝构造函数构造str3
string str4 = str1;//语句4 拷贝初始化,这里相当于隐式调用拷贝构造函数,而不是调用赋值运算符函数
 
  • 为了提高效率,允许编译器跳过创建临时对象这一步,直接调用构造函数构造要创建的对象,这样就完全等价于直接初始化了(语句1和语句3等价),但是需要辨别两种情况。
    • 当拷贝构造函数为private时:语句3和语句4在编译时会报错
    • 使用explicit修饰构造函数时:如果构造函数存在隐式转换,编译时会报错

9.野指针和悬空指针

答案:
都是是指向无效内存区域(这里的无效指的是"不安全不可控")的指针,访问行为将会导致未定义行为。

  • 野指针 :指的是没有被初始化过的指针
int main(void) { 
    
    int* p;     // 未初始化
    std::cout<< *p << std::endl; // 未初始化就被使用
    
    return 0;
}
  • 悬空指针:指针最初指向的内存已经被释放了的一种指针。
int main(void) { 
  int * p = nullptr;
  int* p2 = new int;
  
  p = p2;

  delete p2;
}

此时 p和p2就是悬空指针,指向的内存已经被释放。继续使用这两个指针,行为不可预料。需要设置为p=p2=nullptr。此时再使用,编译器会直接保错。 避免野指针比较简单,但悬空指针比较麻烦。c++引入了智能指针,C++智能指针的本质就是避免悬空指针的产生

10.如何用代码判断大小端存储

答案:
大端存储:字数据的高字节存储在低地址中
小端存储:字数据的低字节存储在低地址中

例如:32bit的数字0x12345678
小端模式中的存储方式为:
在这里插入图片描述
大端模式中的存储方式为:
在这里插入图片描述
方式一:使用强制类型转换-这种法子不错

#include <iostream>
using namespace std;
int main()
{
    int a = 0x1234;
    //由于int和char的长度不同,借助int型转换成char型,只会留下低地址的部分
    char c = (char)(a);
    if (c == 0x12)
        cout << "big endian" << endl;
    else if(c == 0x34)
        cout << "little endian" << endl;
}

方式二:巧用union联合体

#include <iostream>
using namespace std;
//union联合体的重叠式存储,endian联合体占用内存的空间为每个成员字节长度的最大值
union endian
{
    int a;
    char ch;
};
int main()
{
    endian value;
    value.a = 0x1234;
    //a和ch共用4字节的内存空间
    if (value.ch == 0x12)
        cout << "big endian"<<endl;
    else if (value.ch == 0x34)
        cout << "little endian"<<endl;
}

11.volatile、mutable和explicit关键字的用法

答案:
(1)volatile
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。多线程中被几个任务共享的变量需要定义为volatile类型。

volatile 指针
volatile 指针和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念
修饰由指针指向的对象、数据是 const 或 volatile 的:

const char* cpch;volatile char* vpch;

指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:

char* const pchc;char* volatile pchv;

注意:

  • 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
  • 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
  • C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

多线程下的volatile
有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。

如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。

volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。

(2)mutable
mutable的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const函数里面修改一些跟类状态无关的数据成员,那么这个函数就应该被mutable来修饰,并且放在函数后后面关键字位置。

class person
{
    int m_A;
    mutable int m_B;//特殊变量 在常函数里值也可以被修改
public:
    void add() const//在函数里不可修改this指针指向的值 常量指针
    {
        m_A = 10;//错误  不可修改值,this已经被修饰为常量指针
        m_B = 20;//正确
    }
};
class person
{
public:
    int m_A;
    mutable int m_B;//特殊变量 在常函数里值也可以被修改
};

int main()
{
    const person p = person();//修饰常对象 不可修改类成员的值
    p.m_A = 10;//错误,被修饰了指针常量
    p.m_B = 200;//正确,特殊变量,修饰了mutable
}

(3)explicit
explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显式的方式进行类型转换,注意以下几点:

  • explicit 关键字只能用于类内部的构造函数声明上
  • 被explicit修饰的构造函数的类,不能发生相应的隐式类型转换

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1180967.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

芯片无线升级,给产品和芯片买个保险

例如&#xff0c;想让卧室灯过于刺眼&#xff0c;需要稍微暗一个度。 目前来说常见的只能重新买了重新安装&#xff1f;&#xff01; 可都已经安装的好的电灯&#xff0c;实在是食之无味&#xff0c;弃之可惜。 这时候产品不拆换&#xff0c;还可以升级就显得尤为重要了。 为了…

React 其他常用Hooks

1. useImperativeHandle 在react中父组件可以通过forwardRef将ref转发到子组件&#xff1b;子组件拿到父组件创建的ref&#xff0c;绑定到自己的某个元素&#xff1b; forwardRef的做法本身没有什么问题&#xff0c;但是我们是将子组件的DOM直接暴露给了父组件&#xff0c;某下…

C++ http协议POST body raw 字段向服务器发送请求

环境&#xff1a;ubuntu系统c使用http协议不是很方便&#xff0c;通过curl库我们可以很方便使用http协议&#xff0c;由于我的请求方式比较特殊&#xff0c;在网上没有找到相关的资料&#xff0c;之前使用python实现过一版&#xff0c;但是当设备数量超过100台时&#xff0c;程…

FPGA时序分析与约束(10)——生成时钟

一、概述 最复杂的设计往往需要多个时钟来完成相应的功能。当设计中存在多个时钟的时候&#xff0c;它们需要相互协作或各司其职。异步时钟是不能共享确定相位关系的时钟信号&#xff0c;当多个时钟域交互时&#xff0c;设计中只有异步时钟很难满足建立和保持要求。我们将在后面…

软件性能测试指标分享,第三方检测机构进行性能测试的好处

在现代科技发展迅猛的时代背景下&#xff0c;软件的性能表现对于用户体验和企业竞争力至关重要。软件性能测试是通过对软件系统进行一系列的测试&#xff0c;以评估其在各种工作条件下的性能表现。这些工作条件可以包括并发用户数、数据量、网络传输速度等。软件性能测试的目的…

[动态规划] (十一) 简单多状态 LeetCode 面试题17.16.按摩师 和 198.打家劫舍

[动态规划] (十一) 简单多状态: LeetCode 面试题17.16.按摩师 和 198.打家劫舍 文章目录 [动态规划] (十一) 简单多状态: LeetCode 面试题17.16.按摩师 和 198.打家劫舍题目分析题目解析状态表示状态转移方程初始化和填表顺序 代码实现按摩师打家劫舍 总结 注&#xff1a;本题与…

python 之 列表推导式

文章目录 基本结构示例 1&#xff1a;将列表中的元素乘以 2 添加条件判断示例 2&#xff1a;筛选出偶数并加倍 嵌套列表推导式示例 3&#xff1a;生成九九乘法表 使用条件表达式示例 4&#xff1a;根据条件返回不同的值 镶嵌使用详细介绍基本结构示例生成二维数组多重筛选和操作…

软件测试需求分析是什么?为什么需要进行测试需求分析?

在软件开发中&#xff0c;软件测试是确保软件质量的重要环节之一。而软件测试需求分析作为软件测试的前置工作&#xff0c;对于保证软件测试的顺利进行具有重要意义。软件测试需求分析是指对软件测试的需求进行细致的分析和规划&#xff0c;以明确测试的目标、任务和范围&#…

vuecli3 批量打印二维码

安装以个命令: npm install qrcode --save npm install print-js --save 页面使用: import qrcode from qrcode import printJS from print-js <el-button type"primary" click"handleBulkPrint">批量打印</el-button>methods: {// 批量打印…

发布成绩看这里

你是否曾经在成绩发布时手忙脚乱&#xff0c;为处理大量的成绩数据而感到烦恼&#xff1f;现在&#xff0c;让我们一起探讨如何利用代码和Excel实现学生自助查询成绩的功能。 一、使用Excel处理成绩数据 收集成绩数据首先需要将学生的成绩数据收集起来。最方便的方法是使用Exce…

明星和KOL的影响力是医美产品推广的加速器

在当今时代&#xff0c;越来越多的人开始关注自身外貌和健康。医美类产品应运而生&#xff0c;为人们的美丽和自信带来了无限可能。然而&#xff0c;面临激烈的市场竞争&#xff0c;医美类产品在营销推广方面必须做出差异化和创新化的努力&#xff0c;才能取得成功。 一、打造独…

OpenAI 首届开发者大会-亮点多多

正如 Sam Altman 此前所言&#xff0c;OpenAI 首届开发者大会为人们带来了一些非常棒的新东西。 继今年春天发布 GPT-4 之后&#xff0c;OpenAI 又创造了一个不眠夜。 过去一年&#xff0c;ChatGPT 绝对是整个科技领域最热的词汇。OpenAI 也依靠 ChatGPT 取得了惊人的成绩&…

鸿蒙原生应用开发-DevEco Studio本地模拟器的使用

使用Local Emulator运行应用/服务 DevEco Studio提供的Local Emulator可以运行和调试Phone、TV和Wearable设备的HarmonyOS应用/服务。在Local Emulator上运行应用/服务兼容签名与不签名两种类型的HAP。 Local Emulator相比于Remote Emulator的区别&#xff1a;Local Emulator是…

一文掌握 Apache SkyWalking

Apache SkyWalking SkyWalking是一个开源可观测平台&#xff0c;用于收集、分析、聚合和可视化来自服务和云原生基础设施的数据。SkyWalking 提供了一种简单的方法来保持分布式系统的清晰视图&#xff0c;甚至跨云。它是一种现代APM&#xff0c;专为云原生、基于容器的分布式系…

idea Error: java: OutOfMemoryError: insufficient memory处理

IDEA设置里&#xff0c;修改heap size更大一点&#xff0c;可以解决问题

Solidity快速入门之函数输出

返回值return和returns Solidity有两个关键字与函数输出相关&#xff1a;return和returns&#xff0c;他们的区别在于&#xff1a; returns加在函数名后面&#xff0c;用于声明返回的变量类型及变量名&#xff1b;return用于函数主体中&#xff0c;返回想要返回的变量&#x…

1560分钟一节课VUE项目从入门到精通

在职场&#xff0c;流传着这样一句话&#xff1a;跳槽加薪是现实&#xff0c;原地加薪是梦想。工作跳一跳&#xff0c;工资翻一番。 事实好像确实如此&#xff0c;相关机构调研发现&#xff0c;跳槽换工作后的平均加薪幅度能达到36%&#xff01; ▲ 图源网络&#xff0c;如侵删…

nodejs统计文件/文件夹数量

nodejs统计文件/文件夹数量 const fs require(fs); const path require(path);const htmlList []; const cssList []; const jsList []; let fileNum 0 let filesNum 0 function getFiles(dir) {const folders fs.readdirSync(dir);folders.forEach((folderName) >…

卡博替尼Cabozantinib使用说明书、副作用、使用方法、不良反应

&#xff08;图片来源于网络&#xff09; 卡博替尼&#xff08;Cabozantinib&#xff09;是一种靶向药物&#xff0c;属于多受体酪氨酸激酶抑制剂。它被用于治疗多种癌症&#xff0c;包括进行性、转移性甲状腺髓样癌、晚期肾癌和二线治疗晚期肝癌。卡博替尼可以阻断涉及癌细胞…

基于springboot实现招生平台管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现招生管理系统演示 摘要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括招生管理系统的网络应用&#xff0c;在外国招生管理系统已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。招…