1zplay电子竞技:深信服一面C++

作者:编程    发布时间:2020-01-12 11:51     浏览次数 :

[返回]

首先自我介绍

1.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?

第十三章 多进程编程

IPC(Inter-Process Communication)


Linux中创建共享内存的方式?共享内存中起始地址是不是按照页的大小对齐?创建共享内存的时候物理页一定分配吗?惰性空间分配的实现方式?

答:首先,extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

13.1 fork 系统调用

Linux下创建新进程的系统调用是fork.

pid_t fork(void)

该函数的每次调用都返回两次,在父进程中返回的是子进程的PID,在子进程则返回0.该返回值是后续代码判断当前进程是父/子进程的依据,调用失败则返回-1,并设置error.

子进程与父进程有很多属性和原进程相同.如堆指针,栈指针和标志寄存器的值,以及继承下来的父进程的文件描述符(其引用会加1)等.
当然,也有许多属性会被赋予新的值,比如该进程的PPID被设置成原进程的PID,信号位图被清楚(原进程设置的信号处理函数不再对新进程起作用).

数据的复制利用了写时复制,即只有在任一进程(父进程或子进程)对数据执行了写操作复制才会发生.


象棋中马从a到b点的最短路径的求解

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号 库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y );该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。这样的名字包含了函数名、函数参 数数量及类型信息,C++就是靠这种机制来实现函数重载的。

13.2 exec 系列系统调用

需要在子进程中执行其他程序,即替换当前映像,可以使用exec系列函数之一,

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
  • file:文件名
  • path:可执行文件的完整路径
  • arg:可变参数
  • argv:参数数组
  • envp:设置新程序的环境变量

exec函数错误返回1,并设置error.若没出错,则原程序exec调用之后的代码都不会执行,因为此时原程序已经被exec所指定的程序完全替换了.

exec函数不会关闭原程序打开的描述符,除非该文件描述符被设置了类似SOCK_CLOEXEC的属性.

例子:

execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);

c语言怎样判断两个浮点数是否相等?

所以,可以用一句话概括extern “C”这个声明的真实目的:解决名字匹配问题,实现C++与C的混合编程

13.3 处理僵尸程序

若父进程没有及时正确处理子进程的返回信息,子进程都会停留在僵尸态,并占据内核资源.

若拥有子进程的父进程异常终止,则子进程的PPID会被设置为1,即被init进程接管.

要正确处理子进程的退出,父进程需要等待子进程的结束,并获取子进程的返回信息,从而避免僵尸进程的产生,或者使子进程的僵尸态立即结束,有两组函数可以做到:

pid_t wait (int * status);
pid_t waitpid(pid_t pid,int * status,int options);

前者:将阻塞进程,直到该进程的某个子进程结束运行为止.

后者:只等待所指定的子进程,若pid的取值为-1,其行为就和wait一样.而且通过options可以设置其行为,当options是WNOHANG时waitpid调用将是非阻塞的:若指定的子进程没有结束或意外终止,则waitpid会立即返回0;若目标进程确实正常退出了,则waitpid返回子进程的PID.waitpid调用失败返回-1并设置error.

waitpid应该在进程结束的时候被调用以结束进程,故需要在子进程结束后给父进程发送信号,在父进程接收到信号后做出此操作以真正的结束子进程.


结构体的比较是否能够通过内存比较的方法判断是否相等?结构体对齐在小端方式下的实现机制?

2.头文件中的ifndef/define/endif有什么作用?

13.4 管道

管道也是父进程和子进程通信的常用手段.通常的管道只能单向的传输/接收数据,但是也可以利用socket的socketpair创建双向管道.


static的用法,static修饰函数有什么特殊的地方,static的这种特性怎样实现的?

答:这是C++预编译头文件保护符,保证即使文件被多次包含,头文件也只定义一次。

13.5 信号量

P,V 操作 : P原来是荷兰语passeren即传递,V是vrijeven,即释放.

P,V操作应该要被实现为原子操作,因为需要如下的功能(同时的)

  • 拥有检测变量是否为true/false

  • 若是则设置为false/true

Linux信号量的API主要包含了三个系统调用

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semctl(int semid, int semnum, int cmd, ...);
  • semget:创建一个新的信号集或者获取一个已经存在的信号量集..

  • semop:改变信号量的值.

  • semctl:对信号量进行直接控制.


