嵌入式工程师常见面试题(持续更新版本)

前言:鄙人于学堂求学十余载,终是要踏足江湖求一寸安身处!以前都忙着学习新东西以及做项目,现如今也需要面对找工作的压力。此篇博客是作者准备的嵌入式工程师常见的面试题目汇总,其答案包含网络搜索和作者自己感悟总结的,可能存在问题,如有大的纰漏希望诸位前辈矫正!当然,也希望该博客可以帮助同样求职的你,Respect(此篇博客将保持常年更新)!

第一章:进程线程的基本概念

1、什么是进程(Process),线程(Thread),有什么区别?

进程和线程都是操作系统中的基本概念,以下是它们的定义和区别:

  1. 进程(Process):
    进程是操作系统中分配资源和调度的基本单位。一个程序至少要开启一个进程,进程是由进程控制块(PCB)、程序段、数据段三部分组成。一个进程可以包含多个线程,这些线程共享进程的地址空间和资源,并且通过同步机制来协调对共享资源的访问。

  2. 线程(Thread):
    线程是进程的基本执行单元,是处理器调度的最小单位。一个进程中的每个线程都有自己的线程控制块(TCB)、指令指针(IP)、栈和寄存器组。线程的调度由操作系统负责,当一个线程完成了一次调度后,它将返回自己的进程空间,从而执行该进程的其他线程。

以下是进程和线程的区别:

  1. 地址空间:
    进程的地址空间是独立的,而线程的地址空间是进程的一部分。这意味着,进程之间的地址空间是相互独立的,而同一进程内的线程之间可以共享数据和内存。

  2. 资源拥有:
    进程拥有独立的资源,如内存、文件、I/O等,而同一进程内的线程之间可以共享这些资源。这种共享可以通过同步机制来协调,以确保多个线程对共享资源的访问不会发生冲突。

  3. 任务执行:
    进程是独立的任务执行单元,而线程是在进程内的任务执行单元。一个进程可以有多个线程,但一个线程只能属于一个进程。

  4. 崩溃风险:
    当一个进程的某个线程崩溃时,整个进程都会受到影响,因为其他线程也共享了该进程的地址空间和资源。相反,多个进程之间的相互独立性更高,一个进程的崩溃不会影响其他进程。

2、多进程(Multi-Process)、多线程(Multi-Thread)的优缺点?

多进程和多线程在操作系统中都具有其优缺点。以下是一些主要的优缺点:

多进程的优点:

  1. 独立性强:每个进程都有自己的地址空间和资源,进程之间相互独立,一个进程的崩溃不会影响其他进程。
  2. 资源隔离:由于每个进程都有自己的资源,不同进程之间的资源不会相互干扰,这使得多进程在资源管理方面更加稳定和安全。

多进程的缺点:

  1. 资源开销大:每个进程都需要分配独立的资源,包括内存、文件等,这使得系统需要为每个进程分配不同的资源,造成了资源开销较大的问题。
  2. 上下文切换:当多个进程同时运行时,处理器需要频繁地进行上下文切换,即保存和恢复进程的状态,这会带来一定的性能开销。

多线程的优点:

  1. 资源共享:同一进程内的线程可以共享数据和内存,这使得线程之间的协作更加高效。
  2. 效率高:由于线程之间共享地址空间和资源,使得多线程在执行时可以更快地访问共享数据,提高了执行效率。
  3. 调度方便:操作系统可以方便地对同一进程内的线程进行调度,使得线程之间的切换更加迅速。

多线程的缺点:

  1. 同步问题:由于线程之间共享数据和资源,因此需要引入同步机制来确保线程之间的访问不会发生冲突。然而,同步机制的实现可能会带来额外的开销和复杂性。
  2. 调度开销:尽管多线程的调度比多进程更高效,但仍然存在调度开销的问题。当线程数量较多时,调度开销可能会对系统性能产生一定的影响。
  3. 性能限制:当多个线程同时运行时,由于共享资源的限制,系统的性能可能会受到限制。例如,当某个线程被阻塞时,其他线程的执行也会受到影响。

综上所述,多进程和多线程在操作系统中都具有其优缺点。在实际应用中,需要根据具体场景和需求来选择合适的进程模型。在需要隔离和独立性要求较高的场景下,可以选择多进程;而在需要共享数据和协作频繁的场景下,可以选择多线程。同时,需要注意多进程和多线程可能带来的上下文切换、同步问题、调度开销等挑战,以及如何进行有效的优化和管理。 

3、多进程、多线程同步(通讯)的方法?

多进程和多线程之间的同步和通信可以通过以下几种方法实现:

  1. 管道(Pipe):管道是一种进程间通信的方式,可以在不同进程之间传递数据。可以使用管道函数如pipe()、read()和write()等来实现进程间通信。
  2. 共享内存(Shared Memory):共享内存是一种高效的进程间通信方式,它可以让多个进程共享同一块内存区域,从而实现数据共享。可以使用共享内存函数如shmget()、shmat()和shmdt()等来实现进程间通信。
  3. 信号量(Semaphore):信号量是一种同步机制,它可以用于控制多个进程之间的访问权限。可以使用信号量函数如semget()、semctl()和semop()等来实现进程间同步。
  4. 消息队列(Message Queue):消息队列是一种进程间通信方式,它可以让不同进程之间传递消息,并且具有消息的优先级和缓冲机制。可以使用消息队列函数如msgget()、msgrcv()和msgsnd()等来实现进程间通信。
  5. 管道和共享内存的组合:管道和共享内存可以组合使用,例如使用管道将数据写入共享内存中,然后其他进程可以从共享内存中读取数据。
  6. 套接字(Socket):套接字是一种更为通用的进程间通信方式,可以在不同主机之间传递数据。可以使用套接字函数如socket()、bind()、listen()和connect()等来实现进程间通信。

需要注意的是,多进程和多线程之间的同步和通信往往比较复杂,需要结合具体的场景来选择合适的同步和通信方式,并注意避免死锁、竞争条件等问题。

4、进程线程的状态转换图。什么时候阻塞,什么时候就绪?

推荐博客地址:进程的状态转换 - LRH呀 - 博客园 (cnblogs.com)

5、父进程、子进程的关系以及区别?

父进程和子进程之间的关系主要体现在子进程是由父进程通过操作系统提供的fork函数创建的。在fork函数调用后,父进程会复制自己的内存空间和文件描述符等信息给子进程,使得子进程获得与父进程相似的运行环境。

以下是父进程和子进程之间的区别:

  1. ID:子进程有一个新的进程ID,而父进程的进程ID不同于子进程的进程ID。
  2. 内存空间:子进程拥有独立的内存空间,与父进程的内存空间不完全相同。子进程的内存空间是父进程内存空间的一个副本,当父进程修改了某个内存地址上的数据后,子进程在同样的内存地址上的数据也会发生改变。
  3. 文件描述符:子进程继承了父进程的文件描述符,可以访问相同的文件。
  4. 状态和优先级:子进程继承了父进程的状态、优先级和信号屏蔽字等信息。
  5. 资源:子进程继承了父进程的资源,如打开的文件、信号量等。
  6. 父子进程之间是独立的,互相不共享代码段和数据段。

补充说明:Linux 系统创建的第 1 个进程叫 init 进程,它是所有进程的祖宗,此进程pid为1。有时候父子进程之间会出现,父进程运行结束了,在内存中销毁了。 而子进程还没运行结束,此时,linux会把子进程挂在linux中的init进程下。

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

进程上下文和中断上下文是指在操作系统中执行程序时,处理器所维护的两个不同的执行环境。

进程上下文:是在进程中执行的程序的上下文环境。在进程上下文中,处理器保存了程序运行时的所有状态信息,包括程序计数器、寄存器、堆栈指针、内存映射等。当一个进程被调度器调度时,操作系统会将进程的上下文保存到进程控制块(PCB)中,然后加载到内存中的适当位置,以便处理器可以正确地执行该进程。

中断上下文:是在中断处理程序中执行的上下文环境。当一个中断发生时,处理器会保存当前正在执行的进程的上下文,并跳转到中断处理程序的入口地址。在中断处理程序执行完毕后,处理器会恢复之前保存的进程上下文,并将控制权交回被中断的进程。

进程上下文和中断上下文的主要区别在于其用途和执行环境的差异。进程上下文主要用于保存和恢复进程的状态信息,以实现进程的切换和调度;而中断上下文则主要用于保存和恢复中断处理程序的状态信息,以实现中断的处理和返回。

7、一个进程可以创建多少线程,和什么有关

一个进程可以创建的线程数受到操作系统和计算机硬件的限制。

具体的限制因操作系统和硬件的不同而有所不同。在操作系统方面,一些操作系统可能会限制进程可以创建的最大线程数,例如,在一些嵌入式操作系统中,可能只允许创建几个线程。而在其他操作系统中,可能会允许创建更多的线程。例如Windows Server操作系统限制每个进程可以创建2000个线程。

此外,计算机硬件的性能也会对线程数产生影响。在实际情况中,如果创建过多的线程,可能会导致系统性能下降,因为每个线程都需要占用一定的内存和计算资源。因此,需要根据具体的计算机硬件和应用程序的需求来调整线程数。

总的来说,一个进程可以创建的线程数取决于操作系统和计算机硬件的限制,而具体的限制条件需要根据实际情况来确定。

8、线程通讯(锁):

这些锁都是用于线程同步和互斥的机制,以下是它们的简要介绍:

  1. 信号量(Semaphore):

信号量是一种计数信号,可以用于控制多个线程对共享资源的访问。它通常由一个计数器和一个等待队列组成,计数器用于记录可用的资源数量,当计数器为零时,表示没有可用的资源,线程将被阻塞。等待队列中保存了被阻塞的线程,直到有可用的资源为止。

  1. 读写锁(Read-Write Lock):

读写锁是一种用于控制对共享资源的读写访问的锁。它允许多个线程同时读取共享资源,但只允许一个线程进行写操作。读写锁的实现通常会根据读写操作的特性进行优化,以最大化并发性能。

  1. 条件变量(Condition Variable):

