嵌入式八股文

嵌入式八股文

操作系统、C/C++知识、硬件知识


一、操作系统


大端字节序和小端字节序

  • 大端字节序是指将数据的高位存储在内存地址中的低位,将数据的低位存储在内存地址的高位中。这与我们平常的阅读顺序相同,先看到的是高位,后看到的是低位,因此被称为“大端”。
    例如,对于一个四字节整数0x12345678,在大端模式下,它的高字节是0x12,低字节是0x78

  • 小端字节序是指将数据的高位存储在内存地址中的高位,将数据的低位存储在内存地址的低位中


进程间通信方式

  1. 管道,Pipe;通常指无名管道,用函数pipe()创建

    • 半双工通信,速度慢;改进为流管道后可以实现全双工通信
    • 只能在具有亲缘关系的进程间通信;改进为有名管道后,实现在亲缘关系之外、任何进程之间通信(也称为FIFO,用函数mkfifo()创建),但同样是半双工通信
  2. 信号量,Semophore;

    • 本质是一个计数器,用来控制多个进程对共享资源的访问
    • 可以控制同步访问或者是互斥访问
    • 不能用来传递信息,只能实现共享资源保护,主要作为进程间以及同一进程内不同线程之间的同步手段
  3. 信号,Signal;

    • 本质是Linux内核在软件层面上模拟终端,在进程接收到信号做出相应操作
    • 用于通知接收进程某个事件已经发生。它常常用于进程间错误处理和异常情况的通知
  4. 消息队列,Messge Queue;

    • 将消息以链表的形式构成队列,并用队列标识符进行标识后存放在内存空间中
    • 能够克服信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点,支持消息的随机查询,不必按先进先出次序读取消息
  5. 共享内存,Shared Memory;

    • 将进程的虚拟空间的地址映射到同一个物理空间,这块内存空间可以被其他进程访问,可以实现在不同进程间高效率传输大量数据,摆脱Linux内核的传输限制,效率高于管道
    • 通常与其他通信机制如信号量配合使用来实现进程间的同步和通信
  6. 套接字,Socket;

    • 主要用于不同机器之间的通信

代码例程


线程间同步方式

  1. 互斥锁(Mutex):互斥锁是用于保护共享资源的一种基本同步机制。
    • 它确保任何时刻只有一个线程可以访问共享资源,其它尝试访问该资源的线程将会被阻塞,直到锁被释放。互斥锁通过lock和unlock两种状态来控制对资源的访问权限。一旦一个线程锁定了某个资源,其他试图再次锁定该资源的线程将会被阻止,直到前一个线程解锁该资源。
  2. 条件变量(Condition Variables):条件变量通常与互斥锁一起使用,允许线程在某些条件不满足时等待,并在条件满足时被唤醒。
    • 当线程访问条件变量并发现条件未满足时,它会释放互斥锁并进入等待状态;当其他线程修改了条件并通知条件变量时,等待的线程会被唤醒,重新获取互斥锁并检查条件是否满足。这种方式减少了无谓的锁竞争,提高了系统的效率。
  3. 读写锁(ReadWrite Locks):读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
    • 这种机制在多读少写的情况下特别有用,可以提高并发性能。读写锁有三种状态:读模式下的加锁状态、写模式下的加锁状态和不加锁状态。当有线程进行写操作时,会阻止其他线程进行读或写操作;而如果有线程进行读操作,则允许其他线程进行读操作。
  4. 原子操作(Atomic Operations):原子操作是指一系列不可中断的操作,这些操作在执行过程中不会被其他线程打断,从而保证了操作的原子性。
    • 原子操作主要用于对共享数据的简单修改,如递增、递减或比较并交换等。由于其高效性和无需复杂的锁机制,原子操作在多线程编程中得到了广泛应用。
  5. 信号量(Semaphores):信号量通过计数器来控制对共享资源的访问。
    • 它们允许多个线程同时访问同一资源,但需要限制在同一时刻访问资源的最大线程数。信号量通过增加和减少计数器的方式来控制资源的访问权限,当计数器为零时,任何试图访问资源的线程将会被阻止,直到其他线程释放资源并增加计数器。

二、C/C++知识点

关键字

#define 和 typedef的区别

