学习内容分享

学习内容分享

C/C++部分

关键字

1、C语言中的内存,栈区和静态区

静态区(static):全局变量存储(在程序的整个生命周期都存在),

栈区(stack):局部变量存储(自动的连续内存),

堆区(heap):动态存储(内存池,非连续分配)。

2、new/delete与malloc/free区别

new、delete是C++中的操作符,而malloc和free是标准库函数。

new返回的是特定类型的指针,并且可以自动计算所申请内存的大小,而malloc需要我们计算申请内存的大小,并且在返回是强行转换为实际类型的指针。

3、sizeof和strlen有什么区别

sizeof是操作符,在计算字符串的空间大小时,包含了结束符'\0',而strlen是一个计算字符串长度的库函数,使用时需要引入头文件#include<string.h>,不包含'\0',只计算结束符'\0'之前的字符串长度。

函数

1、重载和重写有什么区别?

1、重写是子类和父类之间的关系,垂直关系;重载是同一个类方法之间的关系,水平关系;

2、重写对于函数名,参数要求是一致的;重载则是在同一个函数名下参数顺序、个数和类型有不同点;

3、重写由一个方法或者一对方法产生关系;重载则是多个方法之间的关系。

2、说一下fork, wait, exec函数?

父进程产生子进程使用fork拷贝出来一个父进程的副本,拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写拷贝机制分配内存,exec函数可以加载一个elf文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork从父进程返回子进程的pid,从子进程返回0,调用了wait的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回0,执行失败则返回-1,exex执行成功则子进程从新的程序开始,无返回值,执行失败则返回-1。

数组

1、数组下标可以是负数吗?

可以,因为下标只是给出了一个当前地址的偏移量而已,只要根据这个偏移量能定位到有效目标地址就行,举个栗子:

cpp 复制代码
#include <stdio.h>
int main() {
    int i =;
    int a[5] = {0, 1, 2, 3, 4};
    int *p = &a[4];
    for (i = -4; i <= 0; i++) printf("%d %x\n", p[i], &p[i]);
    return 0;
}
// 输出结果为
// 0 b3ecf480
// 1 b3ecf484
// 2 b3ecf488
// 3 b3ecf48c
// 4 b3ecf490

指针

1、常量指针和指针常量区别(易混淆)

常量指针:指向常量的指针,指针所指的值不变,指向地址可变

指针常量:指针是常量,指向地址不变,指向的值可变

简而言之,const char* 限制了指针所指向的内容的修改,而 char* const 限制了指针本身的修改。你可以通过检查const关键字的位置来确定是哪种限制:如果const*之前,那么指针指向的内容是常量;如果const*之后(即在指针类型之后),那么指针本身是常量。

容器和算法

1、vector和list的区别是什么?

1 vector底层实现是数组;list是双向链表;

2 vector支持随机访问,list不支持;

3 vector在中间结点进行插入删除会导致内存拷贝,list不会;

4 vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好 。

2、STL中迭代器有什么作用?有指针为何还要迭代器?

1、迭代器(Iterator)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示,迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用*取值后的值而不是输出其本身。

2、迭代器和指针的区别:迭代器不是指针,是类模板,表现的像指针,迭代器封装了指针,重载了指针的一些操作符,->、*、++、--等,可遍历容器内全部或部分元素的对象,相当于一种智能指针,可以根据不同类型的数据结构来实现不同++,--的操作。

裸机开发

STM32中HAL库和寄存器开发区别?

在STM32中,使用寄存器编程和使用HAL(硬件抽象层)库编程是两种常见的编程方式,它们各有优缺点。

使用寄存器编程是指直接对微控制器的寄存器进行操作以控制硬件。这意味着开发者需要直接读取和写入硬件的寄存器地址。

优点:

  1. 性能高:由于直接操作硬件,减少了中间层的开销,因此代码执行速度更快。
  2. 灵活性:提供了对硬件的完全控制,可以实现特定的硬件优化。

