嵌入式软件开发 笔试和面试笔记 (未完结)

嵌入式软件开发 笔试和面试笔记 (未完结),第1张

嵌入式软件开发 笔试和面试笔记 (未完结) C/C++ 1. 关键字 1.1 volatile 关键字

用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中,让编译器每次 *** 作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。

场景

多线程应用中被几个任务共享的变量中断服务程序中用到的变量并行设备的硬件寄存器 1.2 static

一般分为三种情况

函数体内部定义变量:这个变量只会被初始化一次,并且在其它地方多次调用这个函数的时候,这个变量的值保持不变在一个模块(一个.c文件) 定义的变量:可以被模块内部的所有函数访问,但是不能被外部模块访问,相当于属于本模块的全局变量在模块内声明的函数:只能在本模块的范围内调用,外部不能调用

static声明的所有对象,都只初始化一次,并且会保存在内存区域,生命周期和程序周期一致。

1.3 extern “C”

主要作用是为了能正确实现C++代码调用C代码,加上这个关键字之后,会告诉编译器这段代码按照C的编译规则编译 1.4 const

一般分为3个作用

定义变量为常量:定义之后,变量的值不能修改(即赋值),所以定义的时候一定要初始化

修饰形参:表示函数体内部不能对形参加以修改

修饰返回值:只对指针有意义,如果给用const修饰返回值的类型为指针,那么函数返回值(即指针)的内容是不能被修改的,而且这个返回值只能赋给被const修饰的指针。(如果是修饰普通返回值,那么这个返回这也是一个临时变量,可以认为没有意义 )例如︰

const char getchar()
char *str = getchar() 	//错误
const char *str = getchar   //正确

场景:变量、数组、对象、指针、引用、返回值、在另一链接文件中引用const常量

1.5 new delete mallocfree

new delete是C++的 *** 作符, mallocfree是C标准库函数对于非内部数据对象来说,只使用malloc是无法完成动态对象要求的,一般在创建对象时需要调用构造函数,对象消亡时,自动的调用析构函数。而malloc free是库函数而不是运算符,不在编译器控制范围之内,不能够自动调用构造函数和析构函数。而NEW在为对象申请分配内存空间时,可以自动调用构造函数,同时也可以完成对对象的初始化。同理,delete也可以自动调用析构函数。而mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。 1.6 strlen 和 sizeof

sizeof是运算符(事实上,sizeof既是关键字,也是运算符,但不是函数),而strlen是函数。sizeof运算符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能够容纳实现所建立的最大对象的字节大小sizeof可以用类型作为参数,strlen只能用char*作参数,而且必须是以“0结尾的。sizeof还可以以函数作为参数,如int g ( ),则sizeof ( g( ) )的值等于sizeof ( int的值,在32位计算机下,该值为4。)大部分编译程序的sizeof都是在编译的时候计算的,所以可以通过sizeof ( x )来定义数组维数。而strlen则是在运行期计算的,用来计算字符串的实际长度,不是类型占内存的大小。例如,charstr[20]= "0123456789”,字符数组str是编译期大小已经固定的数组,在32位机器下,为sizeof ( char ) *20=20,而其strlen大小则是在运行期确定的,所以其值为字符串的实际长度10。5.当数组作为参数传给函数时,传递的是指针,而不是数组,即传递的是数组的首地址。不用sizeof求int字节数 #define Mysizeof(value) (char * )(&value+1)-(char*)&value 1.7 struct union

结构体与联合体虽然都是由多个不同的数据类型成员组成的,但不同之处在于联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员,而结构体中所有成员占用空间是累加的,其所有成员都存在,不同成员会存放在不同的地址。在计算一个结构型变量的总长度时,其内存空间大小等于所有成员长度之和(需要考虑字节对齐),而在联合体中,所有成员不能同时占用内存空间,它们不能同时存在,所以一个联合型变量的长度等于其最长的成员的长度。对于联合体的不同成员赋值,会对它的其他成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的。 1.8 ++a a++