#define#typedef在C/C++中都是预处理指令,用于提供代码中的符号替换和类型定义,简化编程过程。两者虽然在某些使用场景中都能定义新的标识符,但它们在定义、作用范围以及处理方式等方面有所区别,具体分析如下:

  1. 定义

    • #define:这是一个预处理器命令,用来定义常量或宏。它可以直接定义一个常量值或者定义一个参数化的宏,用于代码的替换和计算。通过这种方式,可以在编译前对源代码进行文本替换。
    • #typedef:这也是一个预处理指令,但它专门用于为类型定义一个新的名字。#typedef提供了一种简化复杂类型的声明的方式,使得这些类型在代码中更容易使用和管理。
  2. 作用范围

    • #define:其定义的宏没有具体的作用范围,一旦定义,在其后的源文件中都有效,除非被重新定义或者取消定义(使用#undef)。
    • #typedef: 定义的类型会遵循C/C++的作用域规则,这包括文件作用域、类作用域等。这意味着#typedef可以被限制在特定的区域内。
  3. 处理方式

    • #define#define仅进行简单的文本替换,并不进行类型检查或语义分析。这使得它可能在某些情况下导致不预期的行为。
    • #typedef#typedef则是一个完全的类型检查过程,定义的新类型会被编译器识别并在编译时进行相应的类型检查。在目标机器上会获得最高的精度

    例如定义个REAL的浮点类型 typedef long double REAL,在不支持long的机器上会变成 typedef double REAL,在不支持double的机器上会变成 typedef float REAL

  4. 使用场景

    • #define:通常用于定义常量和小型的具有计算功能的宏。例如,你可以使用#define来创建一个简单的数学运算宏。
    • #typedef:适用于为复杂的类型(如指针、数组、结构体)创建简洁的别名。例如,可以使用#typedef定义一个指向特定结构体的指针类型,使代码更易读。
  5. 灵活性

    • #define:更加灵活,可以在任何地方定义和取消定义,且可以进行条件编译。
    • #typedef:主要用于提高代码的可读性和可维护性,通过为类型重命名,使得类型系统更加清晰。
  6. 性能

    • #define:由于是预处理器指令,不会引入额外的运行时开销,但其文本替换可能会导致编译出的代码较大。
    • #typedef:同样不会影响运行时性能,但可以提升编译时的代码效率,特别是在处理复杂类型时。
  7. 对指针的操作

#define INTPTR1 int*
typedef int* INTPTR2;
INTPTR1 p1, p2;
INTPTR2 p3, p4;

含义分别为,声明一个指针变量p1和一个整型变量p2;声明两个指针变量p3、p4

#define INTPTR1 int*
typedef int* INTPTR2;
int a = 1;
int b = 2;
int c = 3;
const INTPTR1 p1 = &a;
const INTPTR2 p2 = &b;
INTPTR2 const p3 = &c;

在上述代码中,p1相当于 const int* 是一个常量指针,即不可更改指向内容,但可以更改指针指向的地址;p2相当于 int* const 是一个指针常量,即不可更改指针指向的地址,但是可以更改指向的内容。p3也是一个指针常量

在这里插入图片描述


#pragma once / #ifndef

#pragma once

  • 确保同一个文件(文件名判断,而不是内容相同的文件)不会被重复包含,避免出现宏命名冲突问题
  • 不支持跨平台
  • 如果一个头文件有多份拷贝,该方法不能保证头文件不被重复包含

#ifndef ... #define ... #enddef

  • 受C/C++语言支持,可以跨平台使用
  • 不仅能保证同一个文件不被重复包含,也可以保证内容相同的文件(代码段)不被重复包含
  • 该方法会打开文件进行编译判断是否重复包含,因此对于大型工程项目来说运行速度较慢

函数

strcpy(), sizeof(), strlen()

char * strcpy ( char * destination, const char * source )

  • 该函数的使用需要#include <string.h>
  • destination :表示目标字符串的地址;source :表示源字符串的地址
  • 原字符串地址是被 const 所修饰的常量指针,指向的内容不能被修改
  • 函数的返回值为目标字符串的地址
  • 调用strcpy函数,将源字符串src的内容(包括’\0’)复制到目标字符串destination中,需确保目标字符串有足够的空间大小

sizeof是C语言中的一个单目运算符,用来计算数据类型所占空间的大小,单位为字节;而strlen是一个函数,用来计算字符串长度

  • sizeof() 计算指针变量所占空间大小,在32位机上为4字节;因为指针变量指的内容为地址,地址长度由机器的地址总线决定。
  • strlen() 计算时不考虑 '\0',而 sizeof() 考虑 '\0'

malloc, free 和 new, delete 的区别

  • 属性

    • new/delete是C++关键字(操作符),需要编译器支持;
    • malloc/free是库函数,需要头文件支持c。
  • 参数

    • malloc申请空间时,需要手动计算空间大小并传递;new只需在其后跟上空间的类型(什么类型的数据)即可。
    • malloc申请的空间不会初始化(不会立刻得到物理内存);new可以初始化(立刻得到物理内存)
  • 返回值

    • malloc的返回值为void*, 在使用时必须强转;
    • new不需要,因为new后跟的是空间的类型,返回对象类型的指针。
  • 分配失败

    • malloc申请空间失败时,返回的是NULL,因此使用时必须判空;
    • new不需要,但是new需要捕获异常。
  • 申请自定义类型对象

    • malloc/free只会开辟空间,不会调用构造函数与析构函数;
    • new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现),因此需要自定义类型指针。free释放的时候需要void * 指针
  • 重载

    • C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。
    • 而malloc不允许重载。
  • 内存区域

    • new操作符从自由存储区(free store)上为对象动态分配内存空间;
    • 而malloc函数从上动态分配内存,超过128k后会在文件映射区(mmap调用分配)。
    • 自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
