嵌入式软件开发校招面试笔试题收集与总结

前言

前言:
本文章由俺三年电赛队友兼队长撰写,源文章:蓝牙模块HC05主从配置与连接
队友博客:一个牛逼哄哄的嵌入式软件大佬,长期更新嵌入式软件开发干货

1.内存泄露和野指针出现的可能原因?

内存泄露:
1.未正确使用free函数:在动态分配内存后,如果忘记调用free函数来释放内存,会导致内存泄露。
2.误用指针:将指针指向新的内存块后,如果没有释放之前分配的内存,会导致内存泄露。
3.指针丢失:如果保存了某个指针的地址,但后续无法再找到这个指针来释放内存,也会导致内存泄露。

野指针
1.指针未初始化:声明指针变量但没有初始化,会导致指针指向未知的地址。
2.指针指向已释放的内存:如果指针指向的内存被释放后,没有将指针置为NULL,会导致野指针。
3.函数返回局部变量的地址:如果将函数内部的局部变量地址返回,但该局部变量已经被销毁,会导致野指针

2.C语言自动转换原则

当表达式中存在有符号类型和无符号类型时所有的数都自动转换为无符号类型。

3.介绍一下堆栈

存储内容不同
栈:在函数调用时,栈中存放的是函数中各个参数(局部变量)。栈底下是函数调用后的下一条指令。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

管理方式上不同
栈:由系统自动分配空间,同时系统自动释放空间。例如,声明在函数中一个局部变量“int b“。系统自动在栈中为b开辟空间,当对应的生存周期结束后栈空间自动释放。
堆:需要程序员手动申请并且手动释放,并指明大小。在C语言中malloc函数申请,释放free函数,在C++中new和delete实现。

空间大小不同
栈:获取空间较小。在Windows下,一般大小是1M或2M,当剩余栈空间不足时,分配失败overflow。
堆:获得空间根据系统的有效虚拟内存有关,比较灵活,比较大。

能否产生碎片不同
栈:不会产生碎片,空间连续。
堆:采用的是链表的存储方式,会产生碎片。

生长方向不同
栈:向低地址扩展的数据结构,是一块连续的内存的区域。
堆:向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。

分配方式不同
栈:有2种分配方式——静态分配和动态分配。静态由编译器完成,例如局部变量;动态由alloca函数实现,并且编译器会进行释放。
堆:都是动态分配的,没有静态分配的堆。

分配效率不同
栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来方便。

总的来说就是栈存储的数据又系统决定,堆就比较随意。栈时由系统自动分配释放,堆是由开发人员进行分配释放。栈的可获取空间较小,堆可获取的大小较大,但堆的分配效率较低。由于栈先入后出的特性,使得它不会出现内存碎片。

4.c语言中什么是内联函数,它有什么特点?

在C语言中,内联函数是指将函数的代码直接嵌入到调用该函数的地方,以避免函数调用的开销。这种优化方法被称为“内联扩展”。内联函数的特点包括:

  • 提高程序的执行效率:由于内联函数的代码被直接嵌入到调用它的地方,避免了函数调用的开销,因此在一些需要频繁调用的函数中使用内联可以提高程序的执行效率。
  • 减少函数调用的开销:内联函数的代码被直接嵌入到调用它的地方,因此不需要进行参数传递和返回值处理,减少了函数调用的开销。
  • 编译器自动优化:内联函数的使用是由编译器自动优化的,编译器会根据函数的调用次数、函数体的大小等因素来决定是否使用内联扩展。
  • 可能出现空间和时间上的开销:内联函数的代码被直接嵌入到调用它的地方,因此可能会出现代码膨胀的情况,增加了程序的空间开销。同时,由于内联函数的代码被直接嵌入到调用它的地方,也可能会增加程序的执行时间。
    需要注意的是,内联函数并不是在所有情况下都是最优的选择,具体是否使用内联函数需要根据实际情况进行权衡。

5.c语言中动态库和静态库有什么区别?

在C语言中,动态库和静态库是两种不同的库文件,它们的主要区别在于编译时和运行时的处理方式不同。

静态库(Static Library)是一种在编译时链接的库文件,它包含了一组预编译的目标文件(通常是.o或.obj文件)。在编译时,静态库会被链接到可执行文件中,并成为可执行文件的一部分。因此,静态库在运行时不再需要额外的库文件支持。静态库的主要优点是编译后的可执行文件可以独立运行,不再依赖外部库文件。但是,静态库的缺点是会增加可执行文件的大小,并且如果静态库更新,需要重新编译链接所有使用了该库的可执行文件。

