C++/嵌入式八股学习-day1

发布时间 2023-08-08 22:01:08作者: 我好想睡觉啊

C/C++

内联函数和宏定义的区别

  1. 内联函数是C++语言提供的一种特性,可以在函数定义时使用inline关键字进行声明。而宏定义是C和C++语言都支持的一种预处理指令。

  2. 内联函数是由编译器实现的,因此内联函数的调用是有类型检查的。而宏定义是由预处理器实现的,宏定义的调用是没有类型检查的。这意味着在使用宏定义时,需要保证参数类型的正确性,否则会导致编译错误或者程序运行时错误。

  3. 内联函数的代码是由编译器直接嵌入到调用该函数的地方,因此内联函数的代码是可以进行调试的,可以在代码中打断点进行调试。而宏定义的代码是在预处理阶段直接替换为代码,无法进行调试。

  4. 内联函数的参数和返回值都是有类型的,并且可以使用函数的所有特性(如const和引用参数)。而宏定义中的参数和返回值都是文本替换,不支持类型检查和特性。

  5. 内联函数的使用受到编译器的限制,只有当函数体比较小、被频繁调用、参数是常量等条件满足时才会进行内联。而宏定义没有这些限制,它会在所有地方进行文本替换,因此可能会增加代码的体积和复杂度。

构造函数和初始化列表的区别

1.先说定义
构造函数中初始化是通过在构造函数的函数体内赋值来实现的。
成员初始化列表是通过在构造函数的参数列表后面用冒号分隔,然后列出成员变量名和它们的初始值来实现的。

2.再说区别
使用成员初始化列表会更快一些的原因是因为在构造函数中初始化时,编译器会先调用默认构造函数来初始化成员变量,然后再将初始值赋给它们。这意味着在构造函数体内的赋值语句中,每个成员变量实际上被初始化了两次。而使用成员初始化列表时,成员变量被直接初始化为所需的值,这样就避免了不必要的初始化和赋值操作,从而提高了效率。此外,成员初始化列表还可以初始化const成员变量和引用类型成员变量,而构造函数体内则不能初始化这些成员变量。

3.总结
如果是在构造函数体内进行赋值的话,等于是一次默认构造加一次赋值,而初始化列表只做一次赋值操作。

简单来说就是,在构造函数中初始化时,编译器会先调用构造函数来初始化成员变量,再执行函数体内部的赋值(其实被赋值两次,第一次没有内容而已)。而使用成员初始化列表时,成员变量被直接初始化为所需的值(赋值一次)。

main函数的返回值有什么值得考究之处吗?

main函数的返回值是程序的退出状态码,用于指示程序的执行情况。在C语言中,main函数的返回值类型为int,可以返回任意整数值。

程序的退出状态码通常具有以下含义:

返回0表示程序正常结束。
返回正整数表示程序出现了错误或异常情况,具体的值可以表示不同的错误类型或错误码。
返回负整数表示程序被另一个进程或操作系统中止,具体的值也可以表示不同的中止原因或中止码。
程序的退出状态码可以被其他程序或操作系统捕捉并处理,例如Shell脚本可以根据程序的退出状态码来执行不同的操作。因此,main函数的返回值是程序的一个重要部分,应该根据程序的需求来选择合适的返回值。

另外需要注意的是,如果main函数没有显式地返回任何值,编译器会默认返回0作为程序的退出状态码。因此,即使程序没有显示地指定返回值,也应该在main函数中显式地返回0以表示程序正常结束。

拷贝初始化和直接初始化(赋值)的区别

直接初始化是使用构造函数的一个参数列表来初始化对象,例如:

num1(10); // 直接初始化一个 int 类型的对象
std::string str1("hello"); // 直接初始化一个 std::string 类型的对象
string str2(str1);//语句2 直接初始化,str1是已经存在的对象,直接调用拷贝构造函数对str2进行初始化

拷贝初始化是使用另一个对象来初始化一个新对象,例如:

num2 = num1; // 拷贝初始化一个 int 类型的对象
string str3 = "hello";//语句3 拷贝初始化,先为字符串”hello“创建临时对象,再把临时对象作为参数,使用拷贝构造函数构造str3
std::string str4 = str1; // 语句4 拷贝初始化,这里相当于隐式调用拷贝构造函数,而不是调用赋值运算符函数

需要注意的是,拷贝初始化不仅适用于对象之间的初始化,还适用于函数返回值和表达式求值的结果,例如:

std::string getName() {
    return "Alice";
}
std::string name = getName(); // 拷贝初始化一个 std::string 类型的对象

在使用拷贝初始化时,如果要从一个对象中拷贝构造函数的参数,则会调用该对象的拷贝构造函数,如果该对象没有定义拷贝构造函数,则会调用其移动构造函数(如果有定义),否则会产生编译错误。

数组首元素地址a和数组地址&a有什么区别?