条件变量是一种用于线程之间的通信机制。它可以用于在某个条件满足时,唤醒一个或多个等待的线程。条件变量通常与互斥锁一起使用,用于实现复杂的同步和通知机制。

  1. 互斥锁(Mutex):

互斥锁是一种用于保护共享资源的机制。它允许多个线程同时访问共享资源的一部分,但只允许一个线程同时访问共享资源的整个区域。当一个线程需要访问共享资源的互斥锁时,其他线程将被阻塞,直到该线程释放锁为止。

  1. 自旋锁(Spinlock):

自旋锁是一种基于忙等待的锁机制。当一个线程尝试获取自旋锁时,如果自旋锁已经被其他线程占用,则该线程会在循环中等待自旋锁被释放。自旋锁的好处是可以避免线程进入阻塞状态,从而减少上下文切换的开销,但缺点是可能会消耗大量的CPU时间。

这些锁都是为了实现线程同步和互斥而设计的机制,但每种锁都有其特点和应用场景,需要根据实际情况选择合适的锁机制。

9、什么叫临界区?

临界区是指一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性,需要在同一时间只能被一个线程执行。临界区用于保护共享资源,以避免多个线程同时访问或修改造成的数据竞争和不确定性。

第二章:C/C++面试题

1、new 和 malloc 的区别?

new 和 malloc 都是用于动态分配内存的方法,但是它们在以下方面存在一些区别:

  1. 内存分配位置:new 操作从自由存储区为对象动态分配内存空间,而 malloc 函数从堆上动态分配内存。自由存储区不仅可以是堆,还可以是静态存储区,这取决于 new 在哪里为对象分配内存。
  2. 返回类型安全性:new 操作符内存分配成功时,返回的是对象类型的指针,与对象类型严格匹配,无需进行类型转换,因此 new 是符合类型安全性的操作符。而 malloc 函数在内存分配成功后返回的是 void*,需要通过强制类型转换将 void* 指针转换成所需类型。
  3. 内存分配失败时的返回值:当 new 内存分配失败时,会抛出 bac_alloc 异常,不会返回 NULL;而 malloc 内存分配失败时,返回 NULL。
  4. 是否需要指定内存大小:使用new申请内存时,不需要指定内存块的大小,编译器会根据类型信息自行计算;而malloc则需要显式地指出所需内存块的大小。

总的来说,new和malloc在内存分配位置、返回类型安全性、内存分配失败时的返回值以及是否需要指定内存大小等方面存在差异。

2、malloc的底层实现

(1)当开辟的空间小于 128K 时,调用 brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)

(2)当开辟的空间大于 128K 时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。

3、 在1G内存的计算机中能否malloc(1.2G)?为什么?

在1G内存的计算机中,无法使用malloc(1.2G)。

在C语言中,malloc()函数用于动态分配内存。当使用malloc()请求超过可用内存的大小时,会发生两种情况:

  1. 分配失败:如果请求的内存大小超过了操作系统可用的内存大小,malloc()将返回NULL,表示分配失败。在这种情况下,程序将无法继续执行。
  2. 分配成功但导致崩溃:如果请求的内存大小超过了可用的内存大小,但malloc()仍然返回了非NULL的指针,这通常是由于操作系统使用了内存映射技术或虚拟内存机制。这种情况下,程序可能会继续执行,但它可能会遇到未定义的行为,例如访问已分配的内存时崩溃或导致性能下降。

在1G内存的计算机中,由于可用内存只有1GB,因此无法使用malloc(1.2G),因为这将请求超过可用内存大小的内存大小。如果尝试这样做,malloc()将返回NULL,并且程序将无法继续执行。

4、指针与引用的相同和区别;如何相互转换?

引用时C++独有的特性,而指针则是C/C++都有的,它们有一些相似之处,但也有很多区别。

  1. 相同点:
  • 它们都是用来间接访问内存的方式。
  • 它们本身都占用内存(指针占用内存来存储所指向对象的地址,引用则是对象的别名,所以其实引用本身也是对象)。
  • 它们都可以用来访问动态分配的内存(使用malloc()等函数)。
  1. 不同点:
  • 指针是一个实体,它存储的是一个地址,指向内存的一个存储单元。而引用是原变量的一个别名,它存储的是原变量的值。
  • 指针可以被重新赋值,指向不同的对象,而引用在声明后就不能改变其指向的对象。
  • 指针可以被const修饰,而引用不能被const修饰。
  • sizeof运算符在操作引用时得到的是对象本身的大小,而操作指针时得到的是指针变量本身的大小。
  1. 相互转换:
  • 指针转引用:将指针变量的值直接赋值给引用变量即可。
  • 引用转指针:可以使用类型转换操作符(*)将引用转换为指针。

总的来说,指针和引用在功能和使用上有一些区别,需要根据具体情况选择使用哪种方式。

5、extern"C”的作用

extern "C"是一个C语言链接器的关键字,它有以下几个作用:

  1. 使得在C++中使用C编译方式成为可能,指明该函数使用C编译方式。

  2. 在某些情况下,使用extern "C"声明函数,可以使得C++编译器按照C语言的方式对函数进行编译和链接,从而能够正确地调用该函数。

  3. extern "C"可以用于在C++中调用由C语言编写的库函数。因为C++与C语言的函数调用方式不同,使用extern "C"可以指明该函数使用C语言的方式进行链接,避免因为链接方式不同而导致的错误。

  4. 在使用动态链接库(DLL)时,使用extern "C"可以保证函数的导出和导入与C语言一致。

总之,extern "C"的作用是为了在C++中使用C语言的编译和链接方式,并且在一些特定情况下保证函数的正确导出和导入。

6、重写memcpy()函数需要注意哪些问题,(strcat strncat strcmp strcpy)那些函数会导致内存溢出?

重写 memcpy() 函数时需要注意以下问题:

  1. 源地址和目标地址的正确性:确保源地址和目标地址是有效的、合法的内存地址。在复制过程中,如果源地址无效或目标地址不足,会导致错误或未定义的行为。
  2. 复制的字节数:确保复制的字节数不超过目标地址的剩余空间,以避免内存溢出。在复制过程中,如果目标地址的剩余空间不足以容纳要复制的数据,会导致内存溢出。
  3. 目标地址的内存分配:如果目标地址是动态分配的内存区域(例如通过 malloc() 函数分配的内存),则在复制之前需要确保目标地址的内存已经分配,并且在复制完成后及时释放,以避免内存泄漏。
  4. 边界检查和错误处理:对于类似于 strcat()strncat()strcmp()strcpy() 等函数,在处理字符串时需要注意边界检查,确保输入的字符串不会超过目标地址的限制。对于错误的输入,应该有适当的错误处理机制,例如报错或者返回错误码。

下面是关于 strcat()strncat()strcmp()strcpy() 函数的一些注意事项:

  • strcat() 函数用于将一个字符串追加到另一个字符串的末尾。如果目标字符串的长度不足,或者目标字符串的剩余空间不足以容纳要追加的数据,就会导致内存溢出。
  • strncat() 函数类似于 strcat(),但是可以指定要复制的字节数。在使用 strncat() 时,需要确保目标字符串的剩余空间足够大,以避免内存溢出。
  • strcmp() 函数用于比较两个字符串是否相等。这个函数不会导致内存溢出,但是需要注意输入的字符串长度,如果输入的字符串过长可能会导致性能问题。
  • strcpy() 函数用于将一个字符串复制到另一个字符串中。在使用 strcpy() 时,需要确保目标字符串的长度足够大,以避免内存溢出。此外,还需要注意源字符串是否具有足够的空间,以避免访问越界的问题。

7、char 和 int 之间的转换

在C/C++中,可以使用以下方式将char和int之间进行转换:

  1. 将char转换为int:

    • 使用强制类型转换,将char的ASCII码值转换为int。例如,对于char c = 'A';,可以使用int i = (int)c;来将c转换为int类型的i。
    • 如果char类型的数据只包含ASCII码值,可以使用自然的隐式转换。例如,对于char c = 'A';,可以使用int i = c;来将c转换为int类型的i。
  2. 将int转换为char:

    • 如果int类型的数据是ASCII码值,可以直接赋值给char类型的变量。例如,对于int i = 65;,可以使用char c = i;来将i转换为char类型的c。
    • 如果int类型的数据不是ASCII

8、static的用法(定义和用途) static静态变量,只初始化一次

在编程中,static 是一个关键字,用于指明变量或函数的特性。在C语言中,static 可以用于以下两种情况:

  1. 定义静态变量:

    • 静态变量是在程序执行期间只初始化一次的变量。它们在程序开始时被分配内存,并在程序结束时被释放。
    • 静态变量默认值为0,但也可以在定义时显式地初始化。
    • 静态变量可以用于记录某些状态或计数器,因为它们不会在程序运行期间被重新初始化。
  2. 定义静态函数:

    • 静态函数是在程序执行期间只初始化一次的函数。它们在程序开始时被分配内存,并在程序结束时被释放。
    • 静态函数只在其定义的模块中被使用,不能被其他模块直接调用。
    • 静态函数可以用于实现某些特定的功能,例如加密、解密、字符串处理等。

9、const的用法(定义和用途)

  1. 告诉编译器该变量的值不能被修改:
    使用 const 关键字定义变量可以明确告知编译器该变量的值不可被修改,这有助于编译器进行类型检查和代码优化。

  2. 避免不必要的内存分配:
    使用 const 关键字定义变量可以避免不必要的内存分配,因为编译器会将这些常变量存储在符号表中,而不是在内存中。

  3. 在函数中使用:
    在函数中使用 const 关键字可以确保函数不会修改传入参数的值,这有助于提高函数的可读性和安全性。同时,函数也可以返回 const 类型的值,以确保函数返回的值不会被修改。

10、const 常量和 #define 的区别(编译阶段、安全性、内存占用等)

