• 嵌入式软件工程师面试题——2025校招专题(二)


    说明:

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

    1.用预处理指令#define声明一个常数,用以表明1年中有多少秒?

    解析
    通过这道题面试官想考以下几个知识点:

    • #define语法的基本知识(例如,不能以分号结束,括号的使用,等等)
    • 要懂得预处理器将为你计算常数表达式的值,因此,写出你是如何计算一年中有多
      少秒而不是计算出实际的值,会更有意义
    • 意识到这个表达式将使一个16位机的整型数溢出,因此要用到长整型符号L,告诉
      编译器这个常数是长整型数
      如果在表达式中用到UL(表示无符号长整型),那么你就有了一个好的起点。记住,
      第一印象很重要。

    答案#define SECOND_EVERY_YEAR (606024*365)UL

    2.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个

    解析
    这个测试是为下面的目的而设的

    • 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符
      变为标准C的一部分,宏都是方便地产生嵌入代码的唯一方法。对于嵌入式系统来说,为了
      能达到要求的性能,嵌入代码经常是必须的方法
    • 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比
      if-then-else更优化的代码,了解这个用法是很重要的
    • 注意点:一定要带好括号,不要嫌麻烦,因为宏操作是直接替换,如果不带括号会出现一些问题,请看下面的例子
    #define SQUARE(x) x * x
    
    int a = 4;
    int b = SQUARE(a + 1);
    
    • 1
    • 2
    • 3
    • 4

    在预处理阶段,SQUARE(a + 1) 会被替换成 a + 1 * a + 1,由于乘法的优先级高于加法,所以实际上会计算成 a + (1 * a) + 1,结果为 9 而不是 25。

    答案#define MIN(x, y) ((x) <= (y) ? (x) : (y))

    3.说说const修饰指针?

    解析
    关于const修饰指针的情况,一般分为如下4种情况:

    int main()
    {   
        int b = 500;
        const int* a = &b;          // 情况1
        int const* a = &b;          // 情况2
        int* const a = &b;          // 情况3
        const int* const a = &b;    // 情况4
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 先看情况1
      如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常
      量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,1和2的
      情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下
      不允许对内容进行更改操作。
      换句话来说,如果a是一名仓库管理员的话,他所进入的仓库,里面的货物(*a)是他没权
      限允许动的,仓库里面的东西原来是什么就是什么;所以
    	int b = 500;
        const int* a = &b;
        *a = 600; // 错误
    
    • 1
    • 2
    • 3

    但是也有别的办法去改变*a的值,一个是通过改变b的值:

    	int b = 500;
        const int* a = &b;
        b = 600;
        cout << *a << endl; // 得到600
    
    • 1
    • 2
    • 3
    • 4

    还有一种改变*a办法就是a指向别处(管理员换个仓库):

     	int b = 500, c = 600;
        const int* a = &b;
        a = &c;
        cout << *a << endl; // 得到600
    
    • 1
    • 2
    • 3
    • 4
    • 情况2与情况1相同
    • 情况3为指针本身是常量,这种情况下不能对指针本身进行更改操作,而指针所指向
      的内容不是常量

      举例来说:如果a是一名仓库管理员的话,他只能进入指定的某仓库,而不能去别的仓
      库(所以a++是错误的);但这个仓库里面的货物(*a)是可以随便动的,(*a=600是正确
      的)。
      此外,对于情况3:定义时必须同时初始化。
    	int b = 500, c = 600;
        // int *const a; // 错误 没有初始化
        int *const a = &b; // 正确,必须初始化
        *a = 600; // 正确,允许改值
        a = &c; // 错误,指针是常量
        cout << a++ << endl; // 错误
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 对于情况4为指针本身和指向的内容均为常量。那么这个仓库管理员只能去特定的仓库,并且仓库里面所有的货物他都没有权限去改变。
    	int b = 500, c = 600;
        const int* const a = &b;
        a = &c; // 错误
        *a = 600; // 错误
    
    • 1
    • 2
    • 3
    • 4

    4.const与#define相比有什么不同?

    答案:

    • const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意料不到的错误(边际效应)。
    • 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
    • 存储方式不同: 定义的常量存储在程序的数据段中,有固定的地址。而 #define 定义的常量只是简单的文本替换,在编译时会直接替换成对应的值,不会分配内存空间
    • 作用域不同: 常量的作用域与其他变量类似,通常只在定义它的文件或函数内部可见。而 #define 定义的常量没有作用域限制,可以在整个程序中使用。

    5.下列程序的输出结果是什么?

    #include 
    #include 
    #include 
    using namespace std;
    
    struct {
        short a1;
        short a2;
        short a3;
    }A;
    
    struct {
        long a1;
        short a2;
    }B;
    
    int main()
    {
        char *ss1 = "0123456789";
        char ss2[] = "0123456789";
        char ss3[100] = "0123456789";
    	int ss4[100];
    	char q1[] = "abc";
    	char q2[] = "a\n";
    	char *q3 = "a\n";
    	char *str1 = (char *)malloc(100);
    	void *str2 = (void *)malloc(100);
    
        cout << sizeof(ss1) << " ";
        cout << sizeof(ss2) << " ";
        cout << sizeof(ss3) << " ";
        cout << sizeof(ss4) << " ";
        cout << sizeof(q1) << " ";
        cout << sizeof(q2) << " ";
        cout << sizeof(q3) << " ";
        cout << sizeof(A) << " ";
        cout << sizeof(B) << " ";
        cout << sizeof(str1) << " ";
        cout << sizeof(str2) << " ";
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    解析

    • ss1是一个字符指针,指针的大小是一个定值,就是4字节,所以sizeof(ss1)是4字节
    • ss2是一个字符数组,这个数组最初未定大小,由具体填充值来定。填充值
      是“0123456789”。1个字符所占空间是1字节,10个就是10字节,再加上隐含的“\0”,所以一
      共是11字节
    • ss3也是一个字符数组,这个数组开始预分配100,所以它的大小一共是100字节
    • ss4也是一个整型数组,这个数组开始预分配100,但每个整型变量所占空间是4,所以
      它的大小一共是400字节
    • q1与ss2类似,所以是4字节
    • q2里面有一个“\n”,“\n”算做一位,所以它的空间大小是3字节
    • q3是一个字符指针,指针的大小是一个定值,就是4,所以sizeof(q3)是4字节
    • 结构体A中有3个short类型变量,各自以2字节对齐,结构体对齐参数按默认的8字节对齐,则a1、a2、a3都取2字节对齐,sizeof(A)为6,其也是2的整数倍
    • B中a1为4字节对齐,a2
      为2字节对齐,结构体默认对齐参数为8,则a1取4字节对齐,a2取2字节对齐;结构体大小为6字节,6不为4的整数倍,补空字节,增到8时,符合所有条件,则sizeof(B)为8
    • str1与str2与ss1类似,都是指针的大小,所以为4

    答案: 4 11 100 400 4 3 4 6 8 4 4

    6.以下代码为32位机器编译,数据是以4字节为对齐单位,这两个类的输出结果是什么,为什么?

    #include 
    using namespace std;
    
    class A {
    private:
        bool m_bTemp;
        int m_nTemp;
        bool m_bTemp2;
    };
    
    class B {
    private:
        int m_nTemp;
        bool m_bTemp;
        bool m_bTemp2;
    };
    
    int main()
    {   
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    解析
    在访问内存时,如果地址按4字节对齐,则访问效率会高很多。这种现象的原因在于访问内存的硬件电路。一般情况下,地址总线总是按照对齐后的地址来访问的。例如你想得到0x00000001开始的4字节内容,系统首先需要以0x00000000读4字节,从中取得3字节,然后再用0x00000004作为开始地址,获得下一个4字节,再从中得到第一个字节,两次组合出你想得到的内容。但是如果地址一开始就是对齐到0x00000000,则系统只要一次读写即可。
    在这里插入图片描述

    答案:12 8

    7.说明sizeof和strlen之间的区别

    解析
    由以下几个例子我们说明sizeof和strlen之间的区别。
    第1个例子:

    char * ss = "0123456789";
    
    • 1

    sizeof(ss)结果为4,ss是指向字符串常量的字符指针。
    sizeof(*ss)结果为1,*ss是第一个字符。

    第2个例子:

    char ss[] = "0123456789";
    
    • 1

    sizeof(ss)结果为11,ss是数组,计算到“\0”位置,因此是(10+1)。
    sizeof(*ss)结果为1,*ss是第一个字符。

    第3个例子:

    char ss[100] = "0123456789";
    
    • 1

    sizeof(ss)结果为100,ss表示在内存中预分配的大小,100×1。
    strlen(ss)结果为10,它的内部实现是用一个循环计算字符串的长度,直到“\0”为止。

    第4个例子:

    int ss[100] = "0123456789";
    
    • 1

    sizeof(ss)结果为400,ss表示在内存中的大小,100×4。
    strlen(ss)错误,strlen的参数只能是char*,且必须是以“\0”结尾的。

    第5个例子:

    class X
    {
    	int i;
    	int j;
    	char k;
    };
    X x;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    cout< cout<

    答案

    通过对sizeof与strlen的深入理解,得出两者区别如下:

    • sizeof操作符的结果类型是size_t,它在头文件中的typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
    • sizeof是运算符,strlen是函数。
    • sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以“\0”结尾的。sizeof还可以用函数做参数。
    • 数组做sizeof的参数不退化,传递给strlen就退化为指针。
    • 大部分编译程序在编译的时候就把sizeof计算过了,是类型或是变量的长度。这就是sizeof(x)可以用来定义数组维数的原因。
    • strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度,而不是类型
      占内存的大小。
    • sizeof后如果是类型必须加括号,如果是变量名可以不加括号。这是因为sizeof是
      个操作符而不是个函数。
    • 当使用了一个结构类型或变量时,sizeof返回实际的大小。当使用一静态的空间数
      组时,sizeof返回全部数组的尺寸。sizeof操作符不能返回被动态分配的数组或外部的数组的
      尺寸。
    • 数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如
      fun(char [8])、fun(char [])都等价于fun(char *)。在C++里传递数组永远都是传递指向数组首
      元素的指针,编译器不知道数组的大小。如果想在函数内知道数组的大小,需要这样做:进
      入函数后用memcpy将数组复制出来,长度由另一个形参传进去。代码如下:
    void fun (unsigned char *p1, int len)
    {
        unsigned char* buf = new unsigned char[len+1];
        memcpy(buf, p1, len);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • sizeof操作符不能用于函数类型、不完全类型或位字段。不完全类型指具有未知
      存储大小数据的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void
      类型等。

    8.说明sizeof的使用场合

    答案

    • sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如:
    void *malloc(size_t size);
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    
    • 1
    • 2
    • 用它可以看看某种类型的对象在内存中所占的单元字节
    • 在动态分配一对象时,可以让系统知道要分配多少内存
    • 便于一些类型的扩充。在Windows中有很多结构类型就有一个专用的字段用来存放该类型的字节大小
    • 由于操作数的字节数在实现时可能出现变化,建议在涉及操作数字节大小时用sizeof代替常量计算。
    • 如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小

    9. 这个数组占据多大空间?

    int **a[3][4]  = ?
    
    A. 64
    B. 12
    C. 48
    D. 128
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    解析:sizeof问题,3×4×4=48。

    答案:C

    10.一个空类占多少空间?多重继承的空类呢?

    解析:我们用程序来实现一个空类和一个多重继承的空类。看看它们的大小是多少。代
    码如下:

    #include 
    #include 
    #include 
    using namespace std;
    
    class A {};
    class A2 {};
    
    class B : public A {};
    class C : public virtual B {};
    class D : public A, public A2 {};
    
    
    int main()
    {   
        cout << "sizeof(A): " << sizeof(A) << endl;
        cout << "sizeof(B): " << sizeof(B) << endl;
        cout << "sizeof(C): " << sizeof(C) << endl;
        cout << "sizeof(D): " << sizeof(D) << endl;
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    空类所占空间为1,单一继承的空类空间也为1,多重继承的空类空间还是1。
    但是虚继承涉及虚表(虚指针),所以sizeof©的大小为4。

    答案:1 1 4 1

    11.内联函数和宏的差别是什么?

    答案:

    • 内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
    • 内联函数要做参数类型检查,这是内联函数跟宏相比的优势。
    • inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。对于短小的代码来说inline增加空间消耗换来的是效率提高,这方面和宏是一模一样的,但是inline在和宏相比没有付出任何额外代价的情况下更安全。至于是否需要inline函数,就需要根据实际情况来取舍了。
    • inline一般只用于如下情况:
      一个函数不断被重复调用。
      函数只有简单的几行,且函数内不包含for、while、switch语句。

    一般来说,我们写小程序没有必要定义成inline,但是如果要完成一个工程项目,当一个简单函数被调用多次时,则应该考虑用inline。

    • 宏在C语言里极其重要,而在C++里用得就少多了。关于宏的第一规则是绝不应该去使用它,除非你不得不这样做。几乎每个宏都表明了程序设计语言里、程序里或者程序员的一个缺陷,因为它将在编译器看到程序的正文之前重新摆布这些正文。宏也是许多程序设计工具的主要麻烦。所以,如果你使用了宏,就应该准备只能从各种工具(如排错系统、交叉引用系统、轮廓程序等)中得到较少的服务。
    • 宏是在代码处不加任何验证的简单替代,而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。
    • 宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体。
    • 关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。如下风格的函数Foo不能成为内联函数:
    inline void Foo(int x, int y); // inline仅与函数声明放在一起
    void Foo(int x, int y) {}
    
    • 1
    • 2

    而如下风格的函数Foo则成为内联函数:

    void Foo(int x, int y); 
    inline void Foo(int x, int y) {} // inline与函数定义放在一起
    
    • 1
    • 2

    所以说,inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。内联能提高函数的执行效率,至于为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

    以下情况不宜使用内联:
    1 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
    2 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。

  • 相关阅读:
    VS code本地安装PlantUML
    中国膜产业需求动态与竞争策略分析报告2022-2028年
    用工业路由器就能实现的下管廊远程监控无线解决方案
    设计模式之工厂模式
    UML类图
    2022年7月Web3行业月度发展报告区块链篇 | 陀螺科技会员专享
    【数据结构练习】栈的面试题集锦
    第14届蓝桥杯青少组C++选拔赛真题8月21日场次
    C#面:解释什么是闭包
    文本自动粘贴编辑器:支持自动粘贴并筛选手机号码,让信息处理更轻松
  • 原文地址:https://blog.csdn.net/weixin_45257157/article/details/134012389