缺点:

  1. 开发难度大:需要深入了解硬件的结构和寄存器地址,编写代码时容易出错。
  2. 可移植性差:不同的微控制器可能有不同的寄存器结构和地址,因此代码不易移植到其他平台。

使用HAL库编程,HAL库提供了一种硬件抽象的方式,使开发者可以通过函数调用来操作硬件,而无需关心具体的寄存器地址。

优点:

  1. 开发简单:提供了易于使用的API,降低了开发难度。
  2. 可移植性好:HAL库通常支持多种微控制器,因此代码可以很容易地移植到其他平台。
  3. 易于维护:由于使用了抽象层,当硬件发生变化时,只需要修改底层实现,而无需修改上层代码。

缺点:

  1. 性能开销:由于使用了中间层,相对于直接操作寄存器,可能会有一定的性能开销。
  2. 灵活性受限:HAL库提供的是通用的API,可能无法实现某些特定的硬件优化。

举个例子

假设我们要在STM32上设置一个GPIO引脚的输出值。

使用寄存器编程:

c 复制代码
// 假设GPIOA的基址是GPIOA_BASE  
#define GPIOA_ODR  (*((volatile uint32_t *)(GPIOA_BASE + 12)))  

void set_gpio_output(uint16_t pin, uint8_t value) {  
    if (value) {  
        GPIOA_ODR |= (1 << pin);  
    } else {  
        GPIOA_ODR &= ~(1 << pin);  
    }  
}

使用HAL库编程:

c 复制代码
#include "stm32f1xx_hal.h"  

void set_gpio_output(GPIO_TypeDef* GPIOx, uint16_t pin, uint8_t value) {  
    if (value) {  
        HAL_GPIO_WritePin(GPIOx, pin, GPIO_PIN_SET);  
    } else {  
        HAL_GPIO_WritePin(GPIOx, pin, GPIO_PIN_RESET);  
    }  
}

在上面的例子中,使用寄存器编程需要直接操作硬件的寄存器地址,而使用HAL库编程则通过调用HAL_GPIO_WritePin函数来实现相同的功能。可以看到,使用HAL库编程的代码更加简洁和易于理解,同时也提高了代码的可移植性。然而,如果你对硬件有深入的了解,并且需要实现特定的硬件优化,那么使用寄存器编程可能更加合适。

驱动开发

说说bootloader?

BootLoader是在操作系统内核运行之前运行的一段小程序,主要用于初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,由于通常并没有像BIOS那样的固件程序,因此整个系统的加载启动任务就完全由BootLoader来完成。

BootLoader的启动过程可以是单阶段的,也可以是多阶段的。多阶段的BootLoader通常能提供更为复杂的功能和更好的可移植性。它通常分为stage1和stage2两大部分,stage1一般使用汇编语言编写,以达到短小精悍的目的;而stage2则常用C语言编写,以提高代码的可读性和可移植性。

另外,BootLoader的具体实现还依赖于CPU的体系结构。例如,在一个基于ARM7TDMI core的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。

总的来说,BootLoader在嵌入式系统和计算机启动过程中扮演着至关重要的角色,负责初始化硬件、设置环境,并最终加载并启动操作系统或特定的程序。

bootloader启动为什么要开启icache和关闭dcache和tlb?

在bootloader启动阶段,需要开启icache(指令缓存)和关闭dcache(数据缓存)以及tlb(转换后备缓冲器)的原因与它们各自的功能和特性有关。

首先,icache用于存储CPU最近访问过的指令,使得CPU在再次需要这些指令时能够直接从缓存中快速获取,而不是从主存中读取,从而大大提高了指令的访问速度。在bootloader启动阶段,CPU需要频繁地访问指令来执行各种初始化操作,因此开启icache可以显著提高启动速度。

其次,dcache用于存储CPU最近访问过的数据,它的主要目的是加速数据的访问。然而,在bootloader启动阶段,由于内存尚未完全初始化,数据缓存可能会导致数据访问异常,因此通常需要关闭dcache以避免潜在的问题。