动态库(Dynamic Library)是一种在运行时链接的库文件,它包含了一组可重用的代码和数据。在编译时,动态库并不会被链接到可执行文件中,而是在运行时由操作系统动态加载到内存中。因此,动态库在运行时需要额外的库文件支持。动态库的主要优点是可以减少可执行文件的大小,并且如果多个程序使用了同一个动态库,它们可以共享该库的代码和数据。但是,动态库的缺点是需要额外的运行时开销,并且如果动态库更新,需要重新部署所有使用了该库的程序。

6.什么是交叉编译,为什么需要交叉编译

交叉编译是指在一个平台上生成另一个平台上的可执行代码。简单来说,就是将源代码编译成适应不同体系结构或操作系统的目标代码。对应的还有本地编译,本地编译就是在编译平台下编译的代码只能在本平台进行运行。交叉编译是为了适应不同平台主频、内存等等之间的差异而产生的。

交叉编译的出现主要是为了满足特定需求,例如在嵌入式系统开发中,目标系统可能内存较小、显示设备简陋甚至没有,无法在其上进行本地编译,或者编译所需的编译器无法在目标系统上运行。另外,如果目标平台的CPU架构或操作系统与源平台不同,也需要进行交叉编译。

要进行交叉编译,我们需要在主机平台上安装对应的交叉编译工具链(cross compilation tool chain),然后用这个交叉编译工具链编译源代码,最终生成可在目标平台上运行的代码。

7.阐述一下C的编译过程

1)预处理:宏定义展开(#define)、头文件展开(#include)、条件编译(#ifdef #ifndef #endif)等,同时将代码中的注释删除,这里并不会检查语法

2)编译:检查语法,将预处理后文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去
.c转.i .i转.s .s转.o .o转可执行文件,这个可执行文件在windows中通常是.exe

8.void *malloc( size_t size );其中void *表示什么

表示返回指向 void 的指针,可以转换为任何数据类型

9.C语言中变量声明有哪些?它们又有什么区别?

在C语言中,变量声明的方式主要有以下几种:

  • 1.静态变量声明:使用static关键字声明的变量为静态变量。静态变量在程序运行期间一直存在,其生命周期为整个程序运行期间。静态变量在编译时被分配内存,并在程序运行期间一直存在,直到程序结束才被释放。静态变量在函数调用时也可以保持其值。
  • 2.全局变量声明:在函数外部声明的变量为全局变量。全局变量在程序运行期间一直存在,其生命周期为整个程序运行期间。全局变量在编译时被分配内存,并在程序运行期间一直存在,直到程序结束才被释放。全局变量在函数调用时也可以保持其值。
  • 3.局部变量声明:在函数内部声明的变量为局部变量。局部变量的生命周期只在函数执行期间存在,当函数执行结束后,局部变量被释放。局部变量在运行时被分配内存,并只在函数执行期间存在。
  • 4.寄存器变量声明:使用register关键字声明的变量为寄存器变量。寄存器变量的生命周期只在函数执行期间存在,当函数执行结束后,寄存器变量被释放。寄存器变量存储在CPU寄存器中,访问速度比内存快,适用于需要频繁访问的变量。
  • 5.外部变量声明:使用extern关键字声明的变量为外部变量。外部变量的生命周期和全局变量相同,但可以在不同的文件中进行声明和定义。外部变量在编译时被分配内存,并在程序运行期间一直存在,直到程序结束才被释放。 _
    我觉得使用extern声明的变量不会再分配内存了,它只是说明了某一个全局变量来自外部文件。_

这些变量声明的区别主要在于变量的生命周期和作用域不同。静态变量和全局变量的生命周期为整个程序运行期间,而局部变量的生命周期只在函数执行期间存在。寄存器变量的存储位置不同,访问速度更快。外部变量可以在不同的文件中进行声明和定义。

10.define和const声明的常量有什么区别?

  • 1.是否分配内存: const声明的常量可以通过指针间接修改(会有编译的时候会有警告),在C语言中,#define是预处理指令,常用于定义宏。当编译器在预处理阶段遇到#define定义的宏时,它会用定义的内容进行替换,但不会为这些内容分配内存。
  • 2.const定义的常量是有数据类型的,而define定义的常量只是简单的文本替换,没有数据类型。
  • 3.const常量具有作用域,只在定义它的作用域内有效,而define常量没有作用域,可以在文件的任何地方使用。
  • 4.const常量在编译时进行类型检查,可以发现一些隐含的错误,而define常量只是简单的文本替换,不会进行类型检查。
    _需要注意的是