const 常量和#define 都是在C/C++中用于定义常量,但它们之间有一些重要的区别。

  1. 定义方式:
    const 关键字用于定义常量,它是类型安全的,可以用于声明各种类型的常量,如整数、浮点数、字符等。而#define 是通过预处理器定义的,它可以定义任何类型的常量,包括字符串和其他复杂的表达式。

  2. 编译阶段:
    const 是在编译阶段处理的,它在编译时被解析为常量表达式,并且可以直接内联到代码中。而#define 是由预处理器在编译预处理阶段处理的,它会被替换为相应的常量表达式。

  3. 类型安全:
    const 关键字是类型安全的,它需要在声明时指定类型,并且在编译时进行类型检查。而#define 没有类型检查,它只是一个简单的文本替换,没有类型检查和语法检查。

  4. 内存占用:
    const 常量是静态分配内存的,它们被存储在程序的数据区中,并且在编译时就已经确定了其值。而#define 只是一个符号,在编译预处理阶段被替换为相应的值,因此它不会分配内存,只是在编译时进行了一次文本替换。

  5. 可读性和安全性:
    const 常量具有更好的可读性和安全性,它们可以提供更明确的语义,告诉读者该值是不能被修改的。而#define 只是一个预处理器指令,容易被误用或滥用,因此它的可读性和安全性相对较低。

综上所述,const 常量提供了更好的类型安全性和可读性,并且在编译时进行内存分配,同时也具有更好的性能。而#define 则更灵活,可以在编译预处理阶段进行一些复杂的操作,但它的类型安全性和可读性相对较低。在实际编程中,应根据具体情况选择使用 const 常量还是 #define。

11、 volatile作用和用法

volatile 是一个修饰符,用于声明在多线程环境下可能会被意外修改的变量。

在多线程环境下,由于有多个线程同时访问共享变量,可能会导致一些意外的行为,如一个线程正在修改变量时,另一个线程正在读取该变量,读取到的值可能不是最新的值。这种情况下,就需要使用 volatile 关键字来确保变量的可见性和一致性。

具体来说,使用 volatile 关键字可以保证以下几点:

  1. 保证变量的可见性:当一个线程修改变量的值时,其他线程会立即看到这个最新的值。

  2. 防止指令重排:由于编译器可能会对代码进行优化,导致一些指令的执行顺序与代码中的顺序不一致。使用 volatile 关键字可以防止这种指令重排,确保代码的执行顺序与代码中的顺序一致。

需要注意的是,虽然使用 volatile 关键字可以保证变量的可见性和一致性,但它不能保证线程之间的同步。如果需要保证线程之间的同步,还需要使用其他的同步机制。

12、变量的作用域(全局变量和局部变量)

变量的作用域是指变量在程序中可以使用的范围。在C/C++中,变量可以分为全局变量和局部变量两种。

  1. 全局变量(global variable):
    全局变量是在函数外部定义的变量,它们的作用域是整个程序,可以从头文件一直使用到程序结束。全局变量通常在程序启动时初始化,并且可以被程序中的多个函数共同使用。
  2. 局部变量(local variable):
    局部变量是在函数内部定义的变量,它们的作用域仅限于函数内部。当函数执行结束时,局部变量会被销毁,其内存空间也会被释放。

13、sizeof 与 strlen (字符串,数组)

sizeof() 是 C/C++ 中的运算符,用于获取变量或数据类型在内存中所占用的字节数。而 strlen() 是 C/C++ 中的函数,用于计算字符串的长度(不包括字符串结束符 '\0')。

对于字符串和数组,sizeof() 和 strlen() 的使用有以下区别:

  1. 字符串:

    • sizeof() 运算符用于获取整个字符串占用的字节数,包括字符串结束符 '\0'。例如,对于 char str[] = "hello";,sizeof(str) 的结果是 6(包括 '\0')。
    • strlen() 函数用于获取字符串的长度,即从字符串的起始地址开始,直到遇到结束符 '\0' 前,所经过的字节数。例如,对于 char str[] = "hello";,strlen(str) 的结果是 5。
  2. 数组:

    • sizeof() 运算符用于获取整个数组占用的字节数。例如,对于 int arr[] = {1, 2, 3};,sizeof(arr) 的结果是 sizeof(int) * 数组长度(即 sizeof(int) * 3)。
    • strlen() 函数通常不用于数组,因为数组没有结束符 '\0',无法计算其长度。

需要注意的是,sizeof() 运算符的结果是一个编译时确定的常量表达式,而 strlen() 函数需要在运行时逐个字符地计算字符串的长度。在使用时需要根据具体情况选择合适的函数,并注意避免越界访问和空指针异常等问题。

14、经典的sizeof(struct)和内存对齐(一字节对齐) 

在C语言中,sizeof()运算符用于获取变量或数据类型的大小。对于struct类型,sizeof()运算符将返回整个struct的大小,而不是每个成员变量的大小之和。这是因为C语言中的struct成员变量的对齐方式可能会导致struct的大小不是成员变量大小的整数倍。

内存对齐是为了提高内存访问的效率而引入的一种内存布局技术。在32位系统中最小的对齐单位是4字节,在64位系统中最小的对齐单位是8字节。根据对齐单位,编译器会在变量或结构体成员之间插入填充字节,使得它们的起始地址满足对齐要求。

对于一字节对齐,它通常用于一些特定的数据类型,例如char类型,它占用的空间就是1个字节。对于一些需要按照字节进行访问的数据类型,例如位域(bit-fields),也需要使用一字节对齐来保证数据的正确性。

在使用sizeof()运算符时,需要注意以下几点:

  1. 对于结构体或联合体类型,sizeof()返回的大小是整个结构体或联合体的占据的内存空间,而不是每个成员变量的大小之和。
  2. 在进行内存对齐时,需要考虑结构体成员变量的类型、大小和顺序,以及编译器和系统的对齐要求。合理地规划内存布局可以提高程序的性能和效率。
  3. 对于一些特殊的对齐要求,例如一字节对齐,需要使用特定的语法或技巧来实现,例如使用#pragma pack指令或者在结构体中使用特定的对齐修饰符(如__attribute__((packed))。

总之,sizeof()运算符和内存对齐是C语言中重要的概念,需要熟悉和掌握它们的使用方法和注意事项,以便更好地管理内存和提高程序的性能和效率。

推荐博客地址:http://t.csdn.cn/sAlrc

15、const * char 与const char * 

"const * char" 和 "const char *" 都是C语言中的指针声明,但它们有一些不同之处。

  1. "const * char":

    • 这个声明表示一个指向常量字符的指针。这里的"const"修饰的是指针所指向的数据,即字符数据是不可修改的,而指针本身是可修改的。
    • 可以用作指向字符数组的首地址,或者指向字符串的地址。
  2. "const char *":

    • 这个声明表示一个指向字符常量的指针。这里的"const"修饰的是指针本身,即指针自身是
      不可修改的,而指针所指向的数据是可修改的。
    • 通常用于指向字符串字面值或字符常量。

总结来说,"const * char"表示一个指向常量字符的指针,而"const char *"表示一个指向字符常量的指针。在C语言中,对于字符串字面值或字符常量,通常使用"const char *"来声明指针。

16、inline函数

inline 是一个C/C++中的关键字,用于告诉编译器该函数的实现应该在编译时进行内联。内联函数的目的是为了减少程序的运行时开销,通过在编译时将函数的调用替换为函数的实现,避免了函数调用的额外开销,提高了程序的执行效率。

17、内存四区,什么变量分别存储在什么区域,堆上还是栈上。

在执行一个C/C++语言程序时,会将程序分配到的内存分为四个区域:栈区、堆区、全局区(静态区)和代码区。每个程序都有唯一的四个内存区域,我们需要熟悉和了解各个区域的特性,例如存储什么类型的数据,有谁去申请开辟,又有谁去管理释放。

  • 栈区:栈区是用来存储函数调用和局部变量的一块内存区域,它是在程序执行时自动分配的,并且只在函数调用时使用,不会造成内存泄漏。在函数返回后,栈区会自动释放。
  • 堆区:堆区是用来存储动态分配的内存的一块内存区域,它是由程序员分配和释放的。在程序执行时,通过malloc等函数从堆区分配一块内存,使用完后需要手动释放,否则会造成内存泄漏。
  • 全局区(静态区):全局区是用来存储全局变量和静态变量的一块内存区域,它是在程序执行时就已经分配好并且一直存在直到程序结束时才被释放。
  • 代码区:代码区是用来存储程序代码的一块内存区域,它是在程序执行时就已经分配好并且一直存在直到程序结束时才被释放。

需要注意的是,这四个区域是按照顺序依次排列的,并且每个区域的存储数据类型和申请方式都有所不同,需要根据具体情况进行管理和使用。

第三章 :网络编程

1 、TCP、UDP的区别

TCP(传输控制协议)和UDP(用户数据报协议)是两种常见的网络传输协议,它们有以下区别:

  1. 连接:TCP是面向连接的协议,而UDP是无连接的协议。在发送数据前,TCP通过三次握手建立连接,而UDP不需要建立连接。
  2. 可靠性:TCP提供可靠的数据传输,保证了数据的可靠性和完整性。TCP通过序号、确认应答和超时重传等机制来实现可靠性。而UDP不提供可靠性保证,数据可能丢失、损坏或重复。
  3. 传输速度:UDP比TCP快,因为它不需要建立连接和提供可靠性保障。在一些场景中,如视频、音频等实时应用,UDP更适合。
  4. 数据量限制:UDP的数据包大小通常受限于底层网络协议(如以太网),一般不超过1472字节。而TCP的数据包大小则取决于操作系统和底层网络协议。
  5. 应用场景:TCP适用于可靠性要求高的应用场景,如网页浏览、电子邮件等。UDP适用于对速度和实时性要求高的应用场景,如在线游戏、视频流等。
  6. 头部开销:TCP的首部较大,为20个字节,而UDP的首部较小,为4个字节。

总的来说,TCP提供了可靠的连接和数据传输,但相对较慢;而UDP快,但数据传输不可靠。在选择传输协议时,需要根据具体的应用场景来权衡。

