面试(五)

目录

1. 知道大顶端小顶端吗,代码怎么区分大顶端小顶端

2. 计算机中栈地址与内存地址增长方向相反吗?

3. %p和%d输出指针地址

4. 为什么定义第二个变量时候,地址反而减了

5. 12,32,64位中数据的占字节?

6. IIC可以接入多少个设备

7. STM32的三级流水线

8. STM32F4中初始化硬件IIC代码

9. STM32启动流程:初始化确定启动方式->sp,pc指针->系统时钟初始化->初始化用户堆栈->跳到main()

10. STM32F4初始化蓝牙串口和蓝牙HC-05所有配置的代码

11. MCU/DSP

12. 学一个新板子,我们要先知道什么

13. STM32F4中函数怎么压栈到寄存器

14. STM32为什么中断会进入UART4_IRQHandler()

15. STM32收到串口数据时寄存器的状态

16. C++中链表的插入,删除


1. 知道大顶端小顶端吗,代码怎么区分大顶端小顶端

(1)大顶端: 将数据的最高有效字节存储在起始地址,即高位字节在前。小顶端: 将数据的最低有效字节存储在起始地址,即低位字节在前。

假如现有一32位int型数0x12345678,那么其MSB(Most Significant Byte,最高有效字节)为0x12,其LSB (Least Significant Byte,最低有效字节)为0x78,在CPU内存中有两种存放方式:(假设从地址0x4000开始存放)[一个地址下存放一个字节]

(2) 代码区分大顶端和小顶端。

代码1:

#include <stdio.h>

int main() {
    unsigned int x = 0x12345678;
    unsigned char *p = (unsigned char *)&x;

    if (*p == 0x78) {
        printf("小端");
    } else if (*p == 0x12) {
        printf("大端");
    } else {
        printf("未知");
    }

    return 0;
}

指针类型为char而不是int,是因为我们只关心内存中的最低有效字节(LSB),即最低位的字节。在这个例子中,我们想要检查一个整数的字节序,所以我们需要将整数的地址转换为一个字符指针,然后通过解引用这个指针来访问最低有效字节。这样我们就可以根据这个字节的值来判断整数的字节序是大端还是小端。

代码2:

#include <stdio.h>

union {
    unsigned int i;
    unsigned char c[4];
} u;

int main() {
    u.i = 0x12345678;

    if (u.c[0] == 0x78) {
        printf("小端");
    } else if (u.c[0] == 0x12) {
        printf("大端");
    } else {
        printf("未知");
    }

    return 0;
}
在这个例子中,我们使用了一个联合体,它包含一个整数和一个字符数组。我们将整数赋值给联合体的整型成员,然后通过访问字符数组的第一个元素来判断字节序。如果第一个元素的值是0x78,那么就是小端;如果第一个元素的值是0x12,那么就是大端;否则就是未知。

2. 计算机中栈地址与内存地址增长方向相反吗?

计算机内存地址增长的方向是自小到大。栈地址增长方向与内存地址增长方向相反。

3. %p和%d输出指针地址

是把指针变量p中存储的a的地址以十六进制形式输出,%d是吧p中存储的a的地址以十进制形式输出。输出的都是p的值,不要理解成%p,是把p的地址输出。

//3和4的代码
#include <stdio.h>

int main()
{
    int a = 2;
    int b = 3;
    char c = 'a';
    int *p = &a;
    
    printf("%d\r\n",&a);
    printf("%p\r\n",&a);
    printf("%p\r\n",&b);
    printf("%p\r\n",&c);
    printf("%p\r\n",p);
    p++;
    printf("%p\r\n",p);
    return 0;
}

4. 为什么定义第二个变量时候,地址反而减了

因为栈向下增长,所以先声明的地址大,后声明的地址小。栈的这种向下增长的设计使得它在处理函数调用时非常高效。每次函数调用时,都会在栈顶创建一个新的栈帧,用来存放该函数的局部变量和返回地址。当函数执行完毕返回时,对应的栈帧会被自动清理,栈顶恢复到调用前的位置。

5. 12,32,64位中数据的占字节?

8位系统 32位系统 64位系统
字符型 1字节 1字节 1字节
整型 2字节 4字节 4字节

长整型

4字节 4字节 4字节

浮点型

无统一标准 4字节 4字节

双精度浮点型

无统一标准 8字节 8字节

指针类型

无统一标准 4字节 8字节

6. IIC可以接入多少个设备

IIC最多可以接入2的7次方-1个设备,一共是127个。
第一个字节(为slave address)由7位地址和一位R/W读写位组成的,这字节是个器件地址。
首先,你要知道:常用IIC接口通用器件的器件地址是由种类型号,及寻址码组成的,共7位。
如格式如下: 
  D7 D6 D5 D4 D3 D2 D1 D0
1-器件类型由:D7-D4 共4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的。

2-用户自定义地址码:D3-D1共3位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IC的3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。
所以为什么同一IIC总线上同一型号的IC只能最多共挂8片同种类芯片的原因了。