fork的使用方法,子进程结束以后父进程如何知道,父进程在子进程结束以后要做什么事情?

3. #include 与 #include "file.h"的区别?

13.6 共享内存

书中称共享内存是高效的IPC机制,因为它不涉及进程之间的任何数据传输,这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件.

关于Linux提供的共享内存API

int shmget(key_t key, size_t size, int shmflg); 
void *shmat(int shmid, const void *shmaddr,int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 
  • shmget:创建一段新的共享内存或获取一段已经存在的共享内存.

  • shmat:共享内存被创建后不能立即访问,需要将它关联到进程的地址空间中.

  • 1zplay电子竞技,shmdt:在使用完共享内存后,也需要将它从进程的地址空间中分离.

  • shmctl:控制共享内存的某些属性.


单向链表如何判断有环?

答:前者是从标准库路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。

13.7 共享内存的POSIX方法

Linux提供了另外一种利用mmap在无关进程之间共享内存的方式,它无任何文件的支持,但它需要先使用如下函数进行操作

int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
  • shm_open:返回一个文件描述符,该文件描述符可用于后续的mmap调用,从而将共享内存关联到调用进程.

  • shm_unlink:由shm_open创建的共享内存对象使用完之后也需要被删除.此函数将指定的对象为等待删除,当所有使用该共享内存对象的进程都使用ummap将它从进程中分离之后,系统将销毁这个共享内存对象所占据的资源.


统计英文文章中出现次数最多的几个单词的解决方案?

4.评价一下C/C++各自的特点

13.8 消息队列

消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式.

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • msgget:创建消息队列,或者获取一个已有的消息队列.

  • msgsnd:把一条消息队列添加到消息队列.

  • msgrcv:从消息队列中获取消息.

  • msgctl:控制队列某些属性.


函数调用过程中栈的变化过程?返回值和参数变量哪个先入栈?

答:C语言是一种结构化语言,面向过程,基于算法和数据结构,所考虑的是如何通过一个过程或者函数从输入得到输出;

关于第十三章的总结

  • 复习了一下fork,exec族.

  • 三种System V进程间通信的方式,1)信号量2)共享内存3)消息队列

  • 进程间传递文件描述符的通用方法1)通过UNIX本地域即socketpair的PF_UNIX属性

  • 进程间通信常用的是管道.

  • 其实我觉得这些机制都是指的在某种特定情况下的通信,若是集群的话就不一定了.

  • 这一章代码我就不贴了,都是例子.使用的话查手册就好啦:)


From

Linux 高性能服务器编程 游双著 机械工业出版社

MarkdownPad2

2017/2/11 23:22:21

C++中拷贝构造函数形参用值来进行传递有什么影响?

C++是面向对象,基于类、对象和继承,所考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题,通过获取对象的状态信息得到输出或实现过程控制。

面试30分钟,加上后面问问题十分钟,总共40分钟。期间面试官问我有没有参加笔试,估计这次笔试没有刷人。。。

5.const 有什么用途?

点击作者姓名与作者大佬交流~

答:在C/C++中,(1)可以定义const常量,(2)修饰函数的返回值和形参;

- 互联网名企笔试真题

在C++中,还可以修饰函数的定义体,定义类的const成员函数。被const修饰的东西受到强制保护,可以预防意外的变动,提高了程序的健壮性。

- 校招求职笔经&面经

6.const和#define有什么区别?

- 程序员/产品/运营求职实习信息

答:(1)const和#define都可以定义常量,但是const用途更广。

- 程序员/产品/运营学习交流社区

(2)const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

(3) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

7.关于sizeof小结的。

答:sizeof计算的是在栈中分配的内存大小。

(1) sizeof不计算static变量占得内存;

(2) 指针的大小一定是4个字节,而不管是什么类型的指针;

(3) char型占1个字节,int占4个字节,short int占2个字节

long int占4个字节,float占4字节,double占48字节,string占4字节

一个空类占1个字节,单一继承的空类占1个字节,虚继承涉及到虚指针所以占4个字节

(4) 数组的长度

若指定了数组长度,则不看元素个数,总字节数=数组长度*sizeof(元素类型)

