【C/C++】 面试6

   
64. linux命令

	软连接 ln -s src_app dst_app 文件位置重定向

66. dpdk的优势和sendfile()
    传统linux网络层:

    硬件中断--->取包分发至内核线程--->软件中断--->内核线程在协议栈中处理包--->处理完毕通知用户层

    用户层收包-->网络层--->逻辑层--->业务层

    dpdk网络层:

    硬件中断--->放弃中断流程

    用户层通过设备映射取包--->进入用户层协议栈--->逻辑层--->业务层
    DPDK的优势:
    减少了中断次数。

    减少了内存拷贝次数。

    绕过了linux的协议栈,进入用户协议栈,用户获得了协议栈的控制权,能够定制化协议栈降低复杂度

    DPDK的劣势:

    内核栈转移至用户层增加了开发成本.

    低负荷服务器不实用,会造成内核空转

    dpdk发包性能数据
    iperf3 -c 192.168.0.92 -b 1g
    70MBtytes/s
67.介绍下nginx的结构
    以daemon的方式在后台运行,一个master进程和多个worker进程

    设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。
    master: 
            读取并验证配置信息;
            创建、绑定及关闭套接字;
            启动、终止 worker 进程及维护 worker 进程的个数;
            无须中止服务而重新配置工作;
            控制非中断式程序升级,启用新的二进制程序并在需要时回滚至老版本;
            重新打开日志文件;
            编译嵌入式 Perl 脚本。
    worker: 
            接收、传入并处理来自客户端的连接
            提供反向代理及过滤功能
            nginx 任何能完成的其它任务
    用一段伪代码来总结一下nginx的事件处理模型:
    while (true) {
        for t in run_tasks:
            t.handler();
        update_time(&now);
        timeout = ETERNITY;
        for t in wait_tasks: /* sorted already */
            if (t.time <= now) {
                t.timeout_handler();
            } else {
                timeout = t.time - now;
                break;
            }
        nevents = poll_function(events, timeout);
        for i in nevents:
            task t;
            if (events[i].type == READ) {
                t.handler = read_handler;
            } else { /* events[i].type == WRITE */
                t.handler = write_handler;
            }
            run_tasks_add(t);
    }
68.bpf

69.mutex 和 rwlock 区别

读写锁是mutex和 cond
typedef struct {
  pthread_mutex_t   rw_mutex;       /*最基础的读写互斥锁*/
  pthread_cond_t    rw_condreaders; /* 读等待的条件变量 */
  pthread_cond_t    rw_condwriters; /* 写等待的条件变量 */
  int               rw_magic;
  int               rw_nwaitreaders;
  int               rw_nwaitwriters;
  int               rw_refcount;
} pthread_rwlock_t;

读写锁的特点是:
当读写锁是写加锁时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。
当读写锁是读加锁时,在这个锁被解锁之前,所有试图以读模式对他进行加锁的线程都可以得到访问权,
但是如果线程以写模式对此锁加锁时会造成阻塞,直到所有线程释放读锁

70.拥塞控制算法

 1 慢启动 指数级由小到大逐渐增加拥塞窗口大小,如果网络出现阻塞,拥塞窗口就减小。

        判断出现网络拥塞的依据:没有按时收到应当到达的确认报文

 2 拥塞避免  每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍,拥塞窗口按线性规律缓慢增长

 3 快重传   快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认

 4 快恢复


    窗口过小:无法充分利用网络资源,传输效率低下。
    窗口过大:可能导致网络拥塞,出现较高的丢包率,传输速度降低。

    二. TFB-GCC原理

    1. 接收端记录并反馈收包情况

    (1)transport-wide sequence nunmber

    (2)RTCP RTPFB TW 报文

    2. 发送端结合包接收反馈情况进行带宽预估拥塞控制

    (1)基于延时梯度的带宽预估

    (2)基于丢包率的带宽预估