2、TCP、UDP的优缺点

TCP和UDP都有各自的优缺点,以下是它们的比较:

TCP的优点:

  1. 可靠:TCP提供了可靠的连接,保证了数据的可靠性和完整性,适用于对数据准确性要求高的场景。
  2. 高速:TCP的传输速度相对较快,尤其是在底层网络支持的情况下。
  3. 适应性:TCP的协议规范和实现独立于底层网络协议,可以适应不同的网络环境。
  4. 多路复用:TCP通过连接机制实现了多路复用,可以同时处理多个数据流,提高了传输效率。

TCP的缺点:

  1. 连接管理:TCP需要建立连接和断开连接的过程,这可能会导致一定的开销和延迟。
  2. 数据顺序:TCP在传输过程中需要维护数据顺序,如果某个数据包丢失或乱序,需要重新发送,这可能会影响传输速度。
  3. 数据量限制:TCP的数据包大小受限于底层网络协议,如以太网的最大传输单元(MTU),超过该限制可能导致数据被分割为多个小包传输。
  4. 流量控制:TCP的传输速率受限于底层网络协议的带宽,如果发送速率过快,可能导致数据包丢失或传输受阻。

UDP的优点:

  1. 快速:UDP的传输速度相对较快,尤其适用于实时性要求高的场景,如在线游戏、视频流等。
  2. 灵活性:UDP的数据包大小灵活,可以根据应用需要调整,同时不需要维护数据顺序。
  3. 简单:UDP的协议相对简单,易于实现和维护,适用于一些轻量级的应用场景。
  4. 多路复用:UDP的多路复用机制可以提高传输效率,同时处理多个数据流。

UDP的缺点:

  1. 不可靠:UDP不提供可靠性保障,数据可能丢失、损坏或重复,适用于一些对数据准确性要求不高的场景。
  2. 数据顺序:UDP的数据包顺序需要应用程序自行处理,适用于一些简单的应用场景。
  3. 数据量限制:UDP的数据包大小通常受限于底层网络协议(如以太网),一般不超过1472字节。4. 流控制:UDP的发送速率受限于底层网络协议的带宽和接收方的能力,可能导致数据包丢失或传输受阻。
  4. 数据错误处理:UDP的数据包在传输过程中可能发生错误,如损坏或重复,应用程序需要自行处理这些问题。
  5. 连接管理:UDP没有建立连接和断开连接的过程,因此不需要维护连接状态,这使得它更加轻量级,适用于一些简单的应用场景。

3、TCP UDP适用场景

TCP和UDP都有各自的适用场景。

TCP(传输控制协议)通常用于对准确性要求相对高的场景,比如文件传输、接受邮件和远程登录。TCP在传输数据时需要进行数据的确认、重发、排序等操作,因此相对来说效率没有UDP(用户数据报协议)高。

UDP常用于对实时性要求高的场景,例如在线游戏、流媒体传输。UDP不可靠,因此它不适用于需要保证数据完整性和可靠性的应用场景。但因为UDP不需要进行连接管理、确认和重传等操作,所以它的数据传输速度比TCP快。同时,UDP还支持多播技术,可以将数据包发送到多个目的地。

总的来说,TCP和UDP都有各自的优点和适用场景,选择使用哪一种协议,需要根据具体的需求来决定。

4、TCP为什么是可靠连接

TCP之所以被认为是可靠连接,是因为它采用了以下几种机制来确保数据的可靠传输:

  1. 确认和应答机制:TCP通过发送数据后等待接收方的确认,以及在数据传输过程中采用应答机制,确保每个数据包都被正确接收。如果接收方没有确认收到某个数据包,发送方将重新发送该数据包,直到接收方确认收到为止。

  2. 序列号:TCP给每个数据包分配一个独特的序列号,接收方可以根据序列号对数据包进行排序,确保数据包的顺序正确。

  3. 超时重传:如果发送方在一定时间内没有收到接收方的确认,将重新发送数据包,以确保数据包能够被正确传输。

  4. 流量控制:TCP通过流量控制机制,确保发送速度不会超过接收方处理速度,避免数据包丢失或传输错误。

  5. 拥塞控制:当网络拥塞时,TCP会减缓发送速度,以避免过度拥塞网络,导致数据包丢失。

这些机制的结合,使得TCP能够在网络环境下实现可靠的连接。

5、OSI典型网络模型,简单说说有哪些

OSI(开放式系统互联)典型网络模型是由七个层次组成的模型,每个层次都有其特定的功能和服务。以下是这七个层次及其简要功能:

  1. 物理层:这一层的主要功能是建立物理连接,将比特流从一个地方传输到另一个地方,并进行数模转换和模数转换。

  2. 数据链路层:这一层的主要功能是为网络层提供服务,确保数据在物理层上的传输可靠。数据链路层还提供了一些错误检测和校正功能。

  3. 网络层:这一层的主要功能是进行逻辑地址寻址,实现不同网络之间的路径选择。

  4. 传输层:这一层的主要功能是定义传输数据的协议端口号,并进行流控和差错校验。其中最常用的协议是TCP(传输控制协议)和UDP(用户数据报协议)。

  5. 会话层:这一层的主要功能是建立、管理和终止会话。会话层还提供了一些同步和对话控制的功能。

  6. 表示层:这一层的主要功能是将数据进行加密、解密、压缩、解压缩等处理,以确保数据的完整性和一致性。

  7. 应用层:这一层是OSI模型的最上层,它提供了网络服务与最终用户的一个接口,用户可以操作的一个界面终端。

这些层次协同工作,共同构成了一个完整的、完善的网络模型。

6、三次握手、四次挥手

三次握手和四次挥手是TCP协议中建立连接和关闭连接的过程。

三次握手:

  1. 客户端向服务器发送SYN(同步)报文,请求建立连接。此时,客户端的SYN=1,ACK=0。

  2. 服务器收到客户端的SYN报文后,向客户端发送SYN+ACK(同步+确认)报文,表示确认收到客户端的请求,并请求建立连接。此时,服务器的SYN=1,ACK=1。

  3. 客户端收到服务器的SYN+ACK报文后,向服务器发送ACK(确认)报文,表示确认收到服务器的请求。此时,客户端的ACK=1。

四次挥手:

  1. 客户端向服务器发送FIN(结束)报文,请求关闭连接。此时,客户端的FIN=1,ACK=0。

  2. 服务器收到客户端的FIN报文后,向客户端发送ACK(确认)报文,表示确认收到客户端的请求关闭连接的请求。此时,服务器的ACK=1。

  3. 服务器向客户端发送FIN(结束)报文,请求关闭连接。此时,服务器的FIN=1,ACK=0。

  4. 客户端收到服务器的FIN报文后,向服务器发送ACK(确认)报文,表示确认收到服务器的请求关闭连接的请求。此时,客户端的ACK=1。

这样,TCP连接就建立成功了,并且确保了连接的可靠性。

第四章:常见算法

1、什么是 pid 算法,pid 算法有什么调参经验?

PID算法是一种常见的控制算法,用于调整系统的输入信号,以使系统的输出达到预期的目标。PID算法的名字来源于其三个基本的控制元素:比例(P)、积分(I)和微分(D)。

PID算法的主要作用是将系统的实际输出与期望输出之间的误差最小化。具体的调参经验如下:

  1. 比例系数(P):比例系数主要影响系统的稳定性。比例系数过大可能导致系统不稳定,而比例系数过小则可能导致系统的反应速度过慢。因此,在调参时需要根据实际情况选择适当的比例系数。

  2. 积分系数(I):积分系数主要影响系统的精度。积分系数过大可能导致系统出现过度调整,而积分系数过小则可能导致系统的精度不够。在调参时,需要根据系统的精度要求选择适当的积分系数。

  3. 微分系数(D):微分系数主要影响系统的响应速度。微分系数过大可能导致系统反应过快,而微分系数过小则可能导致系统的响应速度过慢。在调参时,需要根据系统的响应速度要求选择适当的微分系数。

此外,还有其他一些调参经验,如:

  1. 一般来说,先调整比例系数,再调整积分系数和微分系数。

  2. 在调整积分系数和微分系数时,需要根据系统的延迟时间和响应速度进行综合考虑。

  3. 在调整参数时,需要进行逐步调整,以避免参数过大或过小的跳跃。

总之,PID算法的调参是一个需要综合考虑各种因素的过程,需要根据实际情况进行灵活的调整

2、常见的滤波算法有哪些,如何实现的?

常见的滤波算法包括:一阶补偿滤波,算术平均滤波,中位值滤波,限幅平均滤波,滑动平均滤波和卡尔曼滤波。

推荐博客地址:http://t.csdn.cn/YHNiK

3、各种排序算法的时间空间复杂度、稳定性

4、十大排序算法的实现

推荐博客地址:http://t.csdn.cn/Wmf3V

5、二分法查找的原理

二分法查找(也称为折半查找)是一种在有序数组中查找特定元素的搜索算法。其基本原理如下:

  1. 首先,将数组按升序排列,使得数组中的元素位于一个单调递增的序列中。
  2. 接着,选择数组的中间元素作为查找的起始点,如果该元素正好是要查找的元素,则查找过程结束,否则执行下一步。
  3. 如果要查找的元素大于中间元素,则在数组的右半部分重复步骤2;如果要查找的元素小于中间元素,则在数组的左半部分重复步骤2。
  4. 如果在某一步中,数组为空,则表示查找失败,即要查找的元素不在数组中。

通过不断将查找区间缩小一半,最终可以找到要查找的元素,或者确定该元素不在数组中。

第五章: Linux操作系统常见面试题

1、Linux内核的组成部分