至于tlb,它是用于缓存虚拟地址到物理地址的映射关系的。然而,在bootloader阶段,地址映射尚未建立,因此tlb并无实际作用,而且开启tlb可能会增加系统复杂性并降低效率。因此,关闭tlb是合理的选择。

总的来说,开启icache和关闭dcache、tlb在bootloader启动阶段是为了优化启动性能、避免潜在问题并简化系统配置。这些设置确保了CPU能够高效、稳定地执行启动过程中的各项任务。

初始化一个串口、检测系统内存映射、将内核映像和根文件系统映像从flash读到SDRAM空间中、为内核设置启动参数以及调用内核是Bootloader在执行其第二阶段任务时的主要步骤。

  1. 初始化串口

目的:串口(通常称为UART,即通用异步收发器)是一种用于调试和系统日志输出的常见接口。初始化串口是为了让Bootloader能够通过串口输出信息,从而方便开发者了解启动过程中的状态和错误信息。

步骤

  • 配置串口通信参数,如波特率、数据位、停止位和校验位。
  • 启用串口中断(如果需要的话)。
  • 测试串口是否工作正常。
  1. 检测系统内存映射

目的:确定系统的物理内存布局和大小,以便Bootloader和内核知道哪些地址范围是可用的RAM空间。

步骤

  • 读取存储在特定位置的内存映射信息(这些信息通常由硬件平台或引导程序提供)。
  • 解析这些信息,获取RAM的起始地址和大小。
  • 将这些信息保存在合适的数据结构中,供后续使用。
  1. 将内核映像和根文件系统映像从Flash读到SDRAM空间中

目的:在启动过程中,操作系统内核和根文件系统通常存储在非易失性存储器(如Flash)中。为了执行这些程序,需要将它们加载到RAM(通常是SDRAM)中。

步骤

  • 读取Flash中的内核映像和根文件系统映像。
  • 将这些数据块复制到SDRAM中的适当位置。
  • 验证数据是否正确复制(通过校验和或其他机制)。
  1. 为内核设置启动参数

目的:内核在启动时需要一系列参数,如命令行选项、内存布局、设备信息等。这些参数通常由Bootloader提供。

步骤

  • 创建一个参数结构或表,包含内核所需的各种参数。
  • 将这些参数放置在内存中一个预定的位置(通常是内核启动后能够访问到的地址)。
  • 确保内核启动时能够找到并解析这些参数。
  1. 调用内核

目的:将控制权从Bootloader转移到操作系统内核,开始执行内核的初始化代码。

步骤

  • 设置CPU到正确的模式或状态,以准备执行内核代码。
  • 跳转到内核的入口点(即内核映像在内存中的起始地址)。
  • 一旦跳转到内核,Bootloader的任务就完成了,接下来的工作由内核负责。

这些步骤是Bootloader在启动操作系统之前必须完成的关键任务。它们确保了硬件得到适当的配置,操作系统映像被正确加载,并且内核能够接收到必要的信息来成功启动。每个步骤的正确执行都是系统稳定启动的关键。

网络编程

管道(Pipe)

管道(Pipe):管道是一种进程间通信的方式,可以在不同进程之间传递数据。可以使用管道函数如pipe()、read()和write()等来实现进程间通信。

在Unix和Linux中,管道通常通过以下系统调用进行创建和使用:

  1. pipe():这个函数用于创建一个管道。它接受一个文件描述符数组作为参数,并返回两个文件描述符:一个用于读取(通常称为读端),另一个用于写入(通常称为写端)。
  2. read()write():这两个函数分别用于从管道中读取数据和向管道中写入数据。read() 函数从指定的文件描述符(在这种情况下是管道的读端)读取数据,而 write() 函数则将数据写入指定的文件描述符(在这种情况下是管道的写端)。

需要注意的是,管道是半双工的,也就是说,数据只能在一个方向上流动。此外,管道只能用于具有共同祖先的进程之间(例如,一个父进程和它的子进程)。