若没有指定长度,则按实际元素个数类确定

Ps:若是字符数组,则应考虑末尾的空字符。

(5) 结构体对象的长度

在默认情况下,为方便对结构体内元素的访问和管理,当结构体内元素长度小于处理器位数的时候,便以结构体内最长的数据元素的长度为对齐单位,即为其整数倍。若结构体内元素长度大于处理器位数则以处理器位数为单位对齐。

(6) unsigned影响的只是最高位的意义,数据长度不会改变,所以sizeof(unsigned int)=4

(7) 自定义类型的sizeof取值等于它的类型原型取sizeof

(8) 对函数使用sizeof,在编译阶段会被函数的返回值的类型代替

(9) sizeof后如果是类型名则必须加括号,如果是变量名可以不加括号,这是因为sizeof是运算符

(10) 当使用结构类型或者变量时,sizeof返回实际的大小。当使用静态数组时返回数组的全部大小,sizeof不能返回动态数组或者外部数组的尺寸

8.sizeof与strlen的区别?

答: (1)sizeof的返回值类型为size_t(unsigned int);

(2)sizeof是运算符,而strlen是函数;

(3)sizeof可以用类型做参数,其参数可以是任意类型的或者是变量、函数,而strlen只能用char*做参数,且必须是以’’结尾;

(4)数组作sizeof的参数时不会退化为指针,而传递给strlen是就退化为指针;

(5)sizeof是编译时的常量,而strlen要到运行时才会计算出来,且是字符串中字符的个数而不是内存大小;

9.指针和引用的区别?

答:指针和引用都提供了间接操作对象的功能。

(1) 指针定义时可以不初始化,而引用在定义时就要初始化,和一个对象绑定,而且一经绑定,只要引用存在,就会一直保持和该对象的绑定;

(2) 赋值行为的差异:指针赋值是将指针重新指向另外一个对象,而引用赋值则是修改对象本身;

(3) 指针之间存在类型转换,而引用分const引用和非const应用,非const引用只能和同类型的对象绑定,const引用可以绑定到不同但相关类型的对象或者右值

10.数组和指针的区别?

答:(1)数组要么在全局数据区被创建,要么在栈上被创建;指针可以随时指向任意类型的内存块;

(2)修改内容上的差别:

char a[] = “hello”;

a[0] = ‘X’;

char *p = “world”; // 注意p 指向常量字符串

p[0] = ‘X’; // 编译器不能发现该错误,运行时错误

(3)用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

11.空指针和悬垂指针的区别?

答:空指针是指被赋值为NULL的指针;delete指向动态分配对象的指针将会产生悬垂指针。

(1) 空指针可以被多次delete,而悬垂指针再次删除时程序会变得非常不稳定;

(2) 使用空指针和悬垂指针都是非法的,而且有可能造成程序崩溃,如果指针是空指针,尽管同样是崩溃,但和悬垂指针相比是一种可预料的崩溃。

12.C++中有malloc/free,为什么还有new/delete?

答:malloc/free是C/C++标准库函数,new/delete是C++运算符。他们都可以用于动态申请和释放内存。

对于内置类型数据而言,二者没有多大区别。malloc申请内存的时候要制定分配内存的字节数,而且不会做初始化;new申请的时候有默认的初始化,同时可以指定初始化;

对于类类型的对象而言,用malloc/free无法满足要求的。对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。由于malloc /free是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++还需要new/delete。

**13.什么是智能指针?
**

答:当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。

智能指针的一种通用实现技术是使用引用计数。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。

每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对 象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构 造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

14.面向对象技术的基本概念是什么,三个基本特征是什么?

答:基本概念:类、对象、继承; 基本特征:封装、继承、多态。

封装:将低层次的元素组合起来形成新的、更高实体的技术;

继承:广义的继承有三种实现形式:实现继承、可视继承、接口继承。

多态:允许将子类类型的指针赋值给父类类型的指针

15.C++空类默认有哪些成员函数?

答:默认构造函数、析构函数、复制构造函数、赋值函数

16.哪一种成员变量可以在一个类的实例之间共享?

答:static静态成员变量

17.继承层次中,为什么基类析构函数是虚函数?

答:编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向 一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么 会有资源泄漏。

下一篇:没有了