1.*a 和&a 有什么区别
&a:其含义就是“变量a的地址”。
*a:用在不同的地方,含义也不一样。
在声明语句中,*a只说明a是一个指针变量,如int *a;
在其他语句中,*a前面没有操作数且a是一个指针时,*a代表指针a指向的地址内存放的数据,如b=*a;
*a前面有操作数且a是一个普通变量时,a代表乘以a,如c=ba。
2.指针和引用的异同点;如何相互转换
本质:引用是别名,而指针是地址;
指针在运行时可以改变所指向的值,而引用一旦与某个对象绑定之后就不再改变(指向的地址不能改变,但指向的内容可以改变);
指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值;因此指针可以改变指向的对象,而引用的对象不能修改;
由于硬件通过地址访问内存位置,因此引用可以理解为一个常量指针,只能绑定到初始化它的对象上。
3. 请你来说一下函数指针(本质是一个指针)
指针函数:int* fun(int x,int y);
函数指针:int (*fun)(int x,int y);
定义: 函数指针是指向函数的指针变量。
函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。
4.说一下指针数组和数组指针
数组指针:int (*p)[n];
指针数组:int *p[n];
指针数组:本质是数组,数组内存放的是指针
数组指针:本质是指针,指针指向这个数组
5、哪些情况会出现野指针
指针变量未初始化、指针释放后未置空、指针操作超越变量作用域。
6.请你来回答一下include头文件的顺序以及双引号””和尖括号<>的区别?
Include头文件的顺序:对于include的头文件来说,如果在文件a.h中声明一个在文件b.h中定义的变量,而不引用b.h。那么要在a.c文件中引用b.h文件,并且要先引用b.h,后引用a.h,否则汇报变量类型未声明错误。
双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不一样。
对于使用双引号包含的头文件,查找头文件路径的顺序为当前头文件目录
7.简述#ifdef、#else、#endif和#ifndef的作用
利用#ifdef、#endif将某程序功能模块包括进去,以向特定用户提供该功能。在不需要时用户可轻易将其屏蔽。
#else如果前面的条件不成立,就执行else中的程序
#ifndef如果宏没有定义则编译下面的代码
8.简述strcpy、sprintf 与memcpy 的区别
操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型, 目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字 符串的转化,memcpy 主要是内存块间的拷贝。
注意:strcpy、sprintf 与memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。
9.new/delete和malloc/free的区别
1.malloc/free是C/C++的库函数,需要stdlib.h;new/delete是C++的关键字;
2.都可用于申请动态内存和释放内存,new/delete在对象创建的时候自动执行构造函数,对象消亡前自动执行析构函数,底层实现其实也是malloc/free
3.new无需指定内存块的大小,编译器会根据类型信息自行计算;malloc需要显式地支持所需内存的大小
4.new返回指定类型的指针,无需进行类型转换;malloc默认返回类型为void*,必须强行转换为实际类型的指针
5.new内存分配失败时会抛出bad_alloc异常;malloc失败时返回NULL
10.strcat、strncat、strcmp、strcpy函数
strcpy拷贝函数,不会判断拷贝大小,也没有任何安全检查,不会检查目的地址内存是否够用;
strncpy拷贝函数,会计算复制字符串的大小,但没有检查目标的边界;
strcmp比较函数,把src所指向的字符串与dest所指向的字符串进行比较,若dest与src的前n个字符相同,则返回0;若dest大于src,则返回大于0的值;若dest小于src,则返回小于0的值
strcat功能是将两个char类型连接;strncat功能是在字符串的结尾追加n个字符
11.机器大小端问题
大端指数据的高字节保存在内存的低地址中,数据的低字节保存在内存的高地址中;小端与此相反。 (回答出这个即可)
小端:强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样;
大端:符号位的判定固定为第一个字节,很容易判断正负;
union判断大小端的方法;
union从低地址开始存,同一时间内只有一个成员占用内存;修改其中一个成员的值必然会影响另一个成员的值;
12.static的用法(定义和用途)
static修饰局部变量:使其变为静态存储方式(静态数据区),函数执行完成之后不会被释放,而是继续保存在内存中;
static修饰全局变量:使其只在本文件内部有效,其他文件不可链接或引用该变量;
static修饰函数:静态函数,即函数只在本文件内部有效,对其他文件不可见;避免同名干扰,同时保护
13.const的用法(定义和用途)
const起到强制保护的修饰作用,可以预防意外改动,提高程序的健壮性
const修饰常量:定义时就初始化,以后不能更改;
const修饰形参:func(const int a); 该形参在函数里不能改变;
const修饰类成员函数:const类成员函数不能改变成员变量的数值
14.const常量和#define的区别(编译阶段、安全性、内存占用等)
const定义的常量有类型名字,存放在内存的静态区域中,在编译时确定其值;
#define定义的常量是没有类型的一个立即数,编译器会在预处理阶段将程序中所有使用到该常量的地方进行拷贝替换;
由于#define的拷贝有很多份,故宏定义的内存占用要高得多
15.volatile的用法
被定义为volatile的变量可能会被意想不到地改变,编译器不会对volatile变量有关的运算进行编译优化:每次使用该变量必须从内存地址中读取,而不是保存在寄存器中的备份
用到volatile的几种情况
1)并行设备的硬件寄存器(如状态寄存器)
2)中断服务子程序会访问到的非自动变量
3)多线程应用中被几个任务共享的变量
16.sizeof和strlen
sizeof是一个操作符或关键字,不是一个函数,而strlen是一个函数
sizeof返回一个对象或类型所占的内存字节数,不会对其中的数据或指针做运算
strlen返回一个字符串的长度,不包括'/0'
17.inline函数
被频繁调用的函数会导致栈空间或栈内存的大量消耗,因此引入inline修饰函数,即内联函数;内联函数将在程序的每个调用点上“内联式地”展开。内联以代码膨胀为代价,省去了函数调用的开销,从而提高函数的执行效率
18.C程序在内存中包括的五个区域是什么, 各存放的什么?
一个可执行C程序在内存中主要包含5个区域,分别是代码段(text),数据段(data),BSS段,堆段(heap)和栈段(stack)。其中前三个段(text,data,bss)是程序编译完成就存在的,此时程序并未载入内存进行执行。后两个段(heap,stack)是程序被加载到内存中时,才存在的。
1.代码区:.text 就是C程序编译后的机器指令,也就是我们常见的汇编代码。
2.全局初始化数据区/静态数据区:.data,明确被初始化的全局变量、静态变量和常量数据,整个生命周期内都可能需要访问
3.未初始化数据区:.bss,全局未初始化变量
4.栈区stack:由编译器自动分配释放,存放函数的参数值、局部参数的值等。每当一个函数被调用,该函数返回地址和调用信息,如某些寄存器内容,会被存储到栈区,这个被调用的函数再为它的自动变量和临时变量在栈区上分配空间,即C实现函数递归调用的方法
5.堆区heap:用于动态内存分配
19、全局变量和静态变量区别
1.存储方式上并无区别,都是静态存储方式;
2.非静态全局变量作用域为整个源程序;当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的,而静态全局变量则限制了其作用域,只在定义该变量的源文件内有效;
20.在使用gcc 对C语言程序进行编译时,可细分为几个阶段:
预处理(Pre-processing)、编译(Compiling)、汇编(Assembling)、链接(Linking)
预处理:进行头文件、宏定义的替换,条件编译,删除注释,还有语义和词义的分析
编译:将预处理后的文件编译成汇编文件
汇编:将汇编文件转换二进制文件
链接:将二进制文件链接成可执行文件
21.请你说一下进程与线程的概念,以及为什么要有进程线程,其中有什么区别
基本概念:
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务, 是CPU调度的基本单位, 用于保证程序的实时性,实现进程内部的并发;
区别:
1.进程是资源分配的最小单位,线程是CPU调度的最小单位;
2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
3.一个进程崩溃,不会对其他进程产生影响;而一个线程崩溃,会让同一进程内的其他线程也死掉。
4.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
5.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
(系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/O设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。)
6.通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预
7.进程适应于多核、多机分布;线程适用于多核
至少回答3个。
22.进程的状态有哪些?
创建状态(创建态):进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态。
就绪状态(就绪态):进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行。
执行状态(运行/执行态):进程处于就绪状态被调度后,进程进入执行状态。
阻塞状态(阻塞态):正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用。
终止状态(终止态):进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行。
23 .fork和vfork的区别
1.fork( )的子进程是拷贝父进程的数据段和代码段;
vfork( )的子进程与父进程共享数据段
2.fork( )的父子进程的执行次序不确定;
vfork( )保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
3.vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
4.当需要改变共享数据段中变量的值,则拷贝父进程。
24.进程间通信的方式:
进程间通信主要包括:管道(有名管道,无名管道)、系统IPC(包括信号、信号量、消息队列、共享内存)、以及网络通信(套接字socket)。
1. 管道: 管道主要包括无名管道和有名管道
无名管道:PIPE====>int pipe(int pipefd[2]);
1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端,读端只能读,写端只能写
2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
3)无名管道没有名字,只有文件描述符数组它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
4)没办法保证写入数据的原子性
有名管道:FIFO====>int mkfifo(const char *pathname, mode_t mode); (管道名,权限)
1)有名管道存储在linux环境下的文件系统里面,可以使用open函数访问
2)能在任意两个进程之间通信
3)写入的数据具有原子性,要不写入成功,要么写入失败
4)全双工
2. 信号(signal):信号是系统软件层次上对中断机制的一个模拟(软中断方式)是一种异步通信机制。
1)信号的种类
可以从两个不同的分类角度对信号进行分类
可靠性方面: 可靠信号 和 不可靠信号
时间关系方面: 实时信号 和 非实时信号
可靠性:主要问题时信号可能丢失,这些信号来源于早期的unix
后期信号是前期信号的改进和扩充,这些信号支持排队,不会丢失
时间:
非实时信号都是不支持排队,都是不可靠信号
实时信号都是支持排队的,都是可靠信号
19 SIGSTOP 9 SIGKILL 这两个信号必须响应,
34-64号实时信号 (从大到小依次响应)
2)对于一个完整的信号生命周期(从信号的发送到相应的处理函数执行完毕)可以分为四个阶段:
信号诞生
信号在进程中注册
信号的响应和处理
信号的执行和注销
3. 信号量: int semget(key_t key, int nsems, int semflg);
信号量不是一种通信方式,只是一个处理方式,一般用于控制多个进程对共享资源的访问,实现不同进程访问共享内存的互斥与同步,帮助你阻塞进程协调不同进程对于共享资源的访问。
特点:
1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
4)支持信号量组。
4. 消息队列:
特点:进程可以通过消息队列读取自己的指定消息
消息队列的操作步骤:
1)申请系统键值 ftok
2)申请消息队列 msgget
3)消息队列的数据发送和接收,配置消息结构体 msgsnd msgrcv
4)消息对列销毁 msgctl IPC_RMID
5. 共享内存(Shared Memory):key_t ftok(const char *pathname, int proj_id);
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等
特点:
1)共享内存是最快的一种IPC,因为进程是直接对内存进行存取
2)因为多个进程可以同时操作,所以需要进行同步
3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
4)共享内存的操作步骤:
5)申请系统键值 ftok
6)申请共享内存 shmget
7)映射共享内存到用户空间 shmat
8)对共享内存进行读写访问操作
9)解除共享内存映射 shmdt
10)删除共享内存 shmctl IPC_RMID
6. 套接字:int socket(int domain, int type, int protocol);
socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。
25.死锁发生的条件以及如何解决死锁
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件如下:
互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源;
请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源;
不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放;
环路等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链
解决死锁的方法即破坏上述四个条件之一,主要方法如下:
资源一次性分配,从而剥夺请求和保持条件;
可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件;
资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件。
26.多线程,线程同步的几种方式
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
线程间通信的方式:
1、临界区:
通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;
2、互斥量 Synchronized/Lock:
采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
3、信号量 Semphare:
为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
4、事件(信号),Wait/Notify:
通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作
27.TCP三次握手过程
第一次握手:主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B,向主机B 请求建立连接,通过这个数据段, 主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。
第二次握手:主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用那个序列号作为起始数据段来回应我
第三次握手:主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:"我已收到回复,我现在要开始传输实际数据了,这样3次握手就完成了,主机A和主机B 就可以传输数据了。 3次握手的特点 没有应用层的数据 SYN这个标志位只有在TCP建立连接时才会被置1 握手完成后SYN标志位被置0。
28.TCP断开连接要进行4次挥手
第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;
第二次: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;
第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;
第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.
29.IP地址作用,以及MAC地址作用
MAC地址是一个硬件地址,用来定义网络设备的位置,主要由数据链路层负责。而IP地址是IP协议提供的一种统一的地址格式,为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
30.Linux常用指令
Pwd,ls, cd ,cp,mv,rm,cat,touch,mkdir,chmod,
说出3,4个就可。
标签:面试题,函数,int,30,嵌入式,线程,内存,进程,指针 From: https://blog.csdn.net/qq_57285750/article/details/140408552