对于需要全双工通信或在不相关的进程之间进行通信的场景,可能需要使用其他类型的IPC机制,如套接字(Sockets)、共享内存(Shared Memory)或消息队列(Message Queues)等。

总的来说,管道是一种简单而有效的进程间通信方式,特别适用于需要在具有共同祖先的进程之间传递数据的场景。

套接字(Socket)

套接字的工作原理可以类比为电话系统中的电话线和电话机,每个套接字都类似于一个电话机,而网络则类似于电话线。通过套接字,进程可以发送和接收数据,就像通过电话机进行通话一样。

在使用套接字进行通信时,通常涉及到以下几个关键步骤和函数:

  1. 创建套接字 :使用 socket() 函数创建一个新的套接字。这个函数接受三个参数:地址族(如AF_INET表示IPv4)、套接字类型(如SOCK_STREAM表示TCP套接字)和协议类型(通常为0,表示使用默认协议)。
  2. 绑定地址和端口 :对于服务器端套接字,需要使用 bind() 函数将其绑定到一个特定的地址和端口上。这样,客户端就可以通过这个地址和端口来连接到服务器。
  3. 监听连接请求 :对于服务器端套接字,还需要使用 listen() 函数来监听来自客户端的连接请求。这个函数指定了最大连接队列长度,即可以同时等待多少个客户端的连接请求。
  4. 建立连接 :对于客户端套接字,使用 connect() 函数来建立与服务器的连接。这个函数接受服务器的地址和端口作为参数。
  5. 发送和接收数据 :一旦连接建立,就可以使用 send()recv()(或 sendto()recvfrom() 对于无连接套接字)等函数来发送和接收数据了。这些函数允许在连接的双方之间传输字节流。
  6. 关闭套接字 :通信完成后,使用 close() 函数来关闭套接字,释放资源。

需要注意的是,套接字的通信模式可以是面向连接的(如TCP),也可以是无连接的(如UDP)。面向连接的套接字在发送和接收数据之前需要先建立连接,而无连接的套接字则不需要。此外,套接字还支持多种不同的通信选项和配置,可以根据具体需求进行设置。

应用开发

进程等于程序吗?

进程(Process)并不完全等同于运行的程序。进程是操作系统进行资源分配和调度的基本单位,是程序执行时的一个实例。换句话说,进程是程序的一次执行过程,是动态的概念,是程序在执行过程中分配和管理资源的基本单位。

当我们运行一个程序时,操作系统会为其分配相应的资源(如内存空间),并创建一个进程来执行这个程序。这个进程会包含程序的代码、数据以及执行上下文等信息。因此,进程可以看作是程序在操作系统中的一次具体执行。

然而,程序本身只是一组静态的指令和数据,它存储在硬盘等存储介质中,只有当它被加载到内存中并由操作系统创建为一个进程后,它才能被执行。所以,进程是程序执行的一个动态实体,而程序是进程执行的静态基础。

总结来说,进程是运行的程序的一个实例,是程序在执行过程中的一个动态表现。进程和程序在概念上是有所区别的,但它们是密切相关的。

相关推荐
醒了就刷牙42 分钟前
Leetcode 面试150题 88.合并两个有序数组 简单
算法·leetcode·面试
楚疏笃1 小时前
鸿蒙学习统一上架与多端分发-应用分发(2)
学习·华为·harmonyos
谢白羽1 小时前
CUDA补充笔记
笔记
醉陌离1 小时前
渗透测试笔记—shodan(7完结)
笔记
XR-AI-JK1 小时前
UE5肉鸽游戏教程学习
学习·游戏·ue5·ue4·新手学习·游戏教程·ue教程
咔叽布吉2 小时前
【前端学习笔记】ES6 新特性
前端·笔记·学习
Lostgreen2 小时前
SQL on Hadoop
数据库·hadoop·笔记·分布式·sql·学习
坚硬果壳_2 小时前
《硬件架构的艺术》笔记(八):消抖技术
笔记·硬件架构