Linux内核主要由以下几个部分组成:

  1. 进程调度(SCHED):负责控制CPU对进程的访问,使多个进程能够在同一时间竞争CPU资源。

  2. 内存管理(MM):负责管理系统的内存,包括物理内存和虚拟内存,以及内核对内存的访问控制。

  3. 虚拟文件系统(VFS):提供对文件系统的统一抽象接口,使得不同的文件系统能够在更高层次上进行访问和操作。

  4. 网络接口(NET):提供对网络协议的支持,包括网络接口驱动程序和网络协议的处理。

  5. 进程间通信(IPC):提供进程之间的通信机制,包括信号、管道、共享内存等。

  6. 模块管理器(MODULES):负责加载和卸载内核模块,以及与模块相关的管理和维护工作。

  7. 系统调用接口(SYSCALLS):提供用户空间与内核空间之间的接口,使得用户程序能够调用内核提供的系统功能。

这些组成部分共同构成了Linux内核,使得Linux操作系统具有了强大的功能和灵活性。

2、用户空间与内核通信方式有哪些?

用户空间与内核空间之间的通信方式有以下几种:

  1. 系统调用(System Call):当用户程序需要内核提供服务时,可以通过系统调用接口发起系统调用。系统调用是一种由用户程序主动发起的从用户空间向内核空间发送的请求,通常用于完成一些与硬件或系统相关操作,如文件操作、进程控制、内存管理等。

  2. 中断(Interrupt):当硬件或软件事件发生时,可以通过中断接口将控制权转移到内核空间。中断通常用于处理异步事件,如硬件中断、时钟中断、进程中断等。

  3. 信号(Signal):信号是一种由内核或进程发送的异步事件,用于通知用户程序某些事件已经发生或需要立即执行某个操作。信号通常用于进程间的通信和同步,如进程终止信号、错误信号等。

  4. 管道(Pipe):管道是一种半双工的通信机制,用于实现进程之间的数据传输和共享。管道可以是字节流或消息流,可以用于实现进程之间的父子进程通信或进程与内核模块之间的通信。

  5. 共享内存(Shared Memory):共享内存是一种高效的进程间通信方式,通过将同一块物理内存映射到多个进程的虚拟地址空间中,使得多个进程可以同时访问和修改该内存区域中的数据。共享内存可以实现不同进程之间的数据共享和同步。

这些通信方式提供了用户空间和内核空间之间的接口,使得用户程序能够调用内核提供的系统功能,同时也实现了用户空间和内核空间之间的数据传输和通信。

3、系统调用read()/write(),内核具体做了哪些事情

当用户程序调用read()或write()系统调用时,内核会执行以下一系列操作:

  1. 验证参数:内核会验证用户程序传递的参数是否合法,包括文件描述符、缓冲区地址、字节数等。如果参数不合法,内核将返回错误码,并终止系统调用。

  2. 权限检查:内核会检查用户程序是否有足够的权限执行读取或写入操作。如果权限不足,内核将返回错误码,并终止系统调用。

  3. 数据传输:如果参数合法且权限检查通过,内核会根据文件描述符所代表的文件类型和文件偏移量,将数据从文件或设备中读取或写入到用户程序的缓冲区中。

  4. 缓冲区管理:内核会管理文件缓冲区,确保读取和写入的数据正确地缓存和刷新。在读取和写入完成后,内核会将缓冲区中的数据传递给用户程序或从用户程序接收数据。

  5. 内存管理:在读取和写入数据时,内核会管理内存,确保数据的正确存储和访问。如果需要,内核会分配或释放内存,以适应数据的大小和访问需求。

  6. 错误处理:在读取或写入操作完成后,内核会检查是否有错误发生。如果有错误发生,内核会记录错误码,并将错误信息传递给用户程序。

  7. 返回结果:如果读取或写入操作成功完成,内核会将结果返回给用户程序,包括读取或写入的字节数等。

总之,当用户程序调用read()或write()系统调用时,内核会执行一系列操作来验证参数、检查权限、传输数据、管理缓冲区和内存,并处理错误,并将结果返回给用户程序。这些操作保证了用户程序与内核之间的安全和正确的数据传输。

4、系统调用与普通函数调用的区别

系统调用和普通函数调用在以下几个方面存在区别:

  1. 调用方式:系统调用是通过中断实现的,而普通函数调用是通过函数调用实现的。系统调用会将控制权从用户空间切换到内核空间,而普通函数调用是在用户空间内进行的。

  2. 调用层次:系统调用是操作系统提供给应用程序的接口,可以访问硬件和系统资源,而普通函数调用通常是为了实现应用程序内部的逻辑。

  3. 调用时长:系统调用的处理速度通常比普通函数调用慢,因为系统调用需要额外的开销,例如中断处理和上下文切换。

  4. 调用安全性:系统调用在执行时具有更高的安全性,因为它们会进行参数验证和权限检查,而普通函数调用通常不会进行这些检查。

  5. 调用范围:系统调用是全局的,可以跨越不同的进程和用户空间,而普通函数调用是局部的,仅限于当前进程和用户空间。

总之,系统调用和普通函数调用在调用方式、调用层次、调用时长、调用安全性和调用范围等方面存在明显的区别。系统调用是为了实现操作系统提供给应用程序的接口,而普通函数调用则是为了实现应用程序内部的逻辑。

5、内核态,用户态的区别

内核态和用户态是操作系统中的两个不同运行级别,它们之间存在以下区别:

  1. 权限不同:内核态拥有更高的权限,可以访问系统的所有资源,而用户态只能访问受限的资源,例如内存、硬件设备等。

  2. 访问资源:在内核态下,操作系统可以直接访问系统资源,而不需要通过用户程序的请求,而在用户态下,用户程序需要通过系统调用才能访问系统资源。

  3. 运行空间:内核态运行在内核空间,拥有系统的全部内存空间,而用户态运行在用户空间,只拥有有限的内存空间。

  4. 任务执行顺序:内核态是抢占式执行,优先级更高,可以中断用户态任务的执行,而用户态是协作式执行,优先级较低。

  5. 驱动程序:内核态下运行的是驱动程序,用于管理硬件设备的接入和访问,而用户态下运行的则是应用程序和库函数。

内核态和用户态之间的区别是为了保证系统的安全性和稳定性。通过限制内核态的访问权限和资源,可以防止恶意程序对系统造成损害,同时也提供了更好的灵活性和可扩展性。

6、bootloade、内核、根文件的关系

BootLoader、内核和根文件系统是操作系统启动的三个重要组成部分,它们之间的关系如下:

  1. BootLoader:BootLoader是操作系统启动时首先执行的程序,它的作用是加载内核并将控制权交给内核。BootLoader通过读取内核映像文件,将其加载到内存中,然后跳转到内核的入口地址,使内核开始执行。

  2. 内核:内核是操作系统的核心部分,它负责管理系统的进程、内存、设备驱动等核心功能。在内核启动后,它会检查文件系统并挂载根文件系统到系统中。

  3. 根文件系统:根文件系统是操作系统的文件系统,它包含了操作系统的基本文件和目录。在内核启动后,它会检查文件系统并挂载根文件系统到系统中,使应用程序能够访问和操作这些文件和目录。

在操作系统的启动过程中,BootLoader会加载内核并将控制权交给内核,内核会初始化系统的各个组件,然后检查和挂载根文件系统到系统中。这样,应用程序就可以通过根文件系统访问和操作文件和目录。因此,BootLoader、内核和根文件系统之间的关系是相互依存、缺一不可的。

7、Bootloader启动过程

推荐博客地址:http://t.csdn.cn/e8hzp

8、Linux下检查内存状态的命令

在Linux下,可以使用以下命令来检查内存状态:

  1. free命令:可以显示系统内存状态,包括物理内存、已使用的内存、空闲内存、缓存等。

  2. vmstat命令:可以显示虚拟内存状态,包括内存使用情况、缓存、进程状态等。

  3. top命令:可以实时查看系统内存使用情况,包括内存使用率、进程状态、CPU使用率等。

  4. htop命令:可以以更为详细的格式显示系统内存使用情况,包括每个进程的内存使用情况、CPU使用率等。

  5. sar命令:可以生成系统性能报告,包括内存使用情况、CPU使用率、磁盘使用情况等。

  6. dstat命令:可以显示系统运行状态,包括内存使用情况、CPU使用率、磁盘使用情况等。

这些命令可以帮助用户实时查看系统内存状态,并对系统性能进行监控和分析。

9、大小端的区别以及各自的优点,哪种时候用。(判断大小端的三种方式)

大小端(Endian)是指计算机在存储和表示数据时,对于多字节的数据类型(如整数、长整数等),以不同的字节顺序进行存储和表示。

  1. 大端(Big-Endian)表示将数据的高位字节存储在低地址处,而低位字节存储在高地址处。

  2. 小端(Little-Endian)表示将数据的低位字节存储在低地址处,而高位字节存储在高地址处。

以下是一些关于大小端的优点和适用场景:

  1. 优点:

    • 对于网络传输和跨平台通信,使用小端可以减少数据在不同平台之间的转换次数,提高传输效率。
    • 对于大多数应用程序来说,使用小端可以减少内存的使用,因为低位字节可以更早地被释放掉。
    • 对于一些特定的算法和数据结构,使用小端可以提高数据的局部性,从而提高缓存的命中率。
  2. 适用场景:

    • 对于需要跨平台传输数据的应用程序,例如网络通信、分布式系统等,建议使用小端。
    • 对于需要处理大量整数或浮点数运算的应用程序,例如科学计算、图形处理等,建议使用大端。
    • 对于需要处理大量字符串操作的应用程序,例如文本编辑器、编译器等,建议使用小端。

判断大小端的方式有以下三种:

  1. 编写一个多字节的数据类型(例如int、long、float或double),并将其存储到内存中。然后,读取内存中的数据,并逐字节地检查它们的值,以确定字节顺序。
  2. 使用C标准库中的函数,例如htonl(主机字节序)和ntohl(网络字节序),将一个整数从主机字节序转换为网络字节序,或将网络字节序转换为主机字节序。通过比较转换前后的值来确定字节顺序。
  3. 读取和处理操作系统提供的系统寄存器或寄存器映射,以确定字节顺序。不同的处理器架构可能有不同的方式来读取和处理这些寄存器。

需要注意的是,不同的处理器架构可能具有不同的字节顺序,因此在进行跨平台通信或处理不同架构的数据时,应该特别注意字节顺序的问题。

