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

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

C/C++

手写atoi函数

功能:将字符串转换成整型数;atoi()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负号才开始做转换,而再遇到非数字或字符串时('\0')才结束转化,并将结果返回(返回转换后的整型数)。

int my_atoi(char* str){
    assert(str);
    long long ans=0;
    int sign = 1;

    while(*str == ' ') str++;
    if(*str=='-'){sign=-1;str++;}
    if(*str=='+')str++;
    while(*str>='0'&&*str<='9')
    {
        ans=ans*10+sign*(*str-'0');
        str++;
        if(ans>0 && ans > INT_MAX)ans =INT_MAX;
        else if(ans<0&& ans < INT_MIN)ans =INT_MIN;
    }
    return ans;
}

C++有哪几种构造函数

1.默认构造函数

没有任何参数的构造函数被称为默认构造函数。如果没有定义构造函数,则编译器会自动提供默认构造函数。默认构造函数可以用来创建对象,但是不能传递任何参数。

class MyClass {
public:
    MyClass() {
        // 这里可以对成员变量进行初始化
    }
};

2.带参数的构造函数

带有一个或多个参数的构造函数被称为带参数的构造函数。带参数的构造函数可以用来初始化对象的成员变量,可以接受一个或多个参数。

class MyClass {
public:
    MyClass(int a, int b) {
        // 这里可以对成员变量进行初始化,使用参数a和b
    }
};

3.拷贝构造函数
(拷贝其他对象的构造函数)
用于从一个已经存在的对象中创建一个新的对象的构造函数被称为拷贝构造函数。拷贝构造函数接受一个参数,这个参数是同类型的另一个对象的引用。它通常用于在函数参数传递或返回对象时,或者在对象赋值时进行对象的拷贝。

class MyClass {
public:
    MyClass(const MyClass& otherClass) {
        // 这里可以从另一个同类型的对象other中拷贝成员变量的值
    }
};

4.移动构造函数

用于从一个已经存在的临时对象中创建一个新的对象的构造函数被称为移动构造函数。它通常用于在对象的值被转移(比如将一个临时对象转移给一个新对象)时,避免不必要的拷贝操作,从而提高代码的性能。

class MyClass {
public:
    MyClass(MyClass&& otherClass) {
        // 这里可以从另一个同类型的临时对象other中移动成员变量的值
    }
};

5.复制赋值运算符

复制赋值运算符用于将一个对象的值赋给另一个对象。它是一个函数,它接受一个同类型的参数,并返回一个同类型的引用。如果没有定义复制赋值运算符,则编译器会自动生成一个默认的复制赋值运算符。

class MyClass {
public:
    MyClass& operator=(const MyClass& otherClass) {
        // 这里可以将另一个同类型的对象other的值赋给自己的成员变量
        return *this;
    }
};

6.移动赋值运算符

移动赋值运算符用于将一个临时对象的值转移到一个新的对象中。它是一个函数,它接受一个同类型的参数,并返回一个同类型的引用。它通常用于在对象的值被转移时,避免不必要的拷贝操作,从而提高代码的性能。

class MyClass {
public:
    MyClass& operator=(MyClass&& otherClass) {
        // 这里可以从另一个同类型的临时对象other中移动成员变量的值到自己的成员变量
        return *this;
    }
};

程序在执行int main(int argc, char *argv[])时的内存结构,你了解吗?

参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。

当main函数被调用时,操作系统会将栈空间的一部分分配给该函数使用。argc和argv参数是通过栈空间传递给main函数的,它们通常被存储在栈的底部。main函数的局部变量也存储在栈空间中,它们的大小和数量可以根据程序需要动态地变化。函数调用过程中,每次调用都会在栈空间中创建一个新的栈帧,用于存储被调用函数的局部变量和返回地址。

在程序执行过程中,操作系统会根据程序的内存使用情况和系统的可用内存动态地调整进程地址空间的大小,以确保程序能够正常运行并不会耗尽系统的内存资源。

如何设计一个计算仅单个子类的对象个数?

可以在基类中定义一个静态成员变量,用于记录所有派生类对象的个数,再定义一个静态成员函数,用于返回当前子类对象的个数。然后在每个派生类的构造函数和析构函数中更新静态成员变量的值。

以下是一个示例:

// 基类
class Base {
protected:
    static int count;  // 静态成员变量,用于记录所有派生类对象的个数

public:
    static int getCount() {  // 静态成员函数,用于返回当前子类对象的个数
        return count;
    }
};

int Base::count = 0;  // 静态成员变量的初始化

