嵌入式常见面试题二

一、判断题

(1)C语言中,凡不加类型说明的函数,一律按照整型处理:(错)

在 C 语言中,如果函数没有提供类型说明,则编译器会默认将函数声明为返回类型为 int 的函数。这种隐式规则适用于旧版 C 标准(如 ANSI C)中,但在较新的 C 标准(如 C99 和之后的标准)中,函数没有显式声明返回类型会被视为编译错误。

因此,尽管在旧版 C 标准中可能会按照整型处理,但更好的做法是始终为函数提供明确的返回类型声明,以确保代码的清晰性和可移植性。在现代的编程实践中,最好遵循新版的 C 标准,并为所有函数提供明确的返回类型声明。

(2)#ifdef A && B #endif 当A和B都为真时,其间定义的代码将被执行:(错)

#ifdef A
    #ifdef B
        // 在A和B都为真时执行的代码
    #endif
#endif

在上面的代码中,首先使用 #ifdef A 来检查宏 A 是否被定义,然后在宏 A 被定义的情况下再通过 #ifdef B 来检查宏 B 是否被定义。只有当宏 A 和宏 B 都被定义时,内部的代码才会被执行。

需要注意的是,在预处理阶段,#ifdef 主要用于检查某个宏是否被定义,而不是判断条件表达式的真假。因此,要实现在 A 和 B 都为真时执行代码的功能,需要结合宏定义和条件编译的方式来实现。

(3)在switch语句中多个case短语可以供用一个程序段(错)

在C语言中,不允许在switch语句中多个case共用一个程序段

(4)(void *)ptr1和(*(void**))ptr1的结果是否相同?其中 ptr1为同一个指针(不同)

  1. (void *)ptr1:这个表达式表示将指针 ptr1 转换为 void * 类型的指针,即将原始指针的类型信息丢弃,得到一个 void 类型的指针。这种操作通常用于在不同类型的指针之间进行转换或者在函数接口中使用泛型指针。

  2. (*(void **))ptr1:这个表达式包含了两步操作。首先,ptr1 被解引用(*ptr1),得到指向原始指针所指向的内存地址的指针。然后,这个指针被强制转换为一个 void * 类型的指针。

总结来说,*(void**)ptr1 的操作会先解引用 ptr1 得到一个指针,再将这个指针转换为指向 void 类型的指针。而 (void*)ptr1 则是直接将 ptr1 转换为 void 类型的指针。因此,虽然最终结果都是指向 void 类型的指针,但中间操作是不同的,因此这两个表达式的结果可能会不相同。

二、对下列关键字用途做出说明

(1)sfr

在 C51 编译器中,sfr 是一个特定的关键字,用于声明特殊功能寄存器(Special Function Register)。特殊功能寄存器是嵌入式系统中常见的一种寄存器,通常用于与外设或特定硬件模块进行通讯和控制。

使用 sfr 关键字的语法如下

sfr sfr_name = address;

其中,sfr_name 是要声明的特殊功能寄存器的名称,address 是寄存器所在的地址。

通过使用 sfr 关键字,可以将特殊功能寄存器映射到特定的内存地址,以便在程序中直接访问这些硬件寄存器。这样可以方便地对外设进行控制和配置,是嵌入式系统开发中常用的技术之一。

(2)bdata

在 C51 编译器中,bdata 是一个特定的关键字,用于声明位数据(bit data)。在嵌入式系统开发中,经常需要对特定的位进行操作,而 bdata 关键字正是为了方便地处理位数据而引入的。

使用 bdata 关键字的语法如下:

bdata bdata_name;

其中,bdata_name 是要声明的位数据变量名。

通过使用 bdata 关键字,可以将一个字节的内存空间划分为 8 个独立的位,每个位可以单独访问和操作。这样可以更加方便地对位数据进行读写和控制,尤其适用于处理各种标志位、状态位或者配置寄存器。

在实际应用中,使用 bdata 声明的位数据变量可以像操作普通的字节变量一样进行读写操作,但是可以更细粒度地控制每一个位。这种方式可以提高程序的可读性和可维护性,同时也方便了对位操作的编程。

需要注意的是,在使用 bdata 声明的位数据变量时,编译器会根据具体的硬件架构和位操作指令来生成相应的位操作代码,以确保位操作的正确性和效率性

三.问答题

(1)要对MCU绝对地址0x100000赋值,我们可以用*(volatile uint32_t *)0x100000=1234;那么要是想让程序跳转绝对地址是0x100000去执行,应该怎么做?