3-最低一位就是R/W位。这位不用我多说了

7. STM32的三级流水线

三个主要步骤:取值,译码和执行,取指阶段是从存储器中加载指令到指令寄存器;译码阶段是解释指令含义并准备执行所需的操作;执行阶段则实际完成指令的操作,并将结果写回寄存器。流水线允许多个指令在不同的阶段同时进行处理。例如,当第一条指令处于执行阶段时,第二条指令可能正在进行译码,而第三条指令则在取指阶段

8. STM32F4中初始化硬件IIC代码

#include "stm32f4xx.h"

void I2C1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitStructure;

    // 开启I2C1时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    // 开启GPIOB时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

    // 配置PB6和PB7为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 连接PB6和PB7到I2C1的SCL和SDA引脚
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);

    // 初始化I2C1
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 100000; // 设置I2C时钟速率为100kHz
    I2C_Init(I2C1, &I2C_InitStructure);

    // 使能I2C1
    I2C_Cmd(I2C1, ENABLE);
}

9. STM32启动流程:初始化确定启动方式->sp,pc指针->系统时钟初始化->初始化用户堆栈->跳到main()

10. STM32F4初始化蓝牙串口和蓝牙HC-05所有配置的代码

#include "stm32f4xx.h"
#include "usart.h"
#include "bluetooth.h"

void Bluetooth_Init(void)
{
    // 初始化USART1,用于蓝牙通信
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 9600; // 设置波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 设置数据位长度为8位
    USART_InitStructure.USART_StopBits = USART_StopBits_1; // 设置停止位为1位
    USART_InitStructure.USART_Parity = USART_Parity_No; // 不使用奇偶校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 设置为收发模式
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE); // 使能USART1

    // 初始化蓝牙模块
    Bluetooth_Config();
}

void Bluetooth_Config(void)
{
    // 发送AT指令来配置HC-05模块
    // 设置蓝牙名称
    Bluetooth_SendCommand("AT+NAME=HC-05");
    // 设置蓝牙配对密码
    Bluetooth_SendCommand("AT+PSWD=1234");
    // 设置蓝牙工作模式为可连接模式
    Bluetooth_SendCommand("AT+MODE=0");
    // 开启蓝牙模块
    Bluetooth_SendCommand("AT+POWE=1");
}

11. MCU/DSP

MCU是将计算机的主体部分集成在一块半导体上的单片机。DSP是专门用于数字信号处理的微处理器,特别是擅长处理如离散余弦变换、快速傅里叶变换等复杂计算。

12. 学一个新板子,我们要先知道什么

  • 核心处理器:了解SOC使用的中央处理单元类型,如ARM Cortex系列,以及其基本参数,包括频率、核心数量等。
  • 存储选项:研究SOC的内存布局,包括RAM和FLASH的容量,及其对程序执行和数据存储的影响。
  • 输入输出端口:熟悉可用的GPIO数量及其配置选项,理解如何通过这些接口与外部设备进行交互

13. STM32F4中函数怎么压栈到寄存器

在STM32F4中,函数压栈到寄存器涉及对特定寄存器的保存与恢复,确保程序的正常运行和中断处理的正确返回。具体来说,当进行函数调用或响应中断时,处理器自动把PSR, PC, LR, R12, R3, R2, R1, R0等寄存器的内容按照一定顺序压入栈中.手动需要压栈的寄存器包括R11, R10, R9, R8, R7, R6, R5, R4.

14. STM32为什么中断会进入UART4_IRQHandler()

原因是在于STM32微控制器的中断处理机制,该机制通过硬件触发和自动执行预定义的中断服务程序来响应特定的事件。

中断初始化和配置:在STM32中,要使能串口的中断功能,首先要正确配置相关的控制寄存器和中断向量。这包括启用UART4时钟,配置UART4的工作模式(如波特率、数据位等),以及设置中断优先级和使能特定类型的中断源(例如接收中断、发送完成中断等)

中断向量表的角色:启动文件startup_stm32f4xx.s中包含了一个中断向量表,该表为每个中断源指定了一个处理函数。对于UART4,这个向量表中会包含DCD UART4_IRQHandler这样的指令,直接指向了UART4_IRQHandler()函数的内存地址。当UART4的中断条件满足时,处理器会自动跳转到这个地址执行中断服务程序。

硬件自动压栈操作:响应中断时,Cortex-M内核将自动进行压栈操作,保存被中断的程序执行状态,包括程序计数器(PC)、状态寄存器(PSR)、以及一些关键的寄存器如R0-R3、R12、LR、PC、xPSR的值。这一过程完全由硬件自动完成,确保了中断处理完成后能够恢复到原来被打断的程序继续执行。

中断服务程序的执行:一旦处理器响应了中断并进入了UART4_IRQHandler(),它就会执行该函数中的代码来处理具体的中断事件。这包括读取UART4的数据寄存器以获取接收到的数据,或者根据设置处理其他类型的UART中断事件。