71: C++中有几种类型的new

    在C++中,new有三种典型的使用方法:plain new,nothrow new和 placement new

    void* operator new(std::size_t) throw(std::bad_alloc);
    char *p = new char[10e11];
    因此plain new在空间分配失败的情况下,抛出异常std::bad_alloc而不是返回NULL,因此通过判断返回值是否为NULL是徒劳的

    void * operator new(std::size_t,const std::nothrow_t&) throw();
    char *p = new(nothrow) char[10e11];
    if (p == NULL) 
    {
        cout << "alloc failed" << endl;
    }

    这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。
    placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。
    void* operator new(size_t,void*);
    placement new构造起来的对象数组,要显式的调用他们的析构函数来销毁(析构函数并不释放对象的内存),千万不要使用delete,
    这是因为placement new构造起来的对象或数组大小并不一定等于原来分配的内存大小,使用delete会造成内存泄漏或者之后释放内存时出现运行时错误。

72. 虚函数可以是内联函数吗?

   虚函数可以是内联函数,inline 可以修饰虚函数,但是当虚函数表现多态性的时候不能内联。
   解析:内联发生在函数的编译期间,编译器会自主选择内联,而虚函数的多态性发生在运行期间,
   编译器无法知道运行期间调用哪个代码,所以,当虚函数变现为多态性时不可以内联。

   静态成员函数不可以是虚函数。静态函数是属于类的,不属于对象本身,自然无法有自己的虚函数表指针。

   在子类写继承自父类的虚函数,若该在父类中虚函数末尾写了const,而在子类中没有写,那么将认为子类中写的函数不是父类函数的重载;
    同理,若该虚函数在父类中没有const,而在子类中加上了const则认为子类中写的函数也不是父类函数的重载。

    char * strncpy(char * dest,const char * src,size_t count);
    char * strncpy(char * restrict dest,const char * restrict src,size_t count);
    errno_t strncpy_s(char * restrict dest,rsize_t destsz,const char * restrict src,rsize_t count);


73:编译器性能优化
    1、Debug(调试)
        在Debug模式下,编译器不会对代码进行优化,而是专注于生成易于调试的代码。
        这使得开发者可以在调试过程中更直观地跟踪变量的值和程序的执行流程。
    2、Release(发布)
        在Release模式下,编译器会启用各种优化技术来提高代码的运行效率和减小程序的大小。
        这些优化可能会改变代码的执行顺序和变量的存储方式,从而使得代码在调试时的行为与实际运行时有所不同。
        内存优化

    1、开发者可以使用预处理器指令(如#ifdef DEBUG)来编写仅在Debug模式下编译或执行的代码。

    #ifdef MYDLL_EXPORTS
    #define MYDLL_API __declspec(dllexport)
    #else
    #define MYDLL_API __declspec(dllimport)
    #endif

74:C++17新特性
包括结构化绑定、折叠表达式、constexpr if、inline变量、类模板参数推导、
新的字符串字面量、更便捷的并行编程支持(std::execution)等。此外,
C++17还对已有特性进行了改进,包括lambda表达式、随机数生成器、std::optional、std::variant等

75:栈上开辟和堆上开辟

    struct nginx_str_t{
        size_t len;
        u_char* data;
    }
    内存池的好处: 避免栈溢出
    (1)针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法和多线程保护。
    也不需要维护内存空闲表的额外开销,从而获得较高的性能。
    (2)由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。
    (3)比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。

76:Linux 多进程锁的几种实现方案
    1. 文件锁实现多进程锁
    由于文件锁是存放到位于内存的系统文件表中, 所有进程/线程可通过系统访问。如果不同进程使用同一文件锁(写锁/排他锁),
    当取得文件锁时,进程可继续执行;
    如果没有取得锁,则阻塞等待。而唯一标识该文件的是文件路径,因此,可以通过一个共同的文件路径,来实现多进程锁机制。
	static struct flock lock_it; /* 用于加锁的flock对象 */
    static struct unlock_it;     /* 用于解锁的flock对象 */
    static int lock_fd = -1; 
    关键点:
    1)创建/打开一个唯一路径的文件;
    2)P操作,取得文件的写锁(排他锁);V操作,释放文件的锁;

    2. 多线程锁实现多进程锁
    多线程之间天然共享内存/变量,而多进程各有自己的进程空间,它们之间是不共享数据的

    2个关键步骤
    1)互斥锁变量存放到共享内存;
    2)设置互斥锁变量的进程共享属性(PTHREAD_PROCESS_SHARED);

