嵌入式开发工程师笔试面试指南-C语言
C语言基础
1 int main(int argc, char * argv[ ])函数中,参数argc和argv分别代表什么意思?⭐⭐⭐⭐
argc和argv参数在用命令行编译程序时有用。main( int argc, char* argv[] ) 中
第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1。
第二个参数,**char*型的argv[],**为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
argv[0]指向程序运行的全路径名
argv[1]指向在DOS命令行中执行程序名后的第一个字符串
argv[2]指向执行程序名后的第二个字符串
argv[3]指向执行程序名后的第三个字符串
2 结构体和共用体的区别⭐⭐⭐⭐
共用体:
1.使用union 关键字
2.共用体内存长度是内部最长的数据类型的长度。
3.共用体的地址和内部各成员变量的地址都是同一个地址
结构体:
1.结构体内部的成员,大小等于最后一个成员的偏移量+最后一个成员大小+末尾的填充字节数。
2.结构体的偏移量:某一个成员的实际地址和结构体首地址之间的距离。
3.结构体字节对齐:每个成员相对于结构体首地址的偏移量都得是当前成员所占内存大小的整数倍,如果不是会在成员前面加填充字节。结构体的大小是内部最宽的成员的整数倍。
结构体struct和共用体union的区别在于:结构体struct的各个成员会占用不同的内存,互相之间没有影响;而共用体union的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体struct占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体union占用的内存等于最长的成员占用的内存。共用体union使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
3 简述C++有几种传值方式,之间的区别是什么?⭐⭐⭐⭐
C++中的传值方式一共有三种:分别是值传递、引用传递和指针传递
1.值传递:形参即使在函数体内发生改变,也不会影响实参的值
2.引用传递:形参在函数体内的值发生改变
3.指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化的时候,会影响实参的值
1.值传递用于对象时,整个对象会拷贝一个副本,效率很低;
2.引用传递用于对象时,不会发生拷贝行为,只绑定对象更安全、更高效;
3.指针传递与引用传递一样,但没有引用传递安全。
4 全局变量和局部变量的区别⭐⭐⭐⭐
1. 作用域不同:全局变量的作用域为整个程序,而局部变量的作用域为当前函数或循环等
2. 内存存储方式不同:全局变量存储在全局数据区(data)中,局部变量存储在栈区(stack)
3. 生命期不同:全局变量的生命期和主程序一样,随程序的销毁而销毁,局部变量在函数内部或循环内部,随函数的退出或循环退出就不存在了
4. 使用方式不同:全局变量在声明后程序的各个部分都可以用到,但是局部变量只能在局部使用。函数内部会优先使用局部变量再使用全局变量。
5 gcc编译器编译的完整流程,分别有什么作用?⭐⭐⭐⭐⭐
预处理、编译、汇编、链接。
预处理:头文件的展开/宏的替换/去掉注释/条件编译。
编译:检查语法,生成汇编。
汇编:汇编代码转换成机器码。
链接:链接到一起生成可执行文件。
6 static、const、volatile关键字有什么作用?⭐⭐⭐⭐⭐
static:限定作用,延长生命周期。
- 声明静态变量,使其生命周期延长或作用域限定在当前文件内。
- 声明静态函数,使其作用域限定在当前文件内。
- 声明静态成员变量,使其属于类本身而不是对象,多个对象共享同一份内存。
- 使用静态限定符,控制变量的初始化和生命周期。
const:防止变量被修改。
- 值不可修改:一旦常量被赋值后,其值将保持不变,不能再对其进行修改。
- 作用域限制:常量的作用域通常被限制在声明时所在的作用域内部
- 编译时确定:常量的值在编译时就已确定,并在运行时保持不变
volatile:防止编译器过度优化,告诉编译器,每次取值都去内存中取。volatile主要用于中断函数子程序访问的非自动变量、多线程共享的全局变量、并行设备的寄存器。
三个常见场景
- 多线程中的共享变量
- 中断程序中访问到的非自动变量
- 并行设备的硬件寄存器
一个变量既可以是const还可以是volatile类型吗
一个变量可以同时具有const和volatile。const表示变量的值不能被改变,而volatile属性表示变量的值可能会被外部程序改变。
7 const 和 #define的区别⭐⭐⭐⭐⭐
- const是一种编译器关键字,而#define是预处理器指令。const在编译阶段进行处理,而#define在预处理阶段进行处理。
- const定义的常量具有类型,而#define没有。const在声明时需要指定常量的类型,编译器会进行类型检查。而#define只是简单的文本替换,没有类型检查。
- const定义的常量有作用域限制,可以根据声明位置的不同而有不同的作用域。而#define定义的常量没有作用域限制,整个程序中都有效。
- const生成符号表中的一个符号,有明确的名字和类型,可以进行调试和符号查找。而#define没有生成符号表,不会产生对应的符号。
8 extern关键字⭐⭐⭐⭐
- 声明一个在其他文件中定义的外部变量或函数。
- 告诉编译器在链接过程中需要找到对应的定义。
- 允许在当前文件中使用这些外部变量或函数而不需要重新定义。
9 #include<> 和 #include""的区别⭐⭐⭐
使用 #include<>:
- 用于包含系统提供的标准库头文件。
- 在编译器的搜索路径中寻找头文件。
- 编译器会先在系统的标准头文件目录中查找,如果找不到则报错。
使用 #include"":
- 用于包含用户自定义的头文件或项目中使用的其他非系统头文件。
- 在当前源文件的相对路径或指定的绝对路径中寻找头文件。
- 编译器会首先在当前源文件所在目录中查找,如果找不到再根据指定的路径查找。
10 头文件#ifndef/#define/#endif的作用⭐⭐⭐
- #ifndef:用于判断当前头文件是否已经被包含。
- 如果该宏之前没有被定义过,则继续编译下面的代码。
- 如果该宏之前已被定义过,则跳过下面的代码,直接到 #endif。
- #define:用于定义一个宏。
MY_HEADER_H
表示头文件已被包含。- #endif:用于结束 #ifndef / #define / #endif 块。
- 标记了头文件的结束位置。
11 sizeof与strlen的区别⭐⭐⭐
sizeof:
- 用于获取数据类型或变量的字节大小。
- 可以接受多种参数,包括数据类型、变量名、数组名等。
- 返回的是整个数据类型或变量占用的内存空间大小。
strlen:
- 用于获取以’\0’结尾的字符串的实际长度。
- 在运行时计算,需要遍历字符串的内容来确定长度。
- 返回的是字符串中的字符个数,不包括字符串结束符’\0’。
12 C语言的基本类型有哪些(32位系统),占用字节空间⭐⭐⭐⭐⭐
char | 1 |
short int | 2 |
int/long int | 4 |
char * /int * /任何的指针 | 4 |
float | 4 |
double | 8 |
13 常见的变量定义⭐⭐⭐⭐⭐
int a;
:定义了一个变量a
,它的类型是int
。int *a;
:定义了一个指针a
,它指向int
类型的变量。int **a;
:定义了一个指针a
,它指向一个指向int
类型的指针。int a[10];
:定义了一个数组a
,该数组有 10 个元素,每个元素是int
类型。int *a[10];
:定义了一个数组a
,该数组有 10 个元素,每个元素是int
类型的指针。int (*a)[10];
:定义了一个指针a
,该指针指向一个数组,该数组有 10 个元素,每个元素是int
类型。int (*a)(int);
:定义了一个指针a
,该指针指向一个参数是int
,返回值是int
的函数。int (*a[10])(int);
:定义了一个数组a
,该数组的元素是一个指向参数是int
,返回值是int
的函数指针。
指针
1 说说数组和指针的区别⭐⭐⭐⭐⭐
1. 概念:
(1)数组:数组是用于储存多个相同类型数据的集合。 数组名是首元素的地址。
(2)指针:指针相当于一个变量,但是它和普通变量不一样,它存放的是其它变量在内存中的地址。指针名指向了内存的首地址。
2.区别:
(1)赋值:同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝
(2)存储方式:
2. 数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的,数组的存储空间,不是在静态区就是在栈上。
指针:指针本身就是一个变量,作为局部变量时存储在栈上。
(3)求sizeof:
数组所占存储空间的内存大小:sizeof(数组名)/sizeof(数据类型)
在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。
(4)初始化:
//数组 int a[5] = {0}; char b[]={"Hello"};//按字符串初始化,大小为6. char c[]={'H','e','l','l','o','\0'};//按字符初始化 int* arr = new int[n];//创建一维数组 //指针 //指向对象的指针 int *p=new int(0) ; delete p; //指向数组的指针 int *p=new int[n]; delete[] p; //指向类的指针: class *p=new class; delete p;
2 数组指针与指针数组的区别⭐⭐⭐⭐⭐
数组指针是一个指针变量,指向了一个一维数组, 如int (*p)[4],(*p)[4]就成了一个二维数组,p也称行指针;指针数组是一个数组,只不过数组的元素存储的是指针变量, 如int *p[4]。
3 指针函数与函数指针的区别⭐⭐⭐⭐⭐
(1)定义不同
指针函数本质是一个函数,其返回值为指针。
函数指针本质是一个指针,其指向一个函数。
(2)写法不同
指针函数:int *fun(int x,int y);
函数指针:int (*fun)(int x,int y);
(3)用法不同
指针函数返回一个指针。
函数指针使用过程中指向一个函数。通常用于函数回调的应用场景
我们调用别人提供的 API函数(Application Programming Interface,应用程序编程接口),称为Call;如果别人的库里面调用我们的函数,就叫Callback。
4 请你说说野指针⭐⭐⭐⭐⭐
什么是野指针
野指针是指向位置随机的、不正确的指针,系统无法对其进行操作;
野指针的危害
野指针指向的位置是随机的, 危害也是随机的,不一定会产生错误。若程序产生错误,一般为内存泄露导致程序中断。严重的情况,若野指针指向的位置存放一个病毒,对其解引用后就会导致电脑中毒。
野指针产生的原因
1、创建指针时没有对指针进行初始化,导致指针指向一个随机的位置;
2、释放指针指向的内存后没有置空,从而指向垃圾内存;
3、在超越变量作用域下使用指针,如:在栈内存被释放之后,指向栈内存的指针会指向垃圾内存;
野指针的避免方法:
1、在创建指针时必须进行初始化;
2、在释放指针指向的内存之后必须将指针置空;
5 说说使用指针需要注意什么?⭐⭐⭐⭐⭐
1.定义指针时,先初始化为NULL。
2.用malloc申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。在现行C++标准中,如C++11,使用new申请内存后不用判空,因为发生错误将抛出异常。
3.不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
4.避免数字或指针的下标越界,特别要当心发生“多1”或者“少1”操作
5.动态内存的申请与释放必须配对,防止内存泄漏
6.用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”
6 指针、数组和地址之间的关系是的什么?⭐⭐⭐⭐⭐
指针、数组和地址之间存在着紧密的关系,具体如下:
指针与地址:指针是一种变量,其值为另一个变量的地址。通过指针,能间接访问和操作存储在该地址中的数据。例如int p;定义了一个指向int类型的指针p,若p = &a;(a为int型变量),则p存储了a的地址,可通过p来访问a的值。
数组与地址:数组名在很多情况下可看作是一个指向数组首元素的常量指针,即数组在内存中是连续存储的,数组名代表了数组的首地址。如int arr[5];,arr就表示数组arr的首元素arr[0]的地址。
指针与数组:可利用指针来操作数组,通过指针的移动可以访问数组中的各个元素。如int p = arr;,此时p指向数组arr的首元素,p++可使指针指向下一个元素,等价于arr[1]的地址,可通过p获取对应元素的值。
7 void指针就是空指针吗?他有什么作用?⭐⭐⭐⭐⭐
void 指针不是空指针。void 指针是一种无类型指针,可以指向任何类型的数据,而空指针是不指向任何有效内存地址的指针,通常用NULL或nullptr表示。
void 指针的作用主要有:作为通用指针,可用于函数参数,使函数能接受不同类型数据的地址,增强函数通用性;用于动态内存分配函数malloc等,返回的就是 void 指针,可根据需要转换为其他类型指针;还能在实现数据结构时,作为节点数据域指针,以存储不同类型数据。
8 指针和引用的区别以及转换⭐⭐⭐⭐⭐
引用指针的定义
指针(Pointer):
- 定义:指针是一种变量,存储了一个地址,该地址指向内存中的另一个变量。
- 特点:可以修改指针的指向,使其指向其他变量或空地址。可以进行指针运算,如指针加法和减法。可以通过解引用(Dereference)操作符 * 来访问指针所指向的变量。
引用(Reference):
- 定义:引用是变量的别名,它引用了同一块内存空间。
- 特点:引用一旦绑定到一个变量,便无法更改其引用的目标。操作引用和操作原变量是等价的,对引用的修改会反映在原变量上。引用不能指向空地址。
引用和指针的区别:
1. 指针是一个实体,而引用仅是个别名
2. 指针和引用的自增(++)运算意义不一样,指针是对内存地址的自增,引用是对值的自增;
量或对象的地址)的大小;
3. 引用使用时无需解引用(*),指针需要解引用;
4. 引用只能在定义时被初始化一次,之后不可变;指针可变;
5. 引用不能为空,指针可以为空;
6.引用没有const,指针有const;(本人当初看到这句话表示疑问,这里解释一下:指针有“指针常量”即int * const a,但是引用没有int& const a,不过引用有“常引用”即const int &a = 1)
7. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小,在32位系统指针变量一般占用4字节内存。
转换代码示例:
指针转引用:
int num = 10; int* ptr = # // 声明指针并指向变量 int& ref = *ptr; // 将指针转换为引用 // 使用引用 ref = 20; cout << num; // 输出:20
引用转指针:
int num = 30; int& ref = num; // 声明引用 int* ptr = &ref; // 将引用转换为指针 // 使用指针 *ptr = 40; cout << num; // 输出:40
内存
1 请说说内存分布模型⭐⭐⭐⭐⭐
如上图,从低地址到高地址,一个程序由代码段、数据段、BSS段、堆栈段组成。
1.代码段:存放程序执行代码的一块内存区域。只读,不允许修改,代码段的头部还会包含一些只读的常量,如字符串常量字面值(注意:const变量虽然属于常量,但是本质还是变量,不存储于代码段)。
2.数据段data:存放程序中已初始化的全局变量和静态变量的一块内存区域。
3.BSS 段:存放程序中未初始化的全局变量和静态变量的一块内存区域。
4.可执行程序在运行时又会多出两个区域:堆区和栈区。
堆区:动态申请内存用。堆从低地址向高地址增长。
栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。
5.最后还有一个文件映射区(共享区),位于堆和栈之间。
2 堆和栈的区别⭐⭐⭐⭐⭐
1.堆栈空间分配不同。栈由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等,栈有着很高的效率;堆一般由程序员分配释放,堆的效率比栈要低的多。
2.堆栈缓存方式不同。栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放;堆则是存放在二级缓存中,速度要慢些。
3.空间大小: 栈的空间大小并不大,一般最多为2M,超过之后会报Overflow错误。堆的空间非常大,理论上可以接近3G。(针对32位程序来说,可以看到内存分布,1G用于内核空间,用户空间中栈、BSS、data又要占一部分,所以堆理论上可以接近3G,实际上在2G-3G之间)
4.能否产生碎片: 栈的操作与数据结构中的栈用法是类似的。‘后进先出’的原则,以至于不可能有一个空的内存块从栈被弹出。因为在它弹出之前,在它上面的后进栈的数据已经被弹出。它是严格按照栈的规则来执行。但是堆是通过new/malloc随机申请的空间,频繁的调用它们,则会产生大量的内存碎片。这是不可避免地。
3 请你说说内存泄露⭐⭐⭐⭐⭐
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。
(1)new和malloc申请资源使用后,没有用delete和free释放;
(2)子类继承父类时,父类析构函数不是虚函数。
(3)比如文件句柄、socket、自定义资源类没有使用对应的资源释放函数。
(4)shared_ptr共享指针成环,造成循环引用计数,资源得不到释放。
有以下几种避免方法:
第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
第三:使用智能指针。
第四:一些常见的工具插件可以帮助检测内存泄露,如ccmalloc、Dmalloc、Leaky、Valgrind等等。
4 在函数中申请堆内存需要注意什么?⭐⭐⭐⭐⭐
(1)不要错误地返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。即函数内申请的临时数组,不要指望能够拿到数组内的内容,因为函数执行完成后,数组消亡。
(2)不要返回了常量区的内存空间。因为常量字符串,存放在代码段的常量区,生命期内恒定不变,只读不可修改。不可修改拿到也没有什么意义。
(3)通过传入一级指针不能解决,因为函数内部的指针将指向新的内存地址。
解决办法:
(1)使用二级指针
(2)通过指针函数解决,返回用malloc新申请的堆内存空间的地址,这样才能拿到内存内容。
5 请你说说内存碎片⭐⭐⭐⭐⭐
内存碎片通常分为内部碎片和外部碎片:
(1)内部碎片是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免;
(2)外部碎片是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。再比如堆内存的频繁申请释放,也容易产生外部碎片。
解决方法:
(1)段页式管理
(2)内存池
6 请你说说malloc内存管理原理⭐⭐⭐⭐⭐
Malloc函数用于动态分配内存。为了减少内存碎片和系统调用的开销,malloc其采用内存池的方式,以此来减少内存碎片,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。Malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录了一个连续的、未分配的地址。
当进行内存分配时,Malloc会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定 是否进行块合并。
当开辟的空间小于 128K 时,调用 brk()函数;
当开辟的空间大于 128K 时,调用mmap()。
7 什么是内存池⭐⭐⭐⭐⭐
内存池(Memory Pool)是一种动态内存分配与管理技术。通常情况下习惯使用new/delete/malloc/free等API申请分配和释放内存,这样导致的后果是:当程序长时间运行时,由于所申请的内存块大小不定,频繁使用时会造成大量的内存碎片从而降低程序和操作系统的性能。内存池则是在真正使用内存之前,先申请分配一大块内存(内存池)留作备用,当我们申请内存时,从池中取出一块动态分配的内存,释放内存时,再将我们使用的内存释放到我们申请的内存池内,再次申请内存池也可以再取出来使用。并且,尽量与周边的内存块合并。若内存池不够时,则自动扩大内存池,从操作系统中申请更大的内存池。
为什么需要内存池?
解决内存碎片问题
假设系统依次分配了16,16,4,8,8字节,接着释放了16字节和8字节归还给了系统(红色表示还未归还给系统),那么现在堆区空闲了一个16字节的空间和一个8字节的空间。这是如果用户要想申请出一个24字节的空间,那么系统就无法分配连续的空间给用户。这就是内存碎片的问题。
8 说说new和malloc的区别,各自底层实现原理⭐⭐⭐⭐⭐
new的实现原理 简单类型时底层会调用malloc函数。
区别:
1 new是操作符,malloc是函数;
2 malloc()和free()申请释放内存是在堆上完成的,new和delete是在静态储存区完成的;
3 malloc()申请内存失败范围NULL 成功返回VOID类型的指针,而new成功返回对应类型的内存,失败会抛出异常;
4 malloc()可以指定内存大小,并且可以扩充内存而new办不到。
5 new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。
9 strcpy与memcpy的区别⭐⭐⭐⭐⭐
strcpy:
- 用于字符串拷贝。
- 源字符串中的内容会被复制到目标字符串中,直到遇到字符串结束符 ‘\0’。
- 目标字符串必须有足够的空间来存储被复制的内容,否则可能导致缓冲区溢出。
memcpy:
- 用于字节级别的内存拷贝。
- 可以拷贝任意类型的内存块,不仅限于字符串。
- 不会检查字符串结束符,通过指定要拷贝的字节数进行拷贝。
- 可以用于拷贝部分或完整的数组、结构体等。
安全性:
- strcpy函数不进行源字符串长度的检查,如果源字符串太长,可能会导致目标字符串缓冲区溢出。
- memcpy函数本身没有长度限制,应确保源和目标内存区域不会发生重叠,否则可能会导致数据损坏。
- 为了提高安全性,可以使用像strcpy_s、strncpy_s这样提供了长度限制的函数。
总结:
- strcpy适用于字符串拷贝,可以自动识别字符串结束符。
- memcpy适用于字节级别的内存拷贝,适用于任意类型的数据。
10 初始化为0的全局变量在bss还是data⭐⭐⭐⭐⭐
BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。
11 与内存息息相关的重要概念有哪些?(野指针、栈(stack)、堆(heap)、静态区)⭐⭐⭐⭐⭐
与内存息息相关的重要概念有野指针、栈(stack)、堆(heap)、静态区,具体如下:
野指针:是指向不确定或非法内存地址的指针。它可能是未初始化的指针,或指针所指向的内存已被释放后仍在使用的指针。使用野指针会导致程序出现不可预测的行为,如数据错误、程序崩溃等。
栈(stack):是一种自动分配和释放内存的区域,由编译器自动管理。主要用于存储函数的参数、局部变量等。其特点是后进先出,内存分配和释放速度快,但空间大小有限。
堆(heap):用于动态内存分配,由程序员手动申请和释放。可以在程序运行时根据需要动态地分配和释放内存空间,适合存储大小不确定的数据。但如果分配后不及时释放,可能会导致内存泄漏。
静态区:用于存储全局变量和静态变量,在程序编译时就分配好内存,程序结束时才释放。其内存空间在程序的整个生命周期内都存在,且只能初始化一次。
12 简述内存泄漏,如何检测和避免?⭐⭐⭐⭐⭐
内存泄漏是指在程序运行过程中,分配的内存没有被及时释放,导致这部分内存无法再被程序使用。长时间运行或发生频繁的内存泄漏可能导致系统资源不足,性能下降或程序崩溃。
常见内存泄露情况:
- new和malloc申请资源使用后,没有用delete和free释放;
- 子类继承父类时,父类析构函数不是虚函数。
- 比如文件句柄、socket、自定义资源类没有使用对应的资源释放函数。
- shared_ptr共享指针成环,造成循环引用计数,资源得不到释放。
检测内存泄漏:
- 使用内存分析工具:使用工具如Valgrind(Linux),Dr. Memory(Windows),Instruments(macOS)等来检测内存泄漏。这些工具可以检查程序运行时的内存分配和释放情况,并报告潜在的泄漏位置。
避免内存泄漏:
- 使用智能指针:C++中的智能指针(如std::shared_ptr、std::unique_ptr)可以自动管理内存释放,避免手动释放内存的疏忽。
- 务必正确配对分配和释放:确保每次内存分配都有相应的释放,释放内存的操作必须与分配内存的操作对应(malloc和free 一定要malloc和free的次数一致否则就泄露)。
- 减少全局变量和长时间存活的对象:全局变量和长时间存活的对象可能导致无法释放的内存,尽量避免过多使用。
- 使用容器类的自动销毁机制:使用容器类如std::vector、std::map等,它们在销毁时会自动释放内部元素的内存。
- 注意循环引用:避免出现对象之间的循环引用,可以使用弱引用或断开引用关系的方式解决。
- 规范化资源管理:对于文件、网络连接等资源,确保在使用后及时释放,避免产生不必要的资源泄漏。
13 简述大端和小端⭐⭐⭐
这两种字节序的选择取决于计算机系统架构的约定。大部分个人电脑和服务器采用小端字节序,例如 x86 架构的计算机。而一些嵌入式系统和网络协议则常使用大端字节序。
- 大端字节序:将多字节数据的高位字节存储在低地址位置,低位字节存储在高地址位置。类比:高位字节在前,低位字节在后。举例:整数值 0x12345678 在大端字节序中存储为 12 34 56 78,即高位字节 12 存储在低地址位置,低位字节 78 存储在高地址位置。
- 小端字节序:将多字节数据的低位字节存储在低地址位置,高位字节存储在高地址位置。类比:低位字节在前,高位字节在后。举例:整数值 0x12345678 在小端字节序中存储为 78 56 34 12,即低位字节 78 存储在低地址位置,高位字节 12 存储在高地址位置。
#承诺提供免费技术答疑# 本专栏主要是介绍嵌入式开发岗位相关知识和学习攻略。“C/C++软件开发岗位”也可以参考。 该专栏覆盖了嵌入式求职过程中99%常见面试题,详细讲解了嵌入式软件开发岗位、学习攻略、项目经验分享、面试心得,从技术面,HR面,主管面,谈薪一站式服务。订阅即赠送简历模板、内推机会,需要的同学点击我头像私信即可!