10、一个程序从开始运行到结束的完整过程(四个过程)

1、编译预处理:处理伪指令

  1> 头文件包含

  2> 宏定义

  3> 条件编译   #if #endif  

                 gcc -E xxx.c -o  xxx.i               

2、编译:把预处理之后的文件进行语法分析,生成汇编代码

                 gcc  -S xx.i -o xx.s

3、汇编:将汇编文件生成机器代码(二进制代码)

                 as xx.s -o xx.o

4、链接:去指定路径下找库函数 (头文件包含的是声明,具体实现封装在库中)

                 gcc xx.o -o xx

11、什么是堆,栈,内存泄漏和内存溢出? 

堆(Heap)和栈(Stack)是计算机内存管理中的两个重要概念,而内存泄漏和内存溢出则是与内存管理相关的两个问题。

  1. 堆(Heap):堆是一种动态分配的内存区域,它由程序员分配和释放。堆的大小受系统限制,通常位于栈的顶部。在程序运行过程中,当需要分配大块内存时,可以通过调用malloc等函数从堆中分配。堆的优点是可以灵活地分配和释放内存,但需要注意的是,由于堆是动态分配的,可能会存在内存碎片和内存泄漏的问题。

  2. 栈(Stack):栈是一种静态分配的内存区域,它由系统自动分配和释放。栈的大小也受系统限制,通常位于内存的底部。在程序运行过程中,当需要分配局部变量或函数参数时,系统会自动从栈中分配内存。栈的优点是分配和释放内存的速度较快,但缺点是空间有限且大小固定,无法分配大块内存。

  3. 内存泄漏(Memory Leak):内存泄漏指的是程序中的某些代码由于某些原因未能释放已经不再需要的内存,导致内存无法被再次使用。这通常会导致程序运行过程中出现内存不足的情况,从而导致程序性能下降或崩溃。

  4. 内存溢出(Out of Memory):内存溢出指的是程序在运行过程中所需的内存超过了系统所能提供的内存大小。这通常会导致程序崩溃或出现不可预测的行为。为了解决内存溢出问题,程序员需要优化程序的内存使用,避免无谓的内存占用和泄漏。

综上所述,堆、栈、内存泄漏和内存溢出是计算机内存管理中的重要概念。程序员需要合理地使用堆和栈,避免内存泄漏和

12、堆和栈的区别 

堆(Heap)和栈(Stack)是计算机内存管理中的两个重要概念,它们有以下区别:

  1. 分配方式:堆是动态分配的,而栈是静态分配的。在程序运行过程中,当需要分配大块内存时,可以通过调用malloc等函数从堆中分配,而栈则是在程序编译时就已经分配好的。

  2. 大小限制:栈的大小是有限的,而堆的大小受系统限制。栈通常位于内存的底部,大小固定,而堆位于栈的顶部,可能会占用系统的大部分内存。

  3. 内存释放:堆的内存释放需要手动进行,而栈的内存由系统自动释放。在程序运行过程中,如果需要释放堆中的内存,需要手动调用free函数,而栈中的内存会自动释放。

  4. 使用场景:栈通常用于存储局部变量、函数参数和返回地址等,而堆通常用于存储动态创建的数据结构和对象。

需要注意的是,由于堆是动态分配的,可能会存在内存碎片和内存泄漏的问题。同时,由于栈的大小有限制,如果分配过多的局部变量或递归调用导致栈溢出的问题也可能出现。

综上所述,堆和栈在分配方式、大小限制和内存释放方面存在明显的区别。程序员需要合理地使用堆和栈来满足程序的需求,避免内存泄漏和溢出问题。

13、死锁的原因、条件 

死锁的原因主要有以下几个:

  1. 系统资源不足。在这种情况下,进程的资源请求可能无法得到满足,导致死锁。
  2. 进程运行推进的顺序不合适,或者由于进程运行推进顺序与速度不同,也可能导致死锁。
  3. 资源分配不当也是死锁的原因之一。

产生死锁的四个必要条件如下:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

只要上述条件之一不满足,就不会发生死锁,而只要系统发生死锁,这些条件必然成立。

14、硬链接与软链接的区别

硬链接和软链接在以下几个方面存在区别:

  1. 链接原理:硬链接是指通过文件索引节点(inode)进行的链接,而软链接(也叫符号链接)则是指通过文件路径进行的链接。
  2. 链接范围:硬链接仅能在同一文件系统内创建,而软链接可以跨越不同的文件系统。
  3. 文件名和文件路径:硬链接保持文件名不变,但文件路径可以自由更改,而软链接实际上是对文件的一个快捷方式,更改软链接的文件路径不会影响原始文件。
  4. 文件大小:硬链接和原始文件具有相同的大小,而软链接则保存了文件路径的长度。
  5. 创建方式:在Linux系统中,可以使用ln命令创建硬链接和软链接。创建硬链接时,需要使用-l选项,而创建软链接时,需要使用-s选项。
  6. 颜色区分:在Linux系统中,硬链接的文件名通常是白色的,而软链接的文件名则是蓝色的。

总结来说,硬链接和软链接在功能上有明显的区别。硬链接强调文件数据块的一致性,可以在不同的目录中创建相同的文件的多个副本,而软链接则强调指向文件的路径,可以跨越不同的文件系统。

15、虚拟内存,虚拟地址与物理地址的转换

虚拟内存是一种计算机内存管理技术,它将计算机的物理内存与硬盘上的虚拟内存结合起来,通过将部分硬盘空间作为内存来使用,从而实现扩大内存的效果。

在虚拟内存中,每个进程都有其独立的虚拟地址空间,用于存储进程的数据和代码。虚拟地址是进程用来访问内存的地址,它与物理地址不同,虚拟地址需要通过虚拟内存管理模块进行转换,才能与物理地址相对应。

虚拟地址通常包括两部分:基址和偏移量。基址是虚拟地址的起始值,而偏移量则是进程中某个数据或指令在虚拟地址中的位置。通过将基址和偏移量相加,可以得到进程在虚拟地址空间中的实际位置。

在虚拟内存中,为了将虚拟地址转换为物理地址,需要使用页表这个数据结构。页表记录了进程的虚拟地址和对应的物理地址之间的映射关系。当进程访问虚拟地址时,操作系统会根据页表中的映射关系,将虚拟地址转换为物理地址,并将数据从磁盘上的虚拟内存映射到物理内存中。

总结来说,虚拟内存、虚拟地址和物理地址之间的转换是通过页表实现的。通过将虚拟地址与页表中的映射关系相加,可以得到对应的物理地址,从而实现虚拟地址到物理地址的转换。(Linux貌似是通过内存管理单元MMU进行虚拟映射的!)

16、计算机中,32bit与64bit有什么区别

计算机的位数一般指操作系统的位数,32位操作系统可以寻址2的32次方个字节的内存范围,而64位操作系统则可寻址2的64次方个字节的内存范围。以下是它们的主要区别:

  1. 数据处理能力:32位计算机的CPU一次最多能处理32位数据,例如它的EAX寄存器就是32位的,而64位计算机一次可以处理8个字节,因此64位计算机数据处理能力更强,速度更快。
  2. 内存容量支持:32位的系统许多支持4G的内存,而64位系统则可以支持上百G的内存。
  3. 软件运行版本:64位的系统能够兼容32位的软件,但32位的系统不能向上兼容。
  4. 应用场景:由于64位数据处理能力更强,因此适合处理大规模数据运算和复杂任务,如大型数据库、虚拟化和科学计算等应用场景。而32位系统则更适合于小规模数据处理和简单任务,如Web浏览、电子邮件和轻度办公等应用场景。

总的来说,64位计算机相比32位计算机具有更强的数据处理能力和更大的内存支持,适用于大规模数据运算和复杂任务。而32位计算机则适用于小规模数据和简单任务。同时,64位操作系统也提供了更高的安全性。

17、中断和异常的区别

中断(Interrupt)和异常(Exception)在含义和本质上存在明显区别。

  1. 中断:是指系统停止当前正在运行的程序,以便处理其他紧急事件或请求。中断通常是由硬件设施引发的,例如,当处理器检测到硬件故障(如输入/输出设备)或软件优先级较高的请求(如控制台输入)时,会触发中断。中断被视为一种正常现象,因为其目的是为了使系统能够有效地处理各种紧急事件,以保证系统正常运行。
  2. 异常:与中断不同,异常是由于软件错误而引起的。异常通常是在程序运行过程中发生的,可能是由于程序本身的错误,或者是由于程序运行过程中出现了一些未被预料到的状况。

总的来说,中断和异常在本质上都是为了处理紧急事件或请求,但中断主要是由于硬件设施或高优先级的软件请求,而异常则主要是由于程序本身运行过程中发生的错误。

18、 Linux 操作系统挂起、休眠、关机相关命令

 在 Linux 操作系统中,挂起、休眠和关机相关的命令主要如下:

  1. 挂起(suspend):挂起是一种省电模式,系统将机器的硬盘、显示器等外部设备停止工作,而 CPU 和内存仍然工作,等待用户随时唤醒。在挂起状态下,所有运行的程序和进程都会被暂停,而系统的状态和数据也会被保存在内存中。要挂起系统,可以使用以下命令:sudo pm-suspend
  2. 休眠(hibernate):休眠是一种更加省电的模式,它将内存中的数据保存于硬盘中,使 CPU 也停止工作。在休眠状态下,系统的状态和数据会被完整保存,并且可以随时恢复到休眠前的状态。要休眠系统,可以使用以下命令:sudo pm-hibernate
  3. 关机(shutdown):关机是将计算机系统关闭,停止所有进程和活动。关机通常会关闭所有外部设备,包括电源和磁盘。要关机可以使用以下命令:sudo shutdown -h now

需要注意的是,这些命令需要在具有 root 权限的环境下执行。另外,不同的 Linux 发行版可能会有一些命令上的差异,具体操作还需参考对应的发行版说明文档。

19、编译优化选项-o

