常见面试题(2)

1.进程间通信

进程间通信有管道,共享内存,消息队列和信号量

管道其实就是内存中的一块缓冲区,管道分为匿名管道和命名管道;

匿名管道没有标识符,只能在具有亲属关系的进程间进行通信

命名管道有标识符,可以被其它进程找到,可以用于一台主机上任意进程间通信

共享内存:开辟出一块物理内存,需要通信的进程将这块物理内存映射到自己的虚拟地址空间里,直接使用自己的用户空间地址进行访问。它也是所有进程间通信方式中最快的一种,因为通过虚拟地址直接访问内存,相较于其他方式少了两次用户空间与内核空间之间的数据拷贝,它的生命周期随内核;但是它是覆盖式内存操作,没有同步与互斥控制,缺乏安全。

消息队列:其实就是在内核中创建的一个消息链表,不同的进程通过向链表添加和获取节点来实现进程间通信,传输的都是消息结点

信号量本质上就是内核中的一个计数器

作用:实现进程间的同步与互斥(保护进程间对临界资源的访问操作)

信号量实现同步与互斥的原理
实现同步:根据资源数量初始化计数器

通过计数器对资源进行计数,若计数大于0则表示可以访问资源,若资源小于等于0则表示不能访问,则阻塞进程

P操作:在进程访问资源之前进行,判断计数是否大于0;

大于0,则正确返回,计数-1;若小于0,阻塞进程,计数-1;

V操作:当产生一个新的资源,计数+1,唤醒一个阻塞的进程

实现互斥:

初始化临界资源计数器为1;

在访问临界资源之前进行P操作,在访问临界资源之后进行V操作.实现唯一访问

模板在编译时会被实例化

2. 如何限制类只能在堆上创建对象

将类的构造和拷贝构造私有化,在共有部分提供一个静态的在堆上获取对象的函数。

class Student {
public:
    static Student* getduixiang()
    {
        return new Student;
    }
private:
    Student()
    {}
    Student(const Student&) = delete;
};

3.如何限制类只能在栈上创建对象

构造函数私有,提供静态获取栈上的对象

class Student {
public:
    static Student get()
    {
        return Student();
    }
private:
    Student() {};
};

或者屏蔽new

class Student
{
public:
    Student() {};
private:
    void* operator new(size_t s) = delete;
    void operator delete(void* arg) = delete;
};

4.相交链表

    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA==nullptr||headA==nullptr)
        {
            return nullptr;
        }   
        ListNode*pA=headA;ListNode*pB=headB;
        while(pA!=pB)
        {
            pA= pA==nullptr ?headB:pA->next;
            pB= pB==nullptr ?headA:pB->next;
        }
        return pA;
    }

5.反转链表

    ListNode* reverseList(ListNode* head) {
        if(head==nullptr||head->next==nullptr)
        {
            return head;
        }
        ListNode*prev=nullptr;
        ListNode*cur=head;
        while(cur)
        {
            ListNode*temp=cur->next;
            cur->next=prev;
            prev=cur;
            cur=temp;
        }
        return prev;
    }

6.strcpy和strncpy的函数缺陷

因为strcpy函数并不检查目的缓冲区的大小边界,而是将源字符串逐一的全部赋值给目的字符串地址起始的一块连续的内存空间,同时加上字符串终止符。

使用的时候n取dst的size()-1;

1.存在潜在越界问题

当dest的长度 < src的长度的时候,由于无法根据指针判定其所指指针的长度,故数组内存边界不可知的。因此会导致内存越界,尤其是当数组是分配在栈空间的,其越界会进入你的程序代码区,将使你的程序出现非常隐晦的异常。

2.字符串结束标识符’\0’丢失

当dest所指对象的数组长度==count的时候,调用strncpy使得dest字符结束符’\0’丢失。手动给上字符串结束符。

3.效率较低

当count > src所指对象的长度的时候,会继续填充’\0’知道count的长度为止。

4.不能处理内存覆盖问题

不能处理dest和src内存重叠的情况。

自己实现
char* mystrcpy(char* dst, const char* src)
{
    char* ret = dst;
    assert(dst != nullptr);
    assert(src != nullptr);
    
    while ((*dst++ = *src++))
    {
        ;
    }
    return ret;
}
char* my_strncpy(char* dst, const char* src, int n)
{
    int c = n;
    char* ret = dst;
    assert(dst != nullptr);
    assert(src != nullptr);
    while (n--)
    {
        *dst++ = *src++;
    }
    ret[c] = '\0';
    return ret;
}