// 派生类 A
class A : public Base {
public:
    A() {
        count++;  // 在构造函数中增加对象个数
    }

    ~A() {
        count--;  // 在析构函数中减少对象个数
    }
};

// 派生类 B
class B : public Base {
public:
    B() {
        count++;  // 在构造函数中增加对象个数
    }

    ~B() {
        count--;  // 在析构函数中减少对象个数
    }
};

在上面的代码中,基类 Base 定义了一个静态成员变量 count,它用于记录所有派生类对象的个数,并且定义了一个静态成员函数 getCount(),用于返回当前子类对象的个数。在派生类的构造函数和析构函数中,分别对 count 进行增加和减少操作。这样,在任何时候,通过调用派生类的 getCount() 方法,就可以获取该派生类的对象个数了。

由于派生类的构造函数和析构函数都会调用基类的构造函数和析构函数,因此在这里不需要再分别调用基类的构造函数和析构函数。另外,为了防止派生类的构造函数和析构函数被误调用,它们应该被定义为 protected,而不是 public。

堆的动态申请释放时注意的点

  • 判断成功申请到空间
  • 释放后指向NULL
#include <stdio.h>
#include  <stdlib.h>
int main()
{
   char *pt;
   pt= (char *)malloc(10); //堆上申请空间(malloc的输入参数,是申请空间的字节数)
                           //成功 返回值是申请空间的地址,失败返回NULL;
   if(pt </font>NULL){ 
      printf("申请空间失败");
      return -1;
   }
   *pt=0x11;
   *(pt+1)=0x22;
   printf("%x %x %x ",pt[0],pt[1],pt[2]);
   free(pt); //释放空间,避免内存泄漏
   pt =NULL; //避免野指针,操作已释放的空间
   return 0;
}

类对象的大小受哪些因素影响?

  1. 类的非静态成员变量:类的成员变量是影响类对象大小的主要因素,因为它们占用了对象的内存空间。成员变量的大小取决于其类型和内存对齐方式。通常,基本类型的成员变量大小是固定的,而自定义类型的成员变量大小取决于其定义的方式,例如是否有虚函数等。

  2. 继承关系:如果一个类继承自另一个类,那么它的对象大小就包括了父类的对象大小和自己的对象大小。继承关系还会影响类对象的内存布局,例如如果一个派生类覆盖了基类的虚函数,那么在对象中会包含一个指向虚函数表的指针,占用额外的内存空间。

  3. 虚函数:如果一个类定义了虚函数,那么该类的对象通常会包含一个指向虚函数表的指针,用于实现动态绑定。这个指针占用了额外的内存空间,会增加对象的大小。

  4. 内存对齐方式:为了提高内存访问的效率,编译器会将对象的内存空间进行对齐,使得每个成员变量的地址都是对齐方式的倍数。对齐方式通常是由编译器和处理器决定的,不同的处理器和编译器对齐方式可能不同,这也会影响对象的大小。

ARM

ARM CPU上的地址转换涉及哪三个概念?

(1)虚拟地址(VA):CPU内核对外发出VA。

(2)变换后的虚拟地址(MVA, Modified Virtual Address):VA被变换为MVA供cache和MMU使用,由硬件自动完成。如果VA < 32M,则需要使用PID来转换为MVA(VA | (PID << 25)),目的是当两个进程的虚拟地址空间有重叠时,把重叠的VA映射到不同的PA上去,减少切换进程的代价。

(3)物理地址(PA):最后使用PA读写实际设备。

如何保证在侦听UART时不会一直判断接收到0

  1. 设置超时机制:设置一个超时机制,在一定时间内没有接收到有效数据时,就认为没有数据传输,从而避免一直判断接收到0。

  2. 检查数据的有效性:在判断接收到的数据时,可以检查数据的有效性,比如校验和、数据长度等,只有在数据有效时才认为接收到了有效数据。

  3. 使用中断方式接收数据:使用中断方式接收数据可以有效避免一直判断接收到0的情况,当接收到数据时,中断会被触发,处理函数会读取接收缓冲区中的数据。

ARM指令集分为几类?