后置自增运算符需要把原来变量的值复制到一个临时的存储空间,等运算结束后才会返回这个临时变量的值。所以前置自增运算符效率比后置自增要高 1.9 define 和 typedef的区别

Typedef和define都可以用来给对象取一个别名,但是两者却有着很大不同。

首先,二者执行时间不同

关键字typedef在编译阶段有效,由于是在编译阶段,因此typedef有类型检查的功能。Define则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查

#define用法例子:

#define f(x) x*x
void main( )
{
 int a=6,b=2,c;
 c=f(a) / f(b);
 printf("%d //n",c);
}

程序的输出结果是: 36,根本原因就在于#define只是简单的字符串替换。

功能不同

Typedef用来定义类型的别名,这些类型不只包含内部类型(int,char等),还包括自定义类型(如struct),可以起到使类型易于记忆的功能。

如: typedef int (*PF) (const char *, const char *);

定义一个指向函数的指针的数据类型PF,其中函数返回值为int,参数为const char *。

typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以i获得最高的精度:typedef long double REAL;
在不支持 long double 的机器上,该 typedef 看起来会是下面这样:typedef double REAL;
并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:typedef float REAL;

#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

作用域不同

#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。

而typedef有自己的作用域。

void  fun()  
 {  
   #define  A  int  
 }  

 void  gun()  
 {  
    //在这里也可以使用A,因为宏替换没有作用域,  
    //但如果上面用的是typedef,那这里就不能用A  ,不过一般不在函数内使用typedef
 }
2 内存 2.1 分配方式

静态存储区分配:内存分配在程序编译之前完成,且在程序的整个运行期间都存在,例如全局变量、静态变量等。栈上分配:在函数执行时,函数内的局部变量的存储单元在栈上创建,函数执行结束时这些存储单元自动释放。堆上分配: 2.2 栈的作用

存储临时变量:形参、返回值是多线程的基础,每个线程都会有独立的栈空间中断 异常 2.3 压栈顺序

右–>左 2.4 C++的内存管理

在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。

代码段︰包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。数据段∶存储程序中已初始化的全局变量和静态变量BSS段∶存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。堆区︰调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。映射区:存储动态链接库以及调用mmap函数进行的文件映射栈︰使用栈空间存储函数的返回地址、参数、局部变量、返回值 3. 指针 3.1 数组指针和指针数组的区别