7.死锁概念,怎样预防

死锁是指程序无法继续推进,这是由于对锁资源的争抢不当导致的

4个条件:互斥,不可剥夺,环路等待,请求与保持

预防:加解锁顺序一致;使用非阻塞加锁,加不了锁释放已有的锁,银行家算法

8.写时拷贝,引用计数

        写时拷贝(copy-on-write, COW)就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝。
  写时拷贝技术实际上是运用了一个 “引用计数” 的概念来实现的。在开辟的空间中多维护四个字节来存储引用计数。
有两种方法:
①:多开辟四个字节(pCount)的空间,用来记录有多少个指针指向这片空间。
②:在开辟空间的头部预留四个字节的空间来记录有多少个指针指向这片空间。
  当我们多开辟一份空间时,让引用计数+1,如果有释放空间,那就让计数-1,但是此时不是真正的释放,是假释放,等到引用计数变为 0 时,才会真正的释放空间。如果有修改或写的操作,那么也让原空间的引用计数-1,并且真正开辟新的空间。

linux 下的 fork() 就是用的写时拷贝技术,引用计数不光在 string 这里用到,还有智能指针 shared_ptr 也用到了引用计数来解决拷贝问题。

9.http和https的区别

http是明文传输,在客户端与服务器之间传输数据,非常不安全,容易被盗取,端口80;

https采用加密传输,身披SSL/TSL的http协议,SSL/TLS依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密,在数据进行传输之前,对数据进行加密,然后再发送到服务器。这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息仍然是安全的。这就是HTTP和HTTPS的最大区别。端口443;

10.线程池

思想:线程池其实是一堆创建好的线程和一个任务队列,有任务来了就抛入线程池中,分配一个线程进行处理线程池中的线程与任务节点数量都有最大限制,避免资源消耗.

一个阻塞队列,一个任务节点类,线程池类

11.进程和线程

进程是cpu资源调度的基本单位,是一个程序的动态运行。linux内核中的一个task_struct结构体。

线程是进程中的一条执行流,执行程序中的某段代码,是程序执行的基本单位

线程是进程的一条执行流,而进程是pcb,pcb是linux下的执行流,所以linux下的pcb也可以是线程,不过我们通常说的是轻量级进程;

所谓轻量化进程就是一个进程中可以存在多个pcb,这些pcb共用一个虚拟地址空间 每一个线程都是进程中的一个执行流,因此多个执行流可以共同工作,这些pcb合在一起就是线程组,也可以将进程理解为一个线程组.

每个进程中都有唯一的主线程,且只能有一个,主线程和进程是相互依存的关系,主线程结束进程也会结束。一个进程的多个线程之间共用进程虚拟地址空间, 线程在共享区有自己独有的空间

12.TCP和UDP区别

UDP:无连接,不可靠传输,面向数据报

TCP:面向连接的,可靠传输,面向字节流

udp如何实现可靠传输?
需要程序员在应用层进行处理:

  • 包序管理:给每个数据报进行编号
  • 进行丢包检测:实现确认应答机制和超时重传机制

13. vector底层

他有三个迭代器:

_Myfirst 指向的是 vector 容器对象的起始字节位置;_Mylast 指向当前最后一个元素的末尾字节;_myend 指向整个 vector 容器所占用内存空间的末尾字节。这三个迭代器通过组合可以实现不同的功能。

另外需要指明的是,当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:

  1. 完全弃用现有的内存空间,重新申请更大的内存空间;
  2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
  3. 最后将旧的内存空间释放。

 由此可见,vector 扩容是非常耗时的。为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity>=size),以便后期使用。 

14. 僵尸进程危害?怎样处理?

造成内存泄漏,占用大量资源。

使用wait来等待。采用信号SIGCHLD通知处理,并在信号处理程序中调用wait函数

15.拥塞避免算法的曲线图,横竖坐标意义?

横坐标是传输伦次,纵坐标是拥塞窗口大小

当cwnd大于ssthresh执行拥塞避免。

  • 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。

  • 考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值