要让程序跳转到 MCU 的绝对地址 0x100000 处执行,你可以使用函数指针的方式来实现。在 C 语言中,可以通过定义一个函数指针,将其指向地址为 0x100000 的函数,然后通过调用该函数指针来实现跳转执行。

以下是一个示例代码:

// 定义一个函数指针类型 
typedef void (*FunctionPointer)(void); 
// 将函数指针指向地址为 0x100000 的函数
 FunctionPointer ptr = (FunctionPointer)0x100000; 
// 调用函数指针,实现跳转执行 
ptr();

在这段代码中,我们首先定义了一个函数指针类型 FunctionPointer,然后将该函数指针 ptr 指向地址为 0x100000 的函数(假设该地址处存储的是可执行代码)。最后通过调用函数指针 ptr() 来实现程序跳转到 0x100000 地址处执行。

(2)用C语言实现将一整型数字转化为字符串

#include <stdio.h>

void intToString(int num, char *str) {
    int i = 0;
    int isNegative = 0;
    // 处理负数情况
    if (num < 0) {
        isNegative = 1;
        num = -num;
    }
    // 将数字逐位转换为字符存入字符串
    do {
        str[i++] = num % 10 + '0';
        num /= 10;
    } while (num > 0);
    // 如果是负数,加上负号
    if (isNegative) {
        str[i++] = '-';
    }
    // 反转字符串
    int start = 0;
    int end = i - 1;
    while (start < end) {
        char temp = str[start];
        str[start] = str[end];
        str[end] = temp;
        start++;
        end--;
    }
    // 添加字符串结束符
    str[i] = '\0';
}

int main() {
    int num = -12345;
    char str[20]; // 假设最多支持20位整数转字符串

    intToString(num, str);
    printf("转换后的字符串为: %s\n", str);

    return 0;
}

(3)Arm Core M3内核中出现hardfault中断错误的解决方法。

  1. 空指针引用:当程序试图访问空指针指向的内存地址时会触发 HardFault。解决方法是在访问指针前进行有效性检查,避免空指针引用。

  2. 栈溢出:如果栈空间被耗尽,也会导致 HardFault。可以通过增加栈空间或者优化代码来减少栈空间的使用。

  3. 非法指令:执行非法的指令会导致 HardFault。确保程序中没有错误的汇编指令或函数调用。

  4. 数据对齐错误:在 ARM Cortex-M3 上,某些指令要求数据的地址是对齐的,否则可能触发 HardFault。确保数据的地址满足对齐要求。

  5. 中断优先级错误:中断优先级配置不当可能引起 HardFault。请确保正确设置中断优先级,并避免在中断处理程序中引发其他中断。

当程序出现 HardFault 中断错误时,可以通过以下步骤进行调试和解决问题:

  1. 查看 HardFault 异常的原因和出错地址,以便定位问题。
  2. 使用调试工具(如 Keil、Segger J-Link 等)在硬件上进行调试,查看程序状态、寄存器值等信息。
  3. 在程序中添加适当的错误处理机制,例如使用断言或错误处理函数来捕获潜在的错误情况。
  4. 检查代码中可能导致 HardFault 错误的地方,例如指针操作、栈使用等,并进行修正。

(3)IIC中,一个字节的数据传输完毕需要多少个SCL脉冲信号

  1. 起始条件:主设备发出一个起始条件的 SCL 脉冲和一个高电平的 SDA 数据线来启动通信。
  2. 地址传输:主设备发送从设备地址(包括读/写位)的 8 位,每一位都在 SCL 的时钟脉冲中被传输,共计 8 个时钟脉冲。
  3. 应答:主设备发送一个停止条件的时钟脉冲,并释放 SDA,等待从设备发送 ACK 信号。
  4. 数据传输:如果从设备发送 ACK 信号,主设备就可以发送数据。每个数据字节也需要 8 个时钟脉冲来传输。
  5. 结束条件:主设备发送一个停止条件的 SCL 脉冲,然后拉低 SDA 来结束通信。

因此,在 I2C 总线上,一个字节的数据传输需要 19 个 SCL 脉冲信号。需要注意的是,I2C 协议也支持变化的数据帧格式,例如有些设备可能会使用 10 位地址模式,这样一个字节的数据传输所需的时钟脉冲信号数量也会相应变化。

(4)如何判读一个系统的大小端存储模式?

(1)方法一:int *强制类型转换为char *,用“[]”解引用