11.strlen()和sizeof()的区别

  • 1.strlen(): 这是一个函数,通常用于计算字符串(以 null 结尾的字符数组)的长度。strlen() 函数从字符串的开头开始,逐个计数字符,直到遇到结束字符 ‘\0’,然后计算出字符串的长度。例如,对于字符串 “hello”,strlen() 将返回 5,因为它计算的是字符 ‘h’, ‘e’, ‘l’, ‘l’, ‘o’ 的数量。
  • 2.sizeof(): 这是一个运算符,用于计算变量、类型或数据结构的大小(以字节为单位)。sizeof() 并不计算字符串的长度,而是返回数据类型或对象的大小。例如,sizeof(int) 在大多数系统中将返回 4,因为 int 类型通常占用 4 字节。如果你有一个指向字符串的指针,sizeof() 将返回指针本身的大小,而不是字符串的长度。

总结:strlen()是一个函数,sizeof()是一个关键词。strlen()在计算字符串长度的时候不包括“\0”,而sizeof()包括。

12.memcpy()和strcpy()的区别是什么?

memcpy()和strcpy()都是C语言中的标准库函数,用于复制内存内容,但它们之间存在一些重要的区别。

  • 1.参数不同:

strcpy()的原型是char *strcpy(char *dest, const char *src);它用于将src指向的C字符串(包括终止符\0)复制到dest指向的位置。
memcpy()的原型是void *memcpy(void *dest, const void *src, size_t n);,它用于将src指向的内存块的n个字节复制到dest指向的位置。memcpy()可以处理任何类型的数据,而不仅仅是字符串。

  • 2.复制内容不同:

strcpy()只会复制字符串,直到遇到\0为止。如果源字符串的长度超过目标字符串的长度,它可能会导致缓冲区溢出。
memcpy()会复制指定数量的字节,它可以用来复制任何类型的数据,包括字符串。如果源和目标区域重叠,memcpy()的行为是未定义的,可能会导致数据损坏。

  • 3.返回值不同:

strcpy()返回目标字符串的指针。
memcpy()返回目标内存区域的指针。

总的来说,strcpy()主要用于字符串的复制,而memcpy()用于更通用的内存块复制。在处理字符串时,如果源和目标区域重叠,应该使用memmove()函数,而不是memcpy(),以避免未定义的行为。

13.全局变量与局部变量在内存中的区别?

全局变量保存在内存的全局存储区中,占用静态的存储单元;

局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。

14.C语言是如何进行存储管理的

C语言中的存储管理主要涉及到内存分配和释放。在C语言中,内存分为四个区域:堆区(heap)、栈区(stack)、全局/静态存储区(global/static storage)、常量存储区(constant storage)。

  • 1.堆区:由malloc、calloc和realloc等函数分配和释放,这部分内存的分配和释放由程序员控制,不过如果没有正确释放,可能会导致内存泄漏。
  • 2.栈区:由编译器自动分配和释放,存放函数的参数值、局部变量等。其操作方式类似于数据结构中的栈。
  • 3.全局/静态存储区:存放全局变量、静态变量和常量,程序结束后由系统释放。
  • 4.常量存储区:存放常量,不允许修改。
  • 5.程序代码区:存放函数体的二进制代码

15.static的作用

关键字static有三种作用:

  • 1.在函数内部:将某一变量声明为静态变量,函数调用结束该变量内存也不会被释放,直到程序结束。
  • 2.在函数外部(某个.c文件内):被该关键字声明过的变量可以被当前文件内的函数访问及使用,但是不能被其它文件内的函数访问和使用。
  • 3.修饰函数:一个被声明为静态的函数只能被这一文件内的其它函数调用,不能被其它文件的函数调用。

16.register关键字的作用

在C语言中,register是一个关键字,用于指定局部变量的存储类别,具体作用如下:
• register修饰的变量是局部变量,默认的存储类别是auto,可以省略不写。
• register变量通常被存储在CPU的寄存器中,以提高变量的访问速度。
• register变量的地址不能被取得,因此不能对其进行取地址操作。
• register变量的数量和大小都是有限制的,取决于CPU的寄存器数量和大小。

需要注意的是register不能修饰全局变量,因为全局变量是存储在内存中的,如果你用register修饰全局变量编译器可能会忽略而不会报错。

17.inline关键词的作用