在 Linux 系统中,GCC 编译器提供了多种编译优化选项,其中 -o 是一个用于指定输出文件名的选项。以下是关于编译优化选项 -o 的详细说明:

  • -o:该选项用于指定输出文件的名称。在编译过程中,GCC 编译器会将生成的目标文件保存为一个文件,该文件的名称可以是任意的,通过 -o 选项可以指定输出文件的名称。例如,gcc -o myprogram mysource.c 表示将 mysource.c 文件编译为名为 myprogram 的可执行文件。
  • -O:这个选项用于启用编译优化。通过该选项,GCC 编译器会在生成目标代码时进行一些优化操作,以提高代码的运行效率。优化的程度可以通过后续的数字参数来指定,例如 -O1-O2-O3 等。

以下是一些常用的编译优化选项:

  • -O0:禁用所有优化选项,以最小的编译时间生成目标代码。
  • -O1:启用一些基本的优化选项,主要优化代码大小和运行速度,但不会对编译时间产生太大的影响。
  • -O2:启用更多的优化选项,除了基本的优化外,还会采用更多的优化算法以提高代码的运行效率。
  • -O3:启用更多的优化选项,除了基本的优化外,还会采用更多的优化算法以提高代码的运行效率,并可能增加编译时间。
  • -Os:该选项用于优化代码尺寸,会启用一些能够减小目标代码大小的优化选项,但不会对运行效率产生太大影响。

需要注意的是,不同的编译优化选项可能会对编译时间和生成代码的大小和运行效率产生不同的影响。因此,在实际使用时需要根据具体需求和系统资源进行选择。另外,具体的编译优化选项可能会因 GCC 版本的不同而有所差异,需要参考相应的文档进行选择和使用。

20、 在有数据 cache 情况下,DMA 数据链路为:外设-DMA-DDR-cache-CPU,CPU 需要对 cache 做什么操作,才可以得到数据

在有数据cache的情况下,DMA数据链路为:外设-DMA-DDR-cache-CPU。为了使CPU能够得到数据,需要进行以下操作:

  1. 缓存同步(Cache Coherence):由于数据可能已经被缓存在cache中,因此需要确保cache和DDR内存中的数据保持一致。这可以通过使用缓存同步协议来实现,例如MESI(Modified, Exclusive, Shared, Invalid)或MOESI(Modified, Owner, Exclusive, Shared, Invalid)协议。缓存同步的目的是确保CPU在任何时候都能够正确地访问到最新的数据。
  2. 数据更新(Data Update):如果数据已经在DDR内存中更新,但cache中仍保持着旧的数据,那么CPU需要确保能够获取到最新的数据。这可以通过在缓存同步过程中使用写回(Writeback)策略来实现,即将cache中过时的数据写回DDR内存,从而保证数据的正确性。
  3. 数据访问(Data Access):当CPU需要访问数据时,它会首先检查cache中是否存在所需的数据。如果cache中存在,则可以直接从cache中获取数据,而不需要访问DDR内存。如果cache中不存在所需的数据,则需要进行内存访问,从DDR内存中读取数据。

总之,在有数据cache的情况下,CPU需要对cache进行缓存同步、数据更新和数据访问操作,以确保能够在正确的时机获取到数据。这些操作通常由硬件自动处理,而不需要用户的显式干预。

21、Linux中改变文件属性的命令: chmod

在Linux中,改变文件属性的命令是chmodchmod命令用于修改文件的访问权限,控制用户或用户组对文件的访问权限。

chmod命令的用法如下:chmod [选项] [模式] 文件名

作者建议当运行某个文件时出现权限不够的情况,直接使用chmod 777 [文件名]

22、Linux中查找文件中匹配字符串的命令: grep 

 在 Linux 中,查找文件中匹配字符串的命令是 grepgrep 命令用于在文件中搜索指定的文本模式,并输出匹配的行。

grep 命令的用法如下:grep [选项] [匹配模式] 文件名

其中,选项可以是以下之一:

  • -c:只输出匹配行的计数。
  • -i:忽略大小写,即匹配模式中的字符不区分大小写。
  • -n:在匹配的行前面输出行号。
  • -r 或 -R:递归地在目录下查找匹配的行。
  • -v:输出不匹配的行。

匹配模式是一个正则表达式,用于指定需要匹配的文本模式。例如,以下命令将在文件 test.txt 中查找所有包含字符串 "hello" 的行,并将它们输出到终端:

grep "hello" test.txt

第六章:单片机常见面试题

1、ROM与RAM

ROM 和 RAM 都是一种存储技术,但它们有不同的原理和特点。

ROM(Read-Only Memory)是一种只能读出事先存储数据的固态存储器。ROM 的特点是在数据存储后,数据不可以被修改,即只能读取数据,不能写入。ROM 在断电后仍然可以保存数据,因此常用于存储固定的系统信息或程序,例如操作系统或预设的应用程序等。

RAM(Random Access Memory)则是一种可以随机读写数据的存储器。RAM 的特点是数据可以被随机读取和写入,但只有在电源存在的情况下可以保持数据的完整性。RAM 主要用于存储运行时使用的数据和缓存,例如操作系统的临时数据或应用程序的缓存数据等。

此外,ROM 和 RAM 在计算机系统中也有不同的应用场景。ROM 常用于存储固定的系统信息或程序,例如操作系统或预设的应用程序等。而 RAM 则主要用于存储运行时使用的数据和缓存,例如操作系统运行时的临时数据或应用程序的缓存数据等。

总之,ROM 和 RAM 有不同的特点和应用场景,根据需要选择适合的存储方式。

2、GPIO口的 8 种工作方式

在GPIO口的工作方式中,有8种不同的模式:

  1. 输入浮空(GPIO_Mode_In_FLOATING):该模式下,GPIO口不连接任何功能,处于浮空状态,可以用于读取外部的开关状态或传感器信号。
  2. 输入上拉(GPIO_Mode_IPU):该模式下,GPIO口通过上拉电阻连接到正电源,可以用于读取外部的开关状态或传感器信号。
  3. 输入下拉(GPIO_Mode_IPD):该模式下,GPIO口通过下拉电阻连接到地电源,可以用于读取外部的开关状态或传感器信号。
  4. 模拟输入(GPIO_Mode_AIN):该模式下,GPIO口可以作为一个模拟输入口使用,通常用于读取模拟信号,例如温度、湿度等传感器信号。
  5. 开漏输出(GPIO_Mode_Out_OD):该模式下,GPIO口可以通过控制输出高低电平,实现线与逻辑,并且可以实现电平的转换。
  6. 开漏复用功能(GPIO_Mode_AF_OD):该模式下,GPIO口可以通过控制输出高低电平,实现线与逻辑和电平转换的同时,还可以作为其他外设的扩展功能使用。
  7. 推挽式输出(GPIO_Mode_Out_PP):该模式下,GPIO口可以通过控制输出高低电平,实现线与逻辑和电平转换的同时,还可以作为其他外设的扩展功能使用。
  8. 推挽式复用功能(GPIO_Mode_AF_PP):该模式下,GPIO口可以通过控制输出高低电平,实现线与逻辑和电平转换的同时,还可以作为其他外设的扩展功能使用。

这些工作方式可以根据实际应用需求进行选择和配置。

3、请说明总线接口USART、I2C、USB的异同点(串/并、速度、全/半双工、总线拓扑等)

总线接口USART、I2C和USB在串/并、速度、全/半双工和总线拓扑等方面存在一些异同点,以下是具体的比较:

  1. 串/并:USART(Universal Synchronous/Asynchronous Receiver/Transmitter)是一种同步串行通信接口,它支持串行和并行数据传输。I2C(Inter-Integrated Circuit)是一种串行通信接口,只支持串行传输。而USB(Universal Serial Bus)是一种串行总线,也支持串行数据传输。
  2. 速度:USART和I2C的速度通常较低,它们适合于低速应用。而USB的速度相对较高,支持高速数据传输。
  3. 全/半双工:USART和I2C都支持全双工或半双工通信,而USB只支持全双工通信。
  4. 总线拓扑:USART和I2C都是点对点通信协议,可以支持多个设备之间的通信,但需要额外的控制器来管理通信。而USB是一种树形总线结构,设备通过Hub连接,可以支持多个设备的连接和通信。

综上所述,USART、I2C和USB在串/并、速度、全/半双工和总线拓扑等方面都存在差异,根据实际应用场景的需求来选择适合的总线接口。

推荐博客地址:http://t.csdn.cn/2IZ2B

4、单片机的SP指针始终指向:栈顶

单片机的堆栈指针(SP)始终是指向栈顶的。

在单片机中,堆栈是一种内存区域,用于存储临时数据和地址。堆栈指针(SP)是一个指向栈顶的寄存器,它指向当前栈顶元素的位置。当新的元素被压入栈中时,堆栈指针会向下移动,指向新的栈顶位置;当元素被弹出栈时,堆栈指针会向上移动,指向下一个栈顶元素的位置。

由于堆栈是一种先进后出(LIFO)的数据结构,所以堆栈指针始终指向栈顶,即最后一个压入栈的元素。这样在访问栈顶元素时,只需要通过堆栈指针就可以快速定位到栈顶元素,而不必遍历整个栈。

在单片机中,堆栈通常用于存储程序的临时变量、函数参数和返回地址等,它的使用可以大大简化程序的编写和执行效率。

5、FIQ中断向量入口地址

FIQ(Fast Interrupt Queue)是 ARM 架构中的一种中断机制,用于处理高优先级的中断。FIQ 的中断向量入口地址是预先定义好的,根据ARM架构的不同版本,FIQ 的中断向量入口地址可能有所不同。

在 ARMv7 和 ARMv8 架构中,FIQ 的中断向量入口地址为0x0000001C。

在 ARMv9 和A RMv10 架构中,FIQ 的中断向量入口地址为0x00000028。

需要注意的是,这些中断向量入口地址是在内核空间中定义的,用户空间中无法直接访问。在编写使用 FIQ 的中断处理程序时,需要按照对应架构的规范来进行编写。