void checkCpuMode(void)  
{  
    int c = 0x12345678;  
    char *p = (char *)&c;  
    if(p[0] == 0x12)  
        printf("Big endian.\n");  
    else if(p[0] == 0x78)  
        printf("Little endian.\n");  
    else  
        printf("Uncertain.\n");  
}  
(2)方法二:int *强制类型转换为char *,用“*”解引用

void checkCpuMode(void)  
{  
    int c = 0x12345678;  
    char *p = (char *)&c;  
    if(*p == 0x12)  
        printf("Big endian.\n");  
    else if(*p == 0x78)  
        printf("Little endian.\n");  
    else  
        printf("Uncertain.\n");  
}  
(3)方法三:包含short跟char的共用体

void checkCpuMode(void)  
{  
    union Data  
    {  
        short a;  
        char b[sizeof(short)];  
    }data;  
    data.a = 0x1234;  
  
    if(data.b[0] == 0x12)  
        printf("Big endian.\n");  
    else if(data.b[0] == 0x34)  
        printf("Little endian.\n");  
    else  
        printf("uncertain.\n");  
} 

(5)全局变量可不可以定义在可被多个.C文件包含的头文件中?

可以,但要将该全局变量定义为static全局变量,否则会重复定义,但此时没有达到一般意义上的全局变量的效果,而是静态全局变量。

在 C 语言中,全局变量的定义通常应该避免放在头文件中,因为如果一个头文件被多个 .c 文件包含,就会导致同一个全局变量被多次定义,从而引起重定义错误。

通常情况下,头文件中应该只包含函数声明、宏定义、类型定义等内容,而不应该包含全局变量的定义。如果需要在多个文件中共享全局变量,应该将全局变量的声明放在头文件中,而将全局变量的定义放在一个单独的 .c 文件中。

如果一定要在头文件中定义全局变量,并且希望能够被多个 .c 文件包含,可以使用关键字 extern 来声明全局变量,然后在一个 .c 文件中定义该全局变量。其他包含该头文件的 .c 文件可以通过 extern 声明来引用这个全局变量,而不会导致重定义错误。

(6)串口如何发送和接收浮点型数据?

1.在串口通信中发送浮点型数据需要先将浮点数转换为字节数组,然后逐个发送字节到串口。接收端需要按照相同的方式将接收到的字节重新组装成浮点数。

2.要从串口接收浮点型数据,你需要按照相反的方式将接收到的字节重新组装成浮点数。通常,你可以按照以下步骤进行操作:

  1. 从串口接收字节,并将这些字节存储在一个缓冲区中。
  2. 将接收到的字节重新组装成浮点数。

#include <stdio.h>
#include <stdint.h>

void sendFloatOverSerial(float floatValue) {
    uint8_t *byteArray = (uint8_t *)&floatValue; // 将浮点数转换为字节数组

    for (int i = 0; i < sizeof(float); i++) {
        // 发送每个字节到串口
        // 假设这里有一个函数 sendByteToSerial() 可以发送一个字节到串口
        sendByteToSerial(byteArray[i]);
    }
}

int main() {
    float myFloat = 3.14f; // 示例浮点数
    sendFloatOverSerial(myFloat); // 发送浮点数到串口

    return 0;
}



// 假设接收函数为 receiveByteFromSerial(),用于从串口接收一个字节
// 假设浮点数为4字节长度,即32位
float receiveFloatFromSerial() {
    uint8_t byteArray[4]; // 用于存储接收到的4个字节
    float floatValue;

    // 从串口接收4个字节
    for (int i = 0; i < 4; i++) {
        byteArray[i] = receiveByteFromSerial();
    }

    // 将4个字节重新组装成浮点数
    floatValue = *((float *)&byteArray);

    return floatValue;
}

int main() {
    float receivedFloat;
    receivedFloat = receiveFloatFromSerial(); // 从串口接收浮点数
    printf("Received float: %f\n", receivedFloat);

    return 0;
}

(7)如下代码,最终 value的值是  2

int *p1,*p2;

int value;

p1=(int*)0×400;

p2=(int*)0×408;

value = p2-p1;

根据C语言指针运算的规则,两个指针相减的结果是两个指针之间相差的元素个数,而不是地址之间的差值 

指针 p1p2 分别指向了两个不同的地址,它们的差值是 0x408 - 0x400 = 8,这是它们在内存中相隔的字节数。由于这里的指针类型是 int*,所以它们之间相隔的元素个数应该是 8 / sizeof(int),而 sizeof(int) 的大小通常为4个字节(32位系统下),所以计算得到的元素个数为 8 / 4 = 2

(8)以下打印的数据结果是

int arr[] = { 1,2,3,4,5,6,7,8 };