从中断返回:处理完中断事件后,UART4_IRQHandler()函数执行结束,内核会自动恢复之前保存的寄存器值,并从中断发生前的指令继续执行。这个过程称为异常退出处理,确保了程序的正确流程和数据的完整性。

错误处理和调试:如果中断处理过程中遇到问题,如硬件错误或配置不当,可能会触发额外的错误处理机制。在这种情况下,开发人员应检查配置是否正确,以及硬件连接是否稳定可靠。

15. STM32收到串口数据时寄存器的状态

STM32的USART是用于处理串口通信的外设,具备发送和接收数据的能力。而串口接收时,主要是状态寄存器和数据寄存器发生变化。

状态寄存器(USART_SR):此寄存器用于检测USART的当前状态,其中包括多个状态位,如发送寄存器空位、发送完成位、读数据寄存器非空位等。其中最重要的两位是RXNE(读数据寄存器非空)和TC(发送完成)

数据寄存器(USART_DR这是一个双向寄存器,分为发送数据寄存器(TDR)和接收数据寄存器(RDR)。向USART_DR写入数据时,数据会被存储在TDR内,用于后续的发送操作;从USART_DR读取数据时,会自动从RDR提取数据。

16. C++中链表的插入,删除

#include <iostream>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

// 在链表头插入节点
void insertAtHead(ListNode* &head, int val) {
    ListNode* newNode = new ListNode(val);
    newNode->next = head;
    head = newNode;
}

// 在链表尾部插入节点
void insertAtTail(ListNode* &head, int val) {
    ListNode* newNode = new ListNode(val);
    if (head == NULL) {
        head = newNode;
        return;
    }
    ListNode* temp = head;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = newNode;
}

// 在指定位置插入节点
void insertAtPosition(ListNode* &head, int position, int val) {
    if (position == 0) {
        insertAtHead(head, val);
        return;
    }
    ListNode* newNode = new ListNode(val);
    ListNode* temp = head;
    for (int i = 1; i < position && temp != NULL; i++) {
        temp = temp->next;
    }
    if (temp == NULL) {
        cout << "Invalid position!" << endl;
        return;
    }
    newNode->next = temp->next;
    temp->next = newNode;
}

// 删除指定值的节点
void deleteNode(ListNode* &head, int val) {
    if (head == NULL) {
        return;
    }
    if (head->val == val) {
        ListNode* temp = head;
        head = head->next;
        delete temp;
        return;
    }
    ListNode* temp = head;
    while (temp->next != NULL && temp->next->val != val) {
        temp = temp->next;
    }
    if (temp->next == NULL) {
        cout << "Value not found!" << endl;
        return;
    }
    ListNode* toDelete = temp->next;
    temp->next = temp->next->next;
    delete toDelete;
}

// 删除指定位置的节点
void deleteAtPosition(ListNode* &head, int position) {
    if (head == NULL) {
        return;
    }
    if (position == 0) {
        ListNode* temp = head;
        head = head->next;
        delete temp;
        return;
    }
    ListNode* temp = head;
    for (int i = 1; i < position && temp != NULL; i++) {
        temp = temp->next;
    }
    if (temp == NULL || temp->next == NULL) {
        cout << "Invalid position!" << endl;
        return;
    }
    ListNode* toDelete = temp->next;
    temp->next = temp->next->next;
    delete toDelete;
}

相关推荐

  1. Kafka 面试题(

    2024-06-08 22:12:05       27 阅读
  2. Mybatis面试系列

    2024-06-08 22:12:05       32 阅读
  3. 算法面试

    2024-06-08 22:12:05       24 阅读
  4. 网络基础面试题(

    2024-06-08 22:12:05       55 阅读
  5. Golang面试(GC)

    2024-06-08 22:12:05       38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-08 22:12:05       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-08 22:12:05       106 阅读
  3. 在Django里面运行非项目文件

    2024-06-08 22:12:05       87 阅读
  4. Python语言-面向对象

    2024-06-08 22:12:05       96 阅读

热门阅读

  1. SCSS中的结构化伪类选择器详解与示例

    2024-06-08 22:12:05       33 阅读
  2. 基于 PyTorch 的 Python 深度学习:注意力机制

    2024-06-08 22:12:05       28 阅读
  3. linux防止nmap扫描

    2024-06-08 22:12:05       27 阅读
  4. Leetcode 54. 螺旋矩阵(二维数组移动坐标)

    2024-06-08 22:12:05       34 阅读
  5. iperf

    2024-06-08 22:12:05       23 阅读
  6. 爬虫-打包整个小说网站

    2024-06-08 22:12:05       28 阅读
  7. 用Python实现奇怪的疯狂按键需求

    2024-06-08 22:12:05       36 阅读
  8. Django Did you install mysqlclient?

    2024-06-08 22:12:05       35 阅读
  9. 力扣2762. 不间断子数组

    2024-06-08 22:12:05       35 阅读
  10. 使用sqlldr向oracle导入大量数据

    2024-06-08 22:12:05       31 阅读