inline是C\C++语言中的一个关键字,用于修饰函数,在编译器编译时将函数调用处直接展开为函数体,从而避免了函数调用的开销,提高程序的运行速度。具体作用如下:
• inline修饰的函数在编译时将被直接展开为函数调用处的代码,从而避免了函数调用的开销,提高程序的运行速度。
• inline函数一般都定义在头文件中,可以被多个源文件调用,不会引起重复定义的错误。
• inline函数不能使用递归调用,也不能包含复杂的循环结构或switch语句等。

18.C语言头文件是否可以定义一个变量?

可以但是不推荐,推荐的方法是在源文件中定义变量,如果需要在外部文件中调用这个变量,则需要使用extern声明这个变量是一个外部变量。即源文件中定义,头文件中声明

19.define和typedef的区别

#define用来定义宏,是对文本进行简单的替换;typedef用来定义新的类型名,可以对类型进行封装和抽象。
#define定义的宏没有类型检查和作用域限制,可以定义在任何位置;typedef定义的新类型是有类型的,需要在定义后才能使用。

20.define的用处

  • 1.定义常量 eg:#define PI 3.14159
  • 2.定义函数宏:eg:#define SQUARE(x) ((x)*(x))
  • 3.条件编译: eg:#define DEBUG

21.define的缺点

*1.无法进行类型检查

宏定义是在编译前进行字符的替换,因为还没有编译,不能编译前就检查好类型是否匹配,而只能在编译时才知道,所以不具备类型检查功能。

  • 2.需要特别注意给各个变量加括号

由于宏定义的时候,其各个分量未加括号,而在使用宏定义的时候,传递的参数是变量的表达式,然后经过系统展开后,由于优先级的原因,导致其结果不是你所希望的。特殊情况时候,加了括号也无法避免错误(在宏定义中出现++或–之类的操作符的时候)

22.include <file.h> 和 #include "file.h"的区别

前者是从Standard Library的路径寻找和引用file.h,后者是从当前工作路径搜寻并引用file.h

23.堆栈溢出的情况有哪些?

资源申请太多
没有释放
递归层次太多

24.if判断语句笔试题

题目:if判断语句中是否可以完成赋值,赋值后if是true还是false??eg:if(a=5) -----> a=5?
运行以下程序,输出是什么?

#include <stdio.h>

int main()
{
	int i=5;
	if(i=0)
		printf("a=%d\r\n",i-10);
	else
		printf("a=%d\r\n",i++);
	return 0; 
}

这是一个大坑呀,我之前没注意这个问题,笔试的时候直接错了。这程序的输出是a=0;
首先我对标题进行回答:if语句中的判断语句可以使用赋值语句,且能够生效,也就是a=0,最后判断的结果看的是i是0还是非零
如果i是非零,那么就执行第一个分支语句(printf(“a=%d\r\n”,i-10))
如果i是0,那么就执行第二个分支语句(printf(“a=%d\r\n”,i++))

25.交换两个变量的值,不使用第三个变量

有两种方式,一种是通过±运算符,还有一种是通过异或运算符

a = a + b;
b = a - b;
a = a - b;
or
a = a^b;   // 只能对int,char..
b = a^b;
a = a^b;
or
a ^= b ^= a;

26.数组指针和指针数组的区别

1.本质:数组指针是一个指针,它指向一个数组的首地址,而指针数组是一个数组,它的每个元素都是一个指针。
2.存储内容:数组指针存储的是一个地址,这个地址指向一个数组,而指针数组则存储了一系列的指针变量。
3.操作方式:当对数组指针进行加法操作时,它要跨过的是数组元素的数量,即数组长度;而当对指针数组进行加法操作时,它只会增加指针变量的数量。

27.数组指针中a+1和&a+1的区别

#include <stdio.h>
int main(void)
{
    int a[5];
    printf("%p\n", a);
    printf("%p\n", a+1);
    printf("%p\n", &a);
    printf("%p\n", &a+1);
}

假设a的地址是0x000040,那么这个程序的运行结果是什么?请分析

首先,这个程序试图打印出数组a和相关表达式的地址。为了理解输出结果,我们需要理解这些表达式的含义:

  • 1.a: 这是一个指向数组a的首元素的指针。它的地址就是数组a的第一个元素在内存中的地址。假设int类型占4字节,那么a的地址就是0x000040。
  • 2.a+1: 这是一个指向数组a的第二个元素的指针。它的地址是数组a的第二个元素在内存中的地址,即a的地址加上sizeof(int)(在这里是4字节)。所以,a+1的地址是0x000044。
  • 3.&a: 这是一个指向整个数组a的指针。在C中,数组名(在这里是a)可以被解释为指向其首元素的指针。所以,&a和a的值是一样的,都是指向数组a的首元素的指针,地址都是0x000040。
  • 4.&a+1: 这是一个指向数组a之后的下一个内存位置的指针。它的地址是数组a的末尾地址加上1,即&a+1的地址是0x000054(假设一个int的大小是4字节,那么5个int的大小就是20字节,所以&a+1的地址是0x000040 + 20 = 0x000054)。