假设数组int a[10]; int (*p)[10] = &a;其中:

  1. a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。a+1就是第二个元素的地址,*(a + 1) = a[1]。

  2. &a是数组的指针,其类型为int (*)[10](就是下面提到的数组指针),&a+1,系统会认为是数组首地址加上整个数组的偏移(10个int型变量)就是向后移动(10 * 4)个单位,值为数组a尾元素后一个元素的地址。若(int *)p ,此时输出 *p时,其值为a[0]的值,因为被转为int *类型,解引用时按照int类型大小来读取。

什么情况会自动生成默认构造函数?

  1. 没有定义任何构造函数:如果我们没有定义任何构造函数,则编译器会自动生成一个默认构造函数。

  2. 定义了其他构造函数:如果我们定义了其他构造函数,但没有定义默认构造函数,则编译器会自动生成一个默认构造函数。

  3. 派生类没有显式定义构造函数:如果派生类没有显式定义构造函数,而基类没有提供带参数的构造函数,则编译器会自动生成默认构造函数。

  4. 带有虚函数的类,虚函数的引入需要进入虚表,指向虚表的指针,该指针是在构造函数中初始化的,所以没有构造函数的话该指针无法被初始化;

  5. 带有一个虚基类的类
    还需要注意的是:

  6. 并不是任何没有构造函数的类都会合成一个构造函数

  7. 编译器合成出来的构造函数并不会显示设定类内的每一个成员变量
    来源:牛客网

ARM

ARM协处理器指令包括哪3类,请描述它们的功能

ARM协处理器指令包括以下3类:

  1. 用于ARM处理器初始化ARM协处理器的数据处理操作。
  2. 用于ARM处理器的寄存器和ARM协处理器的寄存器间的数据传送操作。
  3. 用于在ARM协处理器的寄存器和内存单元之间传送数据。

编写一个通用型串口接收程序,如何实现判断未知设备的端口号和波特率?

  1. 判断端口号:首先搜索计算机可用端口,然后逐个打开,分别定时1.5秒,有数据进来则认为是目标端口。

  2. 波特率判断:将本机波特率分别设置为2400、4800、9600、19200、38600、57600、115200,每个波特率定时1.5秒,对收到的ASCII码进行分析,将小于等于127的认为是正常的字符,将大于127的认为是乱码,当正常字符数目大于等于10倍乱码数目则认为当前波特率为正确波特率,并终止往后搜索。另外,也可以通过示波器判断。

任务切换的时候,寄存器怎么工作的

通过保存寄存器堆数据值和装载寄存器堆数据实现任务切换。保存寄存器堆数据称为保存现场 ,载入寄存器堆数据称为恢复现场。保存现场和恢复现场称为上下文切换。

应用编程和网络编程

空闲的进程和阻塞的进程状态会不会在唤醒的时候误判?

不会。每个进程有个进程控制块PCB,两种状态的进程分别处于两种队列。唤醒应该是找阻塞队列的进程。

在操作系统中,进程状态的转换是由内核管理的,通常情况下,内核会对进程的状态进行严格的控制,不会出现误判的情况。当一个进程被阻塞或空闲时,内核会记录其状态,并将其从可执行队列中移除,直到满足某些条件(如I/O操作完成)才会将其唤醒并放回可执行队列中。

在唤醒进程时,内核会根据进程当前的状态和优先级来决定是否将其放回可执行队列中,或者将其放回阻塞队列或空闲队列中。通常情况下,内核会根据进程当前的状态和优先级进行正确的判断,从而避免误判的情况。

然而,在多线程或多进程的并发环境中,可能会出现竞争条件等问题,从而导致进程状态的混乱或错误。因此,在设计并发程序时,需要考虑并发问题,并使用同步机制(如锁、信号量等)来保护共享资源,避免竞争条件等问题的出现。

什么是Shell?

Shell是一个计算机程序,它是在操作系统上运行的命令行解释器。它是用户和操作系统之间的接口,允许用户与操作系统交互。用户可以使用Shell来输入命令,然后Shell将这些命令解释并传递给操作系统执行。Shell还可以读取环境变量和配置文件,并在用户输入命令后提供命令历史记录和自动补全功能。

在Unix/Linux操作系统中,常用的Shell包括Bash(Bourne-Again SHell)、Korn Shell(ksh)、C Shell(csh)、T Shell(tcsh)等。每个Shell都有自己的特性和语法,但它们都提供了基本的命令行接口,以便用户可以与操作系统交互。Shell还可以用来编写脚本,以便执行自动化任务和批处理操作。

驱动

上下文有哪些?怎么理解?

上下文简单说来就是一个环境。 用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

进程上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。

  1. 用户级上下文: 正文、数据、用户堆栈以及共享存储区;

  2. 寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);

  3. 系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、 pte)、内核栈。 当发生进程调度时,进行进程切换就是上下文切换(context switch).操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。而系统调用进行的模式切换(mode switch)。模式切换与进程切换比较起来,容易很多,而且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文的切换。

硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“ 中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。中断时,内核不代表任何进程运行,它一般只访问系统空间,而不会访问进程空间,内核在中断上下文中执行时一 般不会阻塞。