77. 引用垂悬
    C++中的"引用垂悬"指的是在函数返回时使用了局部变量作为引用类型的参数。这种情况下会导致未定义行为或者程序错误

78. que和deque
    queue(队列)只能从一端添加元素,从另一端删除元素
    dequeque不是队列,是vector和list的结合体
    deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下

79.仿函数
    仿函数是一个类或结构体,它重载了operator()运算符,使其可以像函数一样被调用。
    仿函数 可以具有任意数量的参数,并可以用于各种不同的操作。这使得它们非常灵活,可以根据需要进行定制。
    class MySubtractionFunc 
    {
    public:
        int operator()(int a, int b) 
        {
            return a - b;
        }
    };
    MySubtractionFunc subfunc;
    int a = sunfunc(5, 6);

80.快速排序的原理 伪代码
   首先快速排序要选取一个基准值,以基准值为分割,把比该基准值小的放左边,基准值大的放右边。然后得出来的序列再进行快速排序
   快速排序的时间复杂度为O(nlogn),是目前基于比较的内部排序里被认为是最好的方法,当数据过大并且数据是杂乱无序的时候,适合用快速排序。


void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *))
{
    if (nmemb <= 1)
        return;
    char *pivot = base + (nmemb - 1) * size;
    char *left = base;
    char *right = pivot - size;
    while (left < right) {
        while (left < right && compar(left, pivot) <= 0)
            left += size;
        while (left < right && compar(right, pivot) >= 0)
            right -= size;
        if (left < right) {
            char tmp[size];
            memcpy(tmp, left, size);
            memcpy(left, right, size);
            memcpy(right, tmp, size);
        }
    }
    if (compar(left, pivot) > 0) {
        char tmp[size];
        memcpy(tmp, left, size);
        memcpy(left, pivot, size);
        memcpy(pivot, tmp, size);
    }
    qsort(base, (left - base) / size, size, compar);
    qsort(left + size, (nmemb - 1 - (left - base) / size), size, compar);
}

81.虚函数表调用虚函数

#include <iostream>
using namespace std;

class Base 
{
public:
    virtual void f()
    {
        cout << "Base::f()" << endl;
    }
    
    virtual void g()
    {
        cout << "Base::g()" << endl;
    }
    
    virtual void h()
    {
        cout << "Base::f()" << endl;
    }
};

class Derive : public Base
{
public:
    virtual void g()
    {
        cout << "Derive::g()" << endl;
    }
};

int main()
{

	cout<< sizeof(Base) << endl;  //4字节,说明虚函数表的指针在这里的x86平台是4字节的(vptr),g++编译器是8字节
	cout<<sizeof(Derive)<<endl;   //4字节,和Base类一个道理
	Derive* d = new Derive();     //派生类指针其实用基类指针指向派生类也一样Base*d = new Derive();
	long* pvptr = (long*)d;       //指向对象d的指针转成long*型。大家注意目前d对象里只有虚函数表指针
	long* vptr = (long*)(*pvptr); //(*pvptr)表示pvptr指向的对象,也就是Derive对象本身这个对象4字节,
								// 这个4字节是虚函数表地址。例如* pvptr 可能等于15440724(0x00eb9b54),
								//这个其实就是虚函数表地址。把一个虚函数表地址用(long *)一转,则本行的意思
								//就是让 vptr 代表Derive对象虚函数表指针,用于指向类Derive的虚函数表

	typedef void(*Func)(void);  //typedef 后面是定义一个函数指针,加上typedef 就代表 Func是一个函数指针类型,
                           //也就是它能当类型用。Func当类型用后就可以像下面这样
	Func f = (Func)vptr[0];  //fgh就是函数指针变量,vptr[0]指向第一个虚函数{project4.exe!Base::f(void)}
	Func g=(Func)vptr[1]; //vptr[1]指向第二个虚函数{project4.exe!Derive::g(void)}
	Func h=(Func)vptr[2]; //vptr[2]指向第三个虚函数{project4.exe!Base::h(void)}

	f();    //Base::f
	g();    //Derive::g子类覆盖父类的虚函数
	h();    //Base::h

	return 0;
}