6、全双工总线类型

全双工总线是指数据可以在两个方向上传递,即可以同时进行发送和接收操作。以下是一些常见的全双工总线类型:

  1. SPI总线(Serial Peripheral Interface):是一种常用的全双工总线,它采用串行通信协议,可以在主设备和从设备之间进行双向数据传输。SPI总线通常使用4条线,包括主设备到从设备的数据传输线(MOSI)和从设备到主设备的数据传输线(MISO)。
  2. I2C总线(Inter-Integrated Circuit):是一种低速全双工总线,它采用串行通信协议,可以在主设备和从设备之间进行双向数据传输。I2C总线通常使用两条线,一条是数据线SDA,另一条是时钟线SCL。
  3. USB总线(Universal Serial Bus):是一种高速全双工总线,它采用串行通信协议,可以在主机和设备之间进行双向数据传输。USB总线通常使用4条线,包括电源线、数据线、地线和ID线。
  4. 以太网(Ethernet):是一种高速全双工总线,它采用并行通信协议,可以在网络设备之间进行双向数据传输。以太网通常使用多条线,包括传输数据的双绞线和传输控制的LED线和CRS线等。

这些全双工总线类型都有其独特的特点和应用场景,根据实际需求选择适合的总线类型。

7、l2C协议时序图和SPI的时序图

I2C和SPI都是嵌入式非常基础且必备的通信协议,内容篇幅偏长,建议大家好好掌握!

8、CAN协议

CAN(Controller Area Network)协议是一种用于设备间通信的高效、可靠、安全的通信协议。以下是一些关于CAN协议的关键信息:

  1. CAN总线是一种广播式通信协议,所有的节点都可以接收和发送数据。
  2. CAN具有多个不同等级的节点,这些节点可以通过不同的ID进行识别和区分。
  3. CAN总线采用差分信号传输,这种传输方式可以抵抗电磁干扰和噪声,保证数据的稳定传输。
  4. CAN协议具有强大的错误检测和纠正能力,包括位错误检测、填充错误检测、CRC校验等,这些功能可以确保数据的准确性和可靠性。
  5. CAN总线在汽车、工业控制、医疗设备等领域广泛应用,用于各种数据和控制信号的传输。
  6. CAN总线具有高速传输特性,通信速率可达1Mbps,适用于需要高速数据传输的应用。
  7. CAN总线具有多主机通信的特点,多个节点可以同时发送和接收数据,实现分布式控制。

总之,CAN协议是一种适用于各种设备间通信的可靠、安全的通信协议,具有广泛的应用场景和优势。

第七章:其他杂项面试题

1、讲一讲冯诺依曼和哈佛体系的区别

冯诺依曼体系和哈佛体系是两种不同的计算机体系结构,它们在存储器组织、指令和数据访问方式等方面存在一些关键的区别。

  1. 存储器组织:冯诺依曼体系将程序指令和数据存储在同一个存储器中,程序指令和数据共享同一组存储器。而哈佛体系则将程序指令和数据分别存储在两个独立的存储器中,即程序存储器和数据存储器。
  2. 总线结构:冯诺依曼体系使用单一的指令总线,所有设备共享同一组总线。而哈佛体系使用两个独立的总线,一条用于程序指令传输,另一条用于数据传输。
  3. 执行效率:由于冯诺依曼体系使用单一的指令总线,所有设备共享同一组总线,因此指令和数据的传输可能存在竞争和冲突,导致执行效率较低。而哈佛体系通过独立的总线实现指令和数据的传输,避免了冲突和竞争,提高了执行效率。
  4. 复杂度:由于哈佛体系需要设计更多的硬件电路,因此相对于冯诺依曼体系而言,哈佛体系的硬件复杂度更高,成本也更高。

总体而言,冯诺依曼体系和哈佛体系各有优缺点。冯诺依曼体系设计简单,易于实现,适用于对执行效率和复杂度要求不高的应用。而哈佛体系则适用于对执行效率和复杂度要求较高的应用,如嵌入式系统和实时系统。

2、讲一讲二叉树和红黑树

二叉树和红黑树是两种常见的自平衡二叉查找树,它们在计算机科学中有着重要的应用。以下是它们的一些关键信息:

  1. 二叉树:

* 定义:二叉树是一种数据结构,它包含一个根节点和两个子树,每个子树也是一棵二叉树。
* 特点:二叉树的每个节点最多有两个子节点,左子节点的值小于父节点的值,右子节点的值大于父节点的值。
* 应用:二叉树常用于排序、查找、插入和删除等操作,尤其是在数据库和数据结构的教学中经常被使用。

  1. 红黑树:

* 定义:红黑树是一种自平衡的二叉查找树,它通过颜色标记和旋转操作来保持树的平衡。
* 特点:红黑树的节点被涂成红色或黑色,根据节点颜色和链接关系,通过旋转操作保持树的平衡,提高查找、插入和删除等操作的效率。
* 应用:红黑树常用于在需要高效地存储和检索数据的场景,例如在操作系统中的文件系统和内存管理等。

总结来说,二叉树和红黑树都是基于二叉查找树的算法,用于高效地管理数据。二叉树结构简单,易于理解;红黑树则更具有平衡性,能够在平均情况下更高效地处理操作。具体选择哪种数据结构取决于具体的应用场景和需求。

3、http默认端口号:80

HTTP(超文本传输协议)的默认端口号是80。在 TCP/IP 协议中,HTTP 请求通过端口号 80 来传输数据。这是由于在传统的互联网应用中,使用 80 号端口号的Web服务器是最常见的,因此它被约定俗成为 HTTP 默认端口号。

在 HTTP 请求中,客户端通过 HTTP 请求头(Request Header)中的 "Port" 字段来指定服务器端口号。如果未指定端口号,则默认使用 80 端口。同样,在 HTTP 响应中,服务器通过 HTTP 响应头(Response Header)中的 "Location" 字段来指定客户端的端口号。

虽然 80 是 HTTP 的默认端口号,但在特定情况下,也可以使用其他端口号。例如,当服务器上存在多个 Web 站点时,可以使用不同的端口号来区分不同的站点。但这种情况下,客户端仍然会将请求发送到服务器的 80 端口,服务器根据请求中的信息将请求转发到相应的端口。

需要注意的是,虽然 80 是 HTTP 的默认端口号,但这并不是 HTTP 协议所强制规定的。实际应用中,也可以根据具体需求和场景选择使用其他端口号或进行端口映射等操作。

4、面向对象编程的三大特性,以及重载的意思。

面向对象编程的三大特性是封装、继承和多态。

  1. 封装(Encapsulation):将对象的属性和方法进行封装,通过访问控制符(public、protected、private)来控制访问权限,实现数据和行为的保护。封装可以使对象内部的数据和实现细节被隐藏起来,只对外暴露有限的操作接口,从而提高安全性和代码的可维护性。
  2. 继承(Inheritance):允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法,从而使得子类可以获得父类的特性。继承可以实现代码的重用和扩展,提高代码的复用性和可维护性。
  3. 多态(Polymorphism):指同一个方法在不同的情况下可以表现出不同的行为。多态性包括编译时多态和运行时多态。编译时多态是通过函数重载(Overloading)和运算符重载实现的,在编译时确定具体的调用;运行时多态是通过动态绑定(Dynamic Binding)实现的,在运行时确定具体的调用。

重载(Overloading)是指在同一个作用域内,允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。重载可以提高代码的复用性和可读性,使得函数名能够更清晰地表达其功能。在编译时,编译器会根据传入的参数类型和数量来确定具体调用的函数。

5、Linux中mysql数据库默认的端口是:3306

 Linux系统中,MySQL数据库的默认端口号是3306。在MySQL中,端口号是用于标识和连接数据库服务器的一个重要参数。在默认情况下,MySQL使用端口号3306来接受客户端的连接请求。

在安装和配置MySQL时,如果没有特别指定端口号,通常会使用默认的3306端口。在Linux系统中,可以使用以下命令来查看MySQL的默认端口号:

mysql -e "show variables like 'port'"

6、C语言编程中,单片机平台,一般有.c和.h文件,如果一个人在.h文件中定义了一个变量,会有什么后果。

在C语言编程中,如果在.h文件中定义了一个变量,可能会导致以下后果:

  1. 重复定义:如果同一个源文件中包含了多个.h文件,且其中有一个文件中定义了与其它文件中相同的变量,就会导致重复定义的问题。在编译时,编译器会报错并停止编译。
  2. 变量作用域问题:在.h文件中定义的变量通常是全局变量,可以在文件中的所有函数中使用。但是,如果在.h文件中定义了局部变量,且在不同的源文件中包含了该.h文件,可能会导致该变量的作用域超出预期,从而导致程序行为的不确定性。
  3. 变量重定义:如果在不同的源文件中包含了相同的.h文件,且其中有一个文件中定义了与其它文件中相同的变量,就会导致变量的重定义问题。在编译时,编译器会报错并停止编译。

为了避免以上问题,通常建议将变量的定义放在.c文件中,并在需要使用该变量的文件中包含对应的.h文件。这样可以确保每个文件中的变量不会发生重复定义或作用域问题。同时,也可以使用头文件保护(#ifndef等)来避免重复包含同一个.h文件,从而避免重复定义的问题。

7、if 语句中如果是或运算(|),第一个条件满足时,第二个条件还会判断吗

在if语句中使用或运算(|)时,如果第一个条件满足,第二个条件不会被判断。只有当第一个条件不满足时,才会去判断第二个条件。

作者有话

该博客为长期更新版本,作者如果平时遇到比较有特点且高频的面试问题都会及时更新到该篇博客上的。因为,作者个人能力有限,部分答案也不敢保证百分百的正确性。所以,如果有大佬发现错误可以评论区留言指正!嵌入式工程师是个需要长期积累学习的职业,看似八股文的面试问题背后常常也透露出些许工程难点的光辉,希望大家都能学有进步,万事顺义,Respect!!!

猜你喜欢

转载自blog.csdn.net/black_sneak/article/details/131549852