数组指针就是指向数组的指针,它表示的是一个指针,这个指针指向的是一个数组,它重点是指针

 #include 
 #include 
 void main()
 {
     int b[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
     int (*p)[4];
     p=b;
     printf("%dn",**(++p));
 }

程序的输出结果为5。
p是一个数组指针,它指向一个包含有4个int类型数组的指针,刚开始p被初始化为指向数组b的首地址并且占四个int的空间,++p相当于把p所指向的地址向后移动4个int所占用的空间,此时p指向数组(5,6,7,8),语句*(++p)﹔表示的是这个数组中第一个元素的地址(可以理解p为指向二维数组的指针,{ 1,2,3.4 } ,{ 5,6,7,8 },{ 9,10,11,12}。p指向的就是{ 1,2,3,4}的地址,*p就是指向元素,{ 1,2,3,4},[p指向的就是1),语句(++p )会输出这个数组的第一个元素5。

指针数组表示的是一个数组,而且数组中的每个元素都是一个指针

#include 
int main()
{
    int i;
    int *p[4];
    int a[4] = {1,2,3,4};
    p[0] = &a[0];
    p[1] = &a[1];
    p[2] = &a[2];
    p[3] = &a[3];
    for(i=0;i<4;i++)
    {
        printf("%d",*p[i]);
    }
}

结果为1234

3.2 函数指针和指针函数的区别

函数指针:在程序编译的时候会为已经定义了的函数分配一段存储空间,这段存储空间的首地址称为函数的地址,地址名字可以表示这个地址,所以当一个指针指向它这个地址的时候,这个指针被称为函数指针

//函数指针的定义
int (*p)(int ,int);

解释:由于()的优先级比 * 的优先级要高,所以p先和结合,故p是一个指针,加上(int, int)就成为了函数指针,完整解释:定义了一个指针p,该指针变量可以指向返回值类型为int,两个int形参的函数,p的类型应该属于 int( * ) (int ,int),*p的两端括号不能省略,如果省略的话那么就是成为了一个返回值类型为指针型的函数 :int *p(int , int)

指针函数:一个返回值是地址的函数,函数返回子必须用同类型的指针变量来承接

int *pfunc(int.int);   
//等价于
int *(pfunc(int,int))
3.3 数组名和指针的区别和联系

数据保存方面:指针保存的是地址(保存的是目标数据地址,自身的地址是由编译器分配),内存访问的偏移量是四个字节,跟位数有关。数组名表示的是第一个元素的地址,内存偏移量是保存的元素数据类型的偏移量,只有对数据名取地址(&)的时候才表示整个数组,内存偏移量是整个数组大小 sizeof(数组名)数据访问方面:指针对数据的访问是间接访问,需要用到解引用符号—>*数组名。数组对数据的访问是直接访问,通过下表访问数组名+元素偏移方式使用场景:指针多用于动态数据结构,如链表;数组多用于存储固定个数且类型统一的数据结构。 3.4 指针常量、常量指针、指向常量的常量指针有什么区别

指针常量:就是一个指向固定地址的指针,不能修改这个指针的指向,但是指向的地址的内容可以修改

int * const p;

常量指针:就是指向常量的指针,不能通过这个指针来修改指向的值

const int *p;
int const *p;

指向常量的常量指针:必须满足指针常量和常量指针的两个内容,也就是说不可以修改指针的指向,也不可以修改指针指向地址的内容

const int *const p;
3.5 指针的引用的区别? 怎么转换

相同点:都是地址,引用是某块内存的别名,引用的本质是指针常量,指向对象不能变,但是指向对象的值看一遍,两者都是地址,所以都会占用内存

区别:

指针是实体,引用是别名对++符号的意义不一样,指针是指地址偏移,引用的表示对应值的递增引用使用的时候无需解引用,指针需要解引用引用不能为空,必须是具体对象的引用,指针可以是一个空指针sizeof计算引用的时候得到的是指向变量的大小,而指针一般是固定的大小,在32位系统中占4个字节

#include "stdio.h"
int main()
{
    int x=10;
    int *p = &x;  //指针
    int &q = x;  //q 是对x的引用, printf("%d",q)  的值就是x的值,不需要解引用符号 * 
    printf("%d %d n",*p,sizeof(p));
    printf("%d %d n",q,sizeof(q));
    
}

转化:

指针转引用:把指针用 * 就可以转化为对象,可以用在引用参数当中引用转指针:把引用类型的对象用&取地址就可以得到指针。 3.6 指针的危险性

long * fellow;
*fellow = 223323;

fellow确实是一个指针,但是没有给这个指针赋予一个地址,所以这个指针将会被解释为存储值为223323的地址,如果这个时候我们的自己使用的变量用到的地址恰好是这个地址,那么这个时候就会出现难以调试的bug,因此,一定要在对指针进行解除引用运算符(*运算)之前,将指针初始化为一个确定的、适当的地址。

3.7 指针的递增递减 *** 作

① 递增递减:

    i++:先引用i的值,再进行加一,++i:先加一再引用。

② 优先级:* 、++i、–i的优先级相同,从右到左结合,i++、i–优先级比上面的三个要高

③ 运算:

    *++p:先进行p++,指针地址往后,再使用 * 获取对应地址的值

    ++*p:先获取当前p所指向地址的值,在将指针往后加一个单位的字节内存

    (*p)++:括号优先级max,本质:对当前地址的实际值进行加一 *** 作

    *p++:++优先级max,本质:取p指向地址的下一个地址的值

3.8 野指针

1.野指针是指向不可用内存的指针,当指针被创建时,指针不可能自动指向NULL,这时,默认值是随机的,此时的指针成为野指针。
2.当指针被free或delete释放掉时,如果没有把指针设置为NULL,则会产生野指针,因为释放掉的仅仅是指针指向的内存,并没有把指针本身释放掉。
3.第三个造成野指针的原因是指针 *** 作超越了变量的作用范围。

避免野指针

3.9 什么是智能指针(待完成) 网络编程 1. TCP 保证可靠性

序列号、确认应答(ack)、超时重传(ARQ):数据到达接收方,接收方需要发出一个确认应答,表示已经收到该数据段,并且确认序号会说明了它下一次需要接收的数据序列号。如果发送发迟迟未收到确认应答,那么可能是发送的数据丢失,也可能是确认应答丢失,这时发送方在等待一定时间后会进行重传。这个时间一般是2*RTT(报文段往返时间)+一个偏差值。窗口控制:TCP会利用窗口控制来提高传输速度,意思是在一个窗口大小内,不用一定要等到应答才能发送下一段数据,窗口大小就是无需等待确认而可以继续发送数据的最大值。如果不使用窗口控制,每一个没收到确认应答的数据都要重发。
使用窗口控制,如果数据段1001-2000丢失,后面数据每次传输,确认应答都会不停地发送序号为1001的应答,表示我要接收1001开始的数据,发送端如果收到3次相同应答,就会立刻进行重发;但还有种情况有可能是数据都收到了,但是有的应答丢失了,这种情况不会进行重发,因为发送端知道,如果是数据段丢失,接收端不会放过它的,会疯狂向它提醒 2. 三次握手和四次挥手

三次握手:

    Client将标志位SYN置为1,随机产生一个值seq=,并将该数据包发送给Server ,Client进入SYN_SENT状态,等待Server确认。

    Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1 , ack=1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

    Client收到确认后,检查ack是否为1,ACK是否为1,如果正确则将标志位ACK置为1 ,ack=K 1,并将该数据包发送给Server ,Server检查ack是否为K1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

四次挥手:

由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

    数据传输结束后,客户端的应用进程发出连接释放报文段,并停止发送数据,客户端进入FIN_WAIT_1状态,此时客户端依然可以接收服务器发送来的数据。服务器接收到FIN后,发送一个ACK给客户端,确认序号为收到的序号1,服务器进入CLOSE_WAIT状态。客户端收到后进入FIN_WAIT_2状态。当服务器没有数据要发送时,服务器发送一个FIN报文,此时服务器进入LAST_ACK状态,等待客户端的确认客户端收到服务器的FIN报文后,给服务器发送一个ACK报文,确认序列号为收到的序号1。此时客户端进入TIME_WAIT状态,等待2MSL ( MSL∶报文段最大生存时间),然后关闭连接。
3. TCP和UDP比较

1.TCP是面向连接的,UDP是面向无连接的

2.UDP程序结构较简单
3.TCP是面向字节流的,UDP是基于数据报的

4.TCP保证数据正确性,UDP可能丢包
5.TCP保证数据顺序,UDP不保证

TCP优点:可靠,稳定TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

TCP确定:慢,效率低,占用系统资源高,易被攻击TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

UDP优点:快,比TCP稍安全UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击。

UDP缺点:不可靠,不稳定因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。

4. TCP和UDP适用场景

TCP应用场景:效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等 *** 作,相比之下效率没有UDP高。举几个例子︰文件传输(准确高要求高、但是速度可以相对慢)、接受邮件.远程登录。UDP应用场景:效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信〔广福、多播).

是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击。

UDP缺点:不可靠,不稳定因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 4. TCP和UDP适用场景

TCP应用场景:效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等 *** 作,相比之下效率没有UDP高。举几个例子︰文件传输(准确高要求高、但是速度可以相对慢)、接受邮件.远程登录。UDP应用场景:效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信〔广福、多播).

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5714244.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存