1.前言本系列将题型分门别类,重要的地方用星号标注,帮助大家打好基础。这篇文章比较深入,需要时间去理解,保证是必答题。文章较长,大家可以先收藏起来,找个大段时间看完。一次看不懂,可以多看几遍。下面分享16个重要知识点。二、C/C++题目1、new和malloc是embedded的,很关心内存。因为可用内存有限,所以嵌入式笔试面试题,内存题是经常被问到的。1)malloc和free是c++/c语言的库函数,需要头文件支持stdlib.h;new和delete是C++的关键字,不需要头文件,需要编译器支持;2)使用new运算符申请内存分配时,不需要指定内存块的大小,编译器会根据类型信息计算。另一方面,Malloc需要明确支持所需内存的大小。3)当new操作符分配内存成功后,返回一个对象类型的指针。类型严格匹配对象,不需要进行类型转换。所以new是一个符合类型安全的操作符。malloc内存分配成功返回void*,需要通过强制类型转换将void*指针转换为我们需要的类型。4)当分配新内存失败时,会抛出bad_alloc异常。当malloc分配内存失败时返回NULL。2、1G内存的电脑可以malloc(1.2G)吗?为什么?(2021浙江大华第二题)答:申请1.2G内存是可以的。分析:在回答这个问题之前,你需要知道malloc的作用和原理。应用程序可以通过malloc函数从程序的虚拟空间中申请一块虚拟地址空间,与物理内存没有直接关系。你得到的是虚拟地址空间中的地址。之后程序运行提供的物理内存由操作系统完成。3、extern"C"的作用我们可以在C++中使用C编译好的功能模块,这时候就需要extern"C"了。即在c++文件中加入extern"C"。extern工作在链接阶段(四个主要阶段:预处理-编译-汇编-链接)。4、strcat、strncat、strcmp、strcpy哪些函数会导致内存溢出?如何提高?(2021浙江大化双面题)strcpy函数会导致内存溢出。strcpy拷贝函数并不安全,它不做任何检查,不判断拷贝的大小,不判断目的地址的内存是否足够。char*strcpy(char*strDest,constchar*strSrc)strncpy复制函数,虽然计算了复制的大小,但是不安全,不检查目标的边界。strncpy(dest,src,sizeof(dest));strncpy_s是一个安全的strcmp(str1,str2),它是一个比较函数,如果str1=str2,则返回零;ifstr1strncat()主要作用是在字符串字符末尾追加n。char*strncat(char*dest,constchar*src,size_tn);strcat()函数主要用于连接两个char类型。例如:chard[20]="Golden";chars[20]="View";strcat(d,s);//printdprintf("%s",d);输出的d是GoldenView(中间没有空格)extension:memcpycopyfunction,它和strcpy的区别在于memcpy可以复制任何类型的数据,而strcpy只能复制string类型。memcpy函数用于将资源内存(src指向的内存区)复制到目标内存(dest指向的内存区);有一个大小变量来控制复制的字节数;函数原型:void*memcpy(void*dest,void*src,unsignedintcount);五、static的用法(定义及用途)(必填)1)使用static修饰局部变量:使之成为静态存储方式(静态数据区),那么这个局部变量在函数执行完成后不会被释放之后,却会继续留在记忆中。2)用static修饰全局变量:使其只在本文件内有效,其他文件不能连接或引用这个变量。3)用static修饰函数:会影响函数的连接方式,使函数只在本文件内部有效,对其他文件不可见(这个在大项目中很重要,避免了很多麻烦,很常见的)。此类函数也称为静态函数。使用静态函数的好处是不用担心会干扰其他文件中的同名函数,同时也是对函数本身的一种保护机制。6、const的用法(定义及用途)(必填)const主要用于修饰变量、函数参数和类成员函数:1)用const修饰常量:定义时初始化,以后不可更改。2)用const修饰形参:func(constinta){};this参数在函数中不能改变3)使用const修饰类成员函数:该函数只能对成员变量进行只读操作,即const类成员函数不能修改成员变量的值。const修饰的东西被强制保护,可以防止意外改变,提高程序的健壮性。参考一个大佬的回答:一听到面试官说:“const就是常量”,我就知道我是在和一个外行打交道。DanSaks在他去年的文章中已经完整的总结了const的所有用法,所以各位ESP(译者:EmbeddedSystemsProgramming)的读者应该都非常熟悉const能做什么,不能做什么。如果你从来没有读过那篇文章,只要能说出const的意思就是“只读”。虽然这个答案不是完整的答案,但我接受它作为正确答案。如果考生能正确回答这个问题,我会再问他一个问题:下面的陈述是什么意思?constinta;intconsta;constint*a;int*consta;intconst*aconst;前两个相同,a是常数整数。第三个表示a是一个指向常量整数的指针(也就是说,整数是不可修改的,但指针是)。第四个意思a是指向整数的常量指针(即指针指向的整数可以修改,但指针不能修改)。最后一个表示a是一个常量指针,指向一个常量整数(即指针指向的整数是不可变的,指针也是不可变的)。七、volatile的作用和用法定义为volatile的变量意味着这个变量可能会被意外改变,这样编译器就不会假定这个变量的值了。准确的说,优化器在每次使用这个变量的时候,都必须仔细的重新读取这个变量在内存中的值,而不是使用一个保存在寄存器中的备份(虽然读写寄存器比读写内存要快)。不能回答这个问题的人将不会被录用。这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式专家经常与硬件、中断、RTOS等打交道,所有这些都需要易失性变量。不了解volatile的内容会带来灾难。volatile用于以下几种情况:1.并行设备的硬件寄存器(如状态寄存器)2.中断服务子程序中访问的非自动变量3.多线程应用中多个任务共享的变量8.const常量的区别和#define(编译阶段、安全、内存使用等)使用#definemax100;定义的常量没有类型(没有类型安全检查,可能会出现意想不到的错误),所以给的是立即数,编译器只是将定义的常量值与定义的常量名称相关联,define定义的宏变量被替换为预处理阶段,程序中用到的常量和常量的每一个地方都必须进行复制和替换;使用constintmax=255;定义的常量有一个类型(编译时会进行类型检查)名称,存放在内存的静态区域,其值在编译时确定。程序运行过程中const变量只有一份,而#define定义的宏变量有多份,所以宏定义在程序运行过程中消耗的内存比const变量9.变量的作用域(全局变量和局部变量)全局变量:定义在所有函数体之外,程序的一部分(甚至是其他文件中的代码)都可以使用。全局变量不受作用域影响(也就是说,全局变量一直存在到程序结束)。局部变量:出现在一个范围内,仅限于一个函数。局部变量通常称为自动变量,因为它们在进入范围时自动创建,在离开范围时消失。关键字auto可以明确说明这个问题,但是局部变量默认是auto的,所以没必要声明成auto。局部变量可以与全局变量同名。在局部变量范围内,全局变量失效,使用局部变量的值。10、sizeof和strlen(string,array)1.如果是数组#includeintmain(){inta[5]={1,2,3,4,5};printf("sizeof数组名=%d\n",sizeof(a));printf("sizeof*数组名=%d\n",sizeof(*a));}运行结果sizeof数组名=20sizeof*arrayname=42.如果是指针,sizeof只会检测指针的类型,指针占用4字节空间(32位机)。什么是大小?它是一个运算符和一个关键字,而不是一个函数。这与strlen()不同,后者是一个函数。那么sizeof的作用是什么呢?返回对象或类型占用的内存字节数。我们会在sizeof()中对数据或指针进行操作吗?基本上不会。比如sizeof(1+2.0)直接检测类型为double,即sizeof(double)=8。如果是指针,sizeof只会检测指针的类型,指针占用4字节空间(32位机器)。char*p="sadasdasd";sizeof(p):4sizeof(*p):1//指向char类型,除非使用strlen(),只对字符串有效,直到'\0',计数结果不包括\0。如果一定要用sizeof来获取指向内容的大小,就得用数组名,比如chara[10];sizeof(a):10//检测到a是数组类型。关于strlen(),是一个函数,查起来比较简单:strlen"\n\t\tag\AATang"答:1111,经典的sizeof(struct)和sizeof(union)内存对齐内存对齐函数:1。PlatformReason(移植原因):并不是所有的硬件平台都能访问任何地址的任何数据;有些硬件平台只能在特定地址获取特定类型的数据,否则会抛出硬件异常。2、性能原因:数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因是为了访问未对齐的内存,处理器需要进行两次内存访问;而对齐的内存访问只需要一次访问。结构体struct内存对齐的三个规则:1.对于结构体的每个成员,第一个成员的偏移量为0,后面排列的成员当前偏移量必须是当前成员类型的整数倍;2.结构体中所有数据成员内存对齐后,结构体本身需要进行一次内存对齐,保证整个结构体占用的内存大小为结构体中最大数据成员的最小整数倍;3.如果有#pragmapack(nintheprogram)预编译指令,则所有成员对齐均以n字节为准(即偏移量为n的整数倍),当前类型和结构中最大的类型不再考虑。#pragmapack(1)structfun{inti;doubled;charc;};sizeof(fun)=13structCAT_s{intld;charColor;unsignedshortAge;char*Name;void(*Jump)(void);}加菲猫;1、使用32位编译,int占4,char占1,unsignedshort占2,char*占4,函数指针占4。由于32位编译是4字节对齐,结构占16字节。(说明:按几个字节对齐由结构体的最长类型决定,这里int是最长字节,所以按4字节对齐);2.使用64位编译,int占4,char占1,unsignedshort占2,char*占8,函数指针占8,既然是64位编译,就是8字节对齐(注:对齐方式byseveralbytes是根据结构体最长的类型决定的,这里函数指针是最长的字节,所以按8字节对齐)所以结构体占用24字节。//64位structC{doublet;//811111111charb;//11inta;//40001111shortc;//211000000};sizeof(C)=24;//注意:142不能放在一起char为1,然后在int之前,地址偏移量必须是4的倍数,所以在char后面加了三个字节,即char占4个字节,然后是int占4个字节,最后是short,只占两个字节,但是总的偏移量必须是一个double的倍数,也就是8的倍数,所以short遵循了union内存对齐的两条规则:1.找到占用字节最多的成员;2.union中的字节数必须是占字节最多的成员字节数的倍数,需要能容纳其他成员//x64typedefunion{longi;intk[5];charc;}D计算union的大小,先找到占字节数最多的成员,本例为long,占8个字节,intk[5]全为int类型,仍占4个字节,然后union的字节数必须是占用最多字节段的成员的字,并且需要能够容纳其他成员。为了容纳k(20字节),它必须是8的倍数并且大于20字节,所以是24字节。扩展:位域(大疆笔试题)C语言允许以位为单位指定结构体中其成员的内存长度。位中的这种成员称为“位域”或“位域”(bitfield)。使用位域可以用更少的位来存储数据。一个位域必须存放在同一个存储单元中,不能跨越两个单元。如果第一个单元空间不能容纳下一个位域,则不使用这个空间,从下一个单元开始存储位域。1.位段声明类似于结构体2.位段的成员必须是int,unsignedint,signedint3.位段的成员名后面有一个冒号和一个数字typedefstruct_data{charm:3;charn:5;shorts;union{inta;charb;};inth;}_attribute_((packed))data_t;答案12m和n一起正好占了一个字节的内存,因为后面有一个short类型的变量,所以shorts之前要加一个字节。所以m和n其实占两个字节,然后short两个字节,加起来是4个字节,然后union占四个字节,一共8个字节,最后inth占四个字节,也就是12个字节的attribute((packed))未对齐GNUC的一个主要特性是__attribute__机制。__attribute__可以设置函数属性(FunctionAttribute)、变量属性(VariableAttribute)和类型属性(TypeAttribute)。__attribute__的写法特点是:__attribute__前后各有两个下划线,后面会有一对括号,对应的__attribute__参数在括号内。用于跨平台通信。不同的平台有不同的内存对齐方式。如果您使用结构进行平台之间的通信,就会出现问题。比如在发送消息的平台上,结构是24字节,而在接收消息的平台上,结构是32字节(只是举个例子),那么每个变量对应的值都是错误的。不同框架的处理器对齐方式会有所不同。如果此时不指定对齐方式,会产生错误结果。导致堆栈空间或堆栈内存的大量消耗。为了解决这个问题,特地引入了inline修饰符,表示为一个内联函数。在大多数机器上,调用一个函数需要做很多工作:寄存器必须在调用前保存,返回时恢复,实际参数必须被复制,程序还必须切换到一个新的位置来执行支持的内联函数在C++中,其目的是为了提高函数的执行效率,可以通过在函数定义前加上关键字inline来将函数指定为内联函数(注意是定义而不是声明).内联函数通常放在程序中的每个调用点展开“内联”。内联是以代码扩展(复制)为代价的,只是节省了函数调用的开销,从而提高了函数的执行效率。13.内存的四个区域,哪些变量存放在哪个区域,在堆上还是在栈上。文本常量区,叫做.rodata,不能更改,更改会导致整个程序运行过程中出现段错误;范围是所有文件;存储位置是数据段。a1:全局静态未初始化变量;生命周期是整个程序的运行周期;范围是当前文件;存储位置为BSS段。a2:全局静态变量a3:全局初始化变量;其他同a0。a4:局部变量;生命周期是在fun函数运行期间;作用域在fun函数内;存储位置是堆栈。a5:局部volatile变量;14.在使用32位编译的情况下,给出判断所用机器大小的方法。union方法的判断方法:利用union结构从低地址开始存储,同时只有一个成员占用内存的特点。大端存储符合阅读习惯。union占用内存最大,这一点和structure不同。a和c共享同一个内存区,所以改变c必然影响a的数据#includeintmain(){unionw{inta;charb;}c;c.a=1;if(c.b==1)printf("小端存储\n");elseprintf("大端存储\n");return0;}指针方法将int类型转成char单字节,p指向a(低字段)的起始字节#includeintmain(){inta=1;char*p=(char*)&a;if(*p==1){printf("小端存储\n");}else{printf("大端存储\n");}return0;}15.使用变量a给出如下定义:a)一个整数;b)指向整数的指针;c)一个指向指针的指针,它指向的指针是一个整数;d)一个包含10个整数的数组;e)一个有10个指针的数组,指向一个整数;f)指向10个整数的指针,指向数字数组;g)指向接受整数参数并返回整数的函数的指针;h)一个包含10个指向函数的指针的数组,该函数接受一个整数参数并返回一个整数答案:a)intab)int*a;c)int**a;d)inta[10];e)int*a[10];f)inta[10],*p=a;g)int(*a)(int)h)int(*a[10])(int)16和/或异或。运算符优先级sum=a&b