所以,这个程序的运行结果是:

0x000040  
0x000044  
0x000040  
0x000054

28.调用free(ptr)对指针进行释放就可以了吗?

调用 free() 函数并不会自动将指针设为 NULL。当你调用 free() 释放内存后,指向该内存的指针就成了悬空指针(dangling pointer)。指针仍然指向一个内存地址,但这个地址已经被操作系统回收,随时可能被分配给其他部分的程序使用。

如果你试图通过这个悬空指针访问内存,程序可能会崩溃,或者更糟糕的是,可能会读到一些无意义的数据。

为了防止这种情况,释放内存后,最好立即将指针设为 NULL:

int *ptr = (int *)malloc(sizeof(int));  
// ... 使用 ptr ...  
free(ptr);  
ptr = NULL; // 防止悬空指针

这样,如果你试图再次访问 ptr,程序会因为访问 NULL 指针而崩溃,这比潜在的悬空指针问题更容易被发现和修复。

需要注意的是,只有指向动态分配(如通过 malloc()、calloc() 或 realloc() 分配)的内存的指针才需要手动释放。指向静态或自动存储期的变量的指针在变量超出其作用域时会自动被清理。对这些指针调用 free() 是错误的。

29.C语言预编译三大功能

宏定义、文件包含、条件编译。

  • 1.宏定义是C语言提供的三种预处理功能的其中一种。宏定义和操作符的区别是:宏定义是替换,不做计算,也不做表达式求解。
  • 2.在C语言中文件包含是指一个源文件可以将另一个源文件的全部内容包含进来。该命令的作用是在预编译时,将指定源文件的内容复制到当前文件中。
  • 3.条件编译指令将决定哪些代码被编译,而哪些是不被编译的。可以根据 表达式 的值或者某个特定的宏是否被定义来确定编译条件。#if、#else、#elif和#endif指令。

30.不同数据类型值域范围

看这段代码,并说一下运行结果?

#define Max_CB 500
void LmiQueryCSmd(StructMSgCB * pmsg)
{
	unsigned char ucNum;
	for(ucNum=0;ucNum<Max_CB;ucNum++)
	{
	}                                          
}

结果是死循环
因为unsigned char 无符号字符型表示范围0~255
char 有符号字符型 表示范围-128~127

31.动态库和静态库有什么区别

动态库(.dll)Dynamic Library
静态库(.lib)Static Library
二者都是为了代码重用

在C语言中,库(Library)是一种包含了一系列函数或变量的集合,它们被封装为一个文件,可以被其他程序复用。库分为两种类型:静态库(Static Library)和动态库(Dynamic Library)。

静态库:
静态库是在编译时期被链接到程序中的。静态库的代码会被直接嵌入到程序中,因此程序运行时不再需要静态库。静态库的主要文件扩展名是“.a”。
优点
  静态库在编译时就已经链接到程序中,因此程序运行时速度快。静态库的代码会被直接嵌入到程序中,所以程序在没有库的环境下也可以运行。
缺点
  静态库会增加程序的体积,可能导致可执行文件变大。如果有多个程序使用了同一个静态库,那么每个程序都会包含该库的代码,造成代码冗余。

动态库:
  动态库是在程序运行时被加载的。动态库的代码不会被嵌入到程序中,而是在程序运行时动态加载到内存中。动态库的主要文件扩展名是“.so”(Linux)或“.dll”(Windows)。
优点
动态库可以减少程序的体积,因为动态库的代码不会被直接嵌入到程序中。如果有多个程序使用了同一个动态库,那么这些程序共享同一份库的代码,节省了内存空间。动态库可以方便地进行版本更新,只需要替换动态库文件即可。
缺点
动态库在程序运行时才被加载,因此相比于静态库,程序启动速度可能较慢。动态库需要被正确安装和配置,否则程序可能无法找到所需的库文件。
联系
无论是静态库还是动态库,它们都是封装了一组函数或变量的文件,可以被其他程序复用。在C语言中,我们通常使用头文件(Header File)来声明库中的函数和变量,然后在程序中引用这些头文件。这样,我们就可以在程序中调用库中的函数或访问库中的变量了。

32.*++p 和 *p++有什么区别?

在这里插入图片描述

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sillyfoxzero

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值