#include <stdlib.h>

int *p = (int *)malloc(100);                   //指向整型的指针p指向一个大小为100字节的内存的地址
int *p = (int *)malloc(25*sizeof(int)); 		 //指向整型的指针p指向一个25个int整型空间的地址
if(p == NULL) printf("Out of memory!\n");
free (p);

new动态分配数组空间

int *pi=new int[];               //指针pi所指向的数组未初始化
int *pi=new int[n];             //指针pi指向长度为n的数组,未初始化
int *pi=new int[]();            //指针pi所指向的地址初始化为0
delete [] pi;                   //回收pi所指向的数组

p尽管没有定义,但仍然存放了它所指向对象的地址,然而p所指向的内存已经被释放,因此p不再有效。建议一旦删除指针所指向的对象,立即将指针置为0,这样就清楚的表明指针不再指向任何对象。

p=NULL;

PS:

在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区;

在C中,C内存区分为堆、栈、全局/静态存储区、常量存储区;

Q & A

Q:free时怎么知道释放的内存空间大小?

A:在malloc分配内存时,在分配内存块的首地址前面还有一个头块(头部指针),这个头块中包含了分配内存块的信息。因此在free内存的时候,释放的是头块的内存+用户分配的内存

Q:malloc分配的是物理内存还是虚拟内存?
A:malloc分配的是虚拟内存,需要初始化之后才能得到物理内存


数据类型相关

  • 未初始化的变量大小
    int a = 10;
    int b = a++;
    int c = ++a;
    int e;
    char d;
    float f;
    double g;
    bool h;
    long long i;
    string j;

    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
    cout << "e = " << e << endl;
    cout << "d = " << d << endl;
    cout << "f = " << f << endl;
    cout << "g = " << g << endl;
    cout << "h = " << h << endl;
    cout << "i = " << i << endl;
    cout << "j = " << j << endl;

输出结果:
未赋值的变量理论上是随机的结果,但打印出来则为0,其中字符类型和字符串类型打印出来的结果分别为 '\0'""
在这里插入图片描述
在这里插入图片描述

二维数组指针取值

#include <iostream>

using namespace std;

int main() {
    int b[2][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}};
    int *p1 = (int*)(&b + 1);
    int *p2 = (int*)(*(b + 1));
    printf("%d %d\n", *p1, *p2);
    printf("%d %d\n", *(p1 - 1), *(p2 - 1));
    return 0;
}

输出结果:
在这里插入图片描述
这里b是一个M×N的二维数组

  • &b,获取的是整个二维数组b的地址,这个地址的类型是int (*)[N],即指向一个有N个整数的数组的指针。
  • &b + 1:由于&b是一个指向数组的指针,&b + 1实际上是将指针向前移动了一个数组的大小(即MNsizeof(int)字节)。这里需要注意的是,这并不是移动到数组的下一行或下一列,而是直接跳过了整个数组的空间。此时指针指向数组地址后一个不存在的内存空间,所以打印值为0。
  • b + 1:这里b被当作指向其第一行的指针,b + 1则指向了数组的下一行(也就是第二行的起始位置)。这个表达式的类型是int (*)[N]。
  • (b+ 1):解引用buf + 1,即访问第二行的首地址。这个操作的结果是一个int[N]类型的数组(尽管在表达式中它会被当作指向数组第一个元素的指针,即int)。

设计模式

单例模式

三、硬件知识

通信协议

UART,USART

IIC

SPI

RS485,RS232

嵌入式C语言八股文是指在嵌入式系统开发中常见的基本知识点和技能要求的简要总结。下面是嵌入式C语言八股文的主要内容: 1. 数据类型:包括基本数据类型(如int、char、float等)和派生数据类型(如数组、结构体、枚举等),掌握各种数据类型的使用方法和特点。 2. 运算符:熟悉各种算术运算符、逻辑运算符、位运算符等,掌握它们的优先级和结合性,能够正确使用运算符完成各种计算任务。 3. 控制语句:包括条件语句(if-else语句)、循环语句(for、while、do-while循环)、选择语句(switch-case语句)等,掌握这些语句的使用方法和注意事项。 4. 函数:了解函数的定义和调用,能够编写函数并正确使用函数参数和返回值,理解函数的作用域和生命周期。 5. 数组和指针:掌握数组和指针的定义和使用,了解数组和指针在内存中的存储方式,能够通过指针进行数组的访问和操作。 6. 文件操作:了解文件操作的基本流程,包括文件的打开、读写和关闭,理解文件指针和文件访问模式的概念。 7. 中断处理:了解中断的基本概念和原理,能够编写中断服务程序(ISR)并正确处理中断请求。 8. 程序调试:掌握常用的调试技巧和工具,能够使用调试器进行程序的单步执行、观察变量值等操作,能够分析程序运行过程中的错误和异常。 以上是嵌入式C语言八股文的主要内容,掌握这些知识和技能,可以帮助你在嵌入式系统开发中更好地应对各种任务和挑战。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值