ARM指令集可以按照不同的方式进行分类,这里列举其中几种分类方法:

  1. 指令集架构(ISA):ARM指令集根据ISA不同可以分为ARMv6、ARMv7、ARMv8等。

  2. 指令集类型:ARM指令集可以分为以下几类:
    (1)数据处理指令集:用于算术运算、逻辑运算、移位操作等,例如ADD、SUB、AND、ORR、LSL等指令。
    (2)访存指令集:用于读写内存,例如LDR、STR等指令。
    (3)分支指令集:用于实现程序的跳转和分支,例如B、BL等指令。
    (4)特权指令集:用于操作系统或者其他特权级别的代码使用,例如MRS、MSR等指令。

  3. 指令集编码:ARM指令集的编码可以分为两种类型,一种是32位指令,另一种是16位指令(也称为Thumb指令集),其中16位指令可以进一步分为Thumb和Thumb-2指令集。

  4. 指令集功能:根据指令所实现的功能可以将ARM指令集分为很多类别,例如乘法指令集、浮点指令集、SIMD指令集等。

1.4嵌入式微处理器和DSP有什么区别?

(1)嵌入式微处理器和DSP一个偏重控制、一个偏重运算。

(2)嵌入式微处理器外围接口丰富,标准化、通用性、功耗控制等做得很好,适用于消费电子、家用电器等控制领域。

(3)DSP对系统结构和指令做了优化,能进行大量数据的快速计算,适用于音视频处理等领域。

1.5 ARM处理器有哪些工作状态?ARM指令和Thumb指令有什么区别?

答案:

(1)ARM处理器共有ARM、Thumb/Thumb-2、调试三种状态。

(2)ARM指令是32位的,较全面;Thumb指令是16位的,较精简。

解读:

ARM状态 工作于32位指令状态,所有指令均为32位。
Thumb状态 工作于16位指令状态,所有指令均为16位。
Thumb-2状态 ARM状态和Thumb状态是早期版本,近期推出的Thumb-2状态兼有16和32位指令,具有更高的性能、更低的功耗以及更少的内存占用。具有Thumb-2技术的ARM处理器无需在ARM和Thumb-2状态之间切换了。
调试状态 处理器停机调试。

1.6 RISC(精简指令集计算机)和CISC(复杂指令集计算机)的区别?

(1)RISC控制器多采用硬件连线控制方式,以期更快的执行速度;而CISC控制器绝大多数采用微程序控制方式。

(2)RISC只有加载和存储指令可以访问内存,数据处理指令只对寄存器的内容操作,为了加速程序的运算,RISC会设置多组寄存器,并指定特殊用途的寄存器,因此通用寄存器数量较CISC多。CISC架构允许数据处理指令对内存进行操作,因此需要的寄存器数量比较少。

(3)RISC大多指令在一个时钟周期内完成,且指令长度统一、数目少;而CISC的复杂指令通过CPU内的微码来完成,需要多个时钟周期,且指令长度不一、数目多。

(4)RISC在实现一个功能的时候,需要的指令数目多,编译器设计就更复杂;CISC的指令丰富,它的编译器可以少做很多事情。

(5)RISC较CISC更易实现指令流水线,因为其指令大多在一个时钟周期内完成。

应用编程和网络编程

请问就绪状态的进程在等待什么?

就绪状态的进程并不是在等待某个特定的事件或资源,而是已经完成了所有必要的前置工作,可以立即被调度执行,但是由于系统资源有限,需要等待CPU时间片的分配,因此处于就绪状态。

拓展知识:
image
(1)就绪态:所有运行条件已就绪,只要得到了CPU时间就可运行。

(2)运行态:得到CPU时间正在运行。

(3)僵尸态:进程已经结束了但父进程还没来得及回收。

(4)等待态:包括浅度睡眠跟深度睡眠。进程在等待某种条件,条件成熟后即进入就绪态。浅度睡眠时进程可以被信号唤醒,但深度睡眠时必须等到条件成熟后才能结束睡眠状态。

(5)暂停态:暂时停止参与CPU调度(即使条件成熟),可以恢复。

1.5子进程从父进程继承的资源有哪些?

答案:子进程继承父进程的绝大部分资源,包括堆栈、内存、用户号和组号、打开的文件描述符、当前工作目录、根目录。

注意:子进程独有进程号、不同的父进程号、自己的文件描述符。

1.6什么是进程上下文、中断上下文?

(1)进程上文:是指进程由用户态切换到内核态时需要保存用户态时CPU寄存器中的值,进程状态以及堆栈上的内容。即保存当前进程的状态,以便再次执行该进程时,能够恢复切换时的状态,继续执行。

(2)进程下文:是指切换到内核态后执行的程序,即进程运行在内核空间的部分。

(3)中断上文:硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上文可以看作硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境)。

(4)中断下文:执行在内核空间的中断服务程序。

posted @ 2023-08-09 14:57  我好想睡觉啊  阅读(42)  评论(0编辑  收藏  举报