82.用过哪些库
   libgo 
   nlohmann/json
   dpdk
   vpp
   boost

83.tcpdump命令
tcpdump -i eth0 -s 0 -w file.pcap
tcpdump -i eth0 port 22
84.动态库和静态库
g++ -o demo demo.cpp  -L/home/tools -lpublic 
g++ -fPIC -shared -o libpublic.so public.cpp 编译动态库
g++ -c -o libpublic.a public.cpp 编译静态库
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/tools


84.事务的隔离级别
   读未提交
   读已提交
   重复读
   序列化

85.Linux共享内存 消息队列
   查看进程树
   pstree -p 

   getpid();
   getppid();

   ipcs -s  查看信号量
   ipcs -m  查看共享内存
   ipcs -p  查看消息队列

   ipcrm sem  删除信号量
   ipcrm -m   删除共享内存

   ipcrm -p   删除消息队列

   msgget();
   msgsnd();
   msgrcv();
   msgctl();

86:内存池和线程池的优势和劣势

    内存池是一种内存分配方式。在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。
    当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。使用内存池的优点有:速度远比 malloc / free 快,
    因为减少了系统调用的次数,特别是频繁申请/释放内存块的情况。
但是,使用内存池也有缺点:
    1.如果预先分配的内存块过多,会浪费大量的空间;
    2.如果预先分配的内存块过少,则会导致频繁地向操作系统申请新的内存块,从而降低程序效率。

    2.传统内存使用的弊端
    1.高并发时较小内存块使用导致系统调用频繁,降低了系统的执行效率
    2.频繁使用时增加了系统内存的碎片,降低内存使用效率
    3.没有垃圾回收机制,容易造成内存泄漏,导致内存枯竭
    4.内存分配与释放的逻辑在程序中相隔较远时,降低程序的稳定性


    线程池是一种并发编程的设计模式,用于管理和调度线程的执行。它具有许多优点和一些缺点。
    优点:
    1. 提高系统性能池可以重用线程,避免了线程的频繁创建和销毁过而减少了系统开销。这样可以大大提高系统的响应速度和吞吐量。
    提高资源利用率池可以根据系统的实际情况调整线程的数量,避免了线程过多导致的资源浪费和线程过少导致的资源利用不充分的情况。

87. cpu高怎么排查

    使用top命令:
    通过`top`命令查看CPU使用率最高的几个进程及其PID(进程标识符)。
    使用`top -c`选项按CPU使用率排序进程列表。
    找出具体占用CPU的进程,并记录其PID。
    定位到线程
    使用`top`命令查看CPU使用率最高的线程及其TID(线程标识符)。
    如果需要,可以使用`top -Hp TID`来获取线程的信息。

88.windows 封装动态库dll 静态库lib

    C++导入导出符号(dllimport/dllexport)

    extern "C" {
	    _declspec(dllexport) int AddTwoInt(int a, int b);
    }

    extern "C" {
	    _declspec(dllimport) int AddTwoInt(int a, int b);
    }

89.QT内存管理
    使用对象父子关系进行内存管理
    在创建类的对象时,为对象指定父对象指针。当父对象在某一时刻被销毁释放时,
    父对象会先遍历其所有的子对象,并逐个将子对象销毁释放

90.virtual 修饰构造函数会发生什么
    在构造和析构函数内不要调用virtual函数,因为这类调用不下降至子类。
91.类型转换失败会发生什么?
    static_cast转换失败的情况 直接是编译不通过:static_cast无法实现BaseOne* 到BaseTwo* 的转换。

92.查看进程树
   pstree -p 

   getpid();
   getppid();

   ipcs -s  查看信号量
   ipcs -m  查看共享内存

   ipcrm sem  删除信号量
   ipcrm -m   删除共享内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值