int* p = arr;

*(p++) += 10;

printf(" % d, % d " ,arr[0],*(p));

printf(" % d ",*(++p));

  1. int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 };

    • 这行代码定义了一个整型数组arr,包含8个元素。
  2. int p = arr;*

    • 将指针p指向数组arr的第一个元素,也就是arr[0]。
  3. *(p++) += 10;

    • 这里使用了后置自增运算符,先计算p指向的元素值加上10,然后p指针向后移动一个元素的位置。
    • 在这里,*(p++)实际上相当于arr[0] += 10,即arr[0]的值变为11。
  4. *printf(" %d, %d ", arr[0], (p));

    • 打印出arr[0]和*(p)的值,即数组arr的第一个元素和p当前指向的元素的值。
    • arr[0]的值已经在前面的操作中改变为11,所以打印出来为11。
    • (p)表示p当前指向的元素的值,由于p已经指向arr[1](在前面的操作中自增了),所以(p)为arr[1]的值,即2。
  5. *printf(" %d ", (++p));

    • 这里使用了前置自增运算符,先让p指向下一个元素,然后取该元素的值进行打印。
    • ++p将p指向arr[2],所以*(++p)即arr[2]的值,为3。

(9)以下语句不能正确赋值的是(A

A. char s1[10]; s1="China”;

B. char s2[ ]={’C’,’h’,’i’,’n’,'a’};

C. char s3[20]=" China”;

D. char *s= " China”;

A. char s1[10]; s1="China”;

  • 这个语句是错误的。在C语言中,数组名是一个常量指针,不可以将一个字符串常量直接赋值给一个数组变量。数组名实际上是一个常量指针,而不是常量地址。当使用数组名时,它会被解释为指向数组第一个元素的常量指针。这使得数组名可以被看作一个常量指针,而不是一个可变的地址。因此,在许多情况下,数组名可以被当做指针来使用,但它本身的值是不可更改的

正确的写法应该是使用strcpy函数将一个字符串复制给数组:

B. char s2[] = {'C', 'h', 'i', 'n', 'a'};

  • 这个语句是正确的,初始化了一个字符数组 s2 并赋值为 "China"。
  • 当你使用 char s1[10] = "China"; 这样的语法时,C语言会自动将 "China" 复制到数组 s1 所指向的内存中。这是因为在初始化时,编译器会自动为 s1 分配内存,并将 "China" 的内容复制到这个内存中。

C. char s3[20] = " China”;

  • 这个语句是正确的,初始化了一个长度为20的字符数组 s3 并赋值为 " China"。

D. char *s = " China”;

  • 这个语句是正确的,定义了一个指向常量字符串的指针 s,并将其指向 " China" 这个字符串的首地址。
  • 33
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 什么是嵌入式系统? 嵌入式系统是一种特殊的计算机系统,它通常被嵌入到其他设备中,如智能手机、电视、汽车、医疗设备等。嵌入式系统具有低功耗、高性能、实时性要求等特点。 2. 嵌入式系统与普通计算机有什么不同? 嵌入式系统通常具有较小的尺寸、低功耗、高可靠性、实时性要求以及特定的功能需求。它们的硬件和软件都是为了满足特定的应用需求而设计的。 3. 嵌入式系统中常用的处理器架构有哪些? 常见的处理器架构包括ARM、MIPS、PowerPC、x86等。 4. 嵌入式系统中常用的操作系统有哪些? 常见嵌入式操作系统包括FreeRTOS、uC/OS、Linux、Windows CE等。 5. 嵌入式系统中常用的通信协议有哪些? 常见的通信协议包括UART、SPI、I2C、CAN、USB、Ethernet等。 6. 嵌入式系统中常用的编程语言有哪些? 常见的编程语言包括C、C++、Assembly等。 7. 嵌入式系统中如何进行调试? 常用的调试方法包括printf调试、LED指示灯调试、仿真器调试、逻辑分析仪调试等。 8. 嵌入式系统中如何进行电源管理? 电源管理主要包括功耗控制、电池管理、供电稳定等。常用的电源管理技术包括休眠模式、时钟频率调整、电源管理芯片等。 9. 嵌入式系统中如何进行存储管理? 存储管理主要包括程序存储、数据存储、配置存储等。常用的存储设备包括FLASH、EEPROM、SD卡、硬盘等。 10. 嵌入式系统中如何进行实时性任务调度? 常见的实时任务调度算法包括优先级调度、时间片轮转调度、最短剩余时间优先调度等。常用的实时操作系统包括FreeRTOS、uC/OS等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值