C++多态虚函数实现原理,对象和虚函数表的内存布局

2023-05-16

基本概念

我们知道C++动态多态是用虚函数实现的,而虚函数的实现方式虽说C++标准没有要求,但是基本都是用虚函数表实现的(编译器决定)。所以我们有必要了解一下虚函数表的实现原理。

用virtual关键字声明的成员函数是虚函数。

具有虚函数的类及其派生的类会在编译时创建虚函数表,简称虚表(vtbl),虚表是虚函数指针的数组。

具有虚函数的类对象有一个虚表指针(vfptr),是编译器生成的指针,在对象构造时初始化。虚表指针vfptr指向虚表的第一个虚函数指针(即vfptr的值是虚表第一个虚函数指针的地址)。

打印对象布局或虚表布局

VS2017

在C/C++ -> 命令行里添加:/d1 reportSingleClassLayoutXXX(XXX是类名),重新编译会在输出窗口的生成里看到对象布局和虚表布局。

查看所有类布局

/d1 reportAllClassLayout 

 查看具体类布局

/d1 reportSingleClassLayoutXXX

gcc

查看虚表布局

g++ -fdump-class-hierarchy  Base.cpp

// >g++(8.3.1)
g++ -fdump-lang-class Base.cpp

然后会生成文件Base.cpp.002t.class,里面有虚表布局

gdb打印对象布局或虚表 

开启打印虚表
set print vtbl on

打印对象的虚表
i vtbl OBJECT

根据虚表打印对象的派生类
set p object on 

对象和虚表的内存布局

为了方便同时在windows和linux测试,统一使用x64编译器,数据类型使用intptr_t(与指针大小相同的整型)。

接下来介绍无继承有虚函数,单继承,多重继承,菱形继承,虚继承下的内存布局。

以下测试会在VS2017或gcc5.4.0进行。

无继承有虚函数

对象布局:第一个成员是虚表指针。

虚表布局:虚函数指针顺序即虚函数声明顺序。

class NoInhert_A {
public:
    NoInhert_A() { cout << "NoInhert_A::NoInhert_A()" << endl; }
    virtual ~NoInhert_A() { cout << "NoInhert_A::~NoInhert_A()" << endl; }
    virtual void f() { cout << "NoInhert_A::f()" << endl; }
    virtual void g() { cout << "NoInhert_A::g()" << endl; }
    intptr_t a = 1;
};

VS2017 

可以看到类NoInhert_A大小为16字节,虚表指针占8字节,变量a占8字节。虚表指针vfptr指向虚表的第一个虚函数指针,也就是析构函数。通过vfptr的偏移可以访问不同的虚函数指针。

gcc5.4.0

no_inhert.cpp.002t.class文件输出:

gdb输出:

可以看到对象布局和VS一样,都是16字节。

虚表指针vfptr指向虚表的第一个虚函数指针,接着是两个虚析构函数和两个虚函数。这是VS和gcc编译器的不同之处,gcc有两个析构函数指针,一个用在栈析构,一个用在堆析构。

虚表第一项是offset_to_top,表示该类虚表指针距离对象顶部地址的偏移量,这里是0,因为对象内存的第一项就是虚表指针,只有存在多重继承才不为0。

虚表的第二项是type_info,即RTTI指针,指向运行时类型信息,用于运行时类型识别,用于typeid和dynamic_cast。

整体内存布局如下:

单继承

单继承和多级继承比较简单,

对象布局:先是父类的成员,然后是子类的成员。

虚表布局:当子类重写虚函数时,父类的虚函数指针替换为子类的虚函数指针。

class SingleInhert_A {
public:
    SingleInhert_A() { cout << "SingleInhert_A::SingleInhert_A()" << endl; }
    virtual ~SingleInhert_A() { cout << "SingleInhert_A::~SingleInhert_A()" << endl; }
    virtual void f() { cout << "SingleInhert_A::f()" << endl; }
    virtual void g() { cout << "SingleInhert_A::g()" << endl; }
    intptr_t a = 1;
};

class SingleInhert_B : public SingleInhert_A {
public:
    SingleInhert_B() { cout << "SingleInhert_B::SingleInhert_B()" << endl; }
    virtual ~SingleInhert_B() { cout << "SingleInhert_B::~SingleInhert_B()" << endl; }
    virtual void f() override { cout << "SingleInhert_B::f()" << endl; }
    virtual void h() { cout << "SingleInhert_B::h()" << endl; }
    intptr_t b = 2;
};

int main() {
    SingleInhert_B obj;
    return 0;
}

 VS2017

gcc5.4.0

single_inhert.cpp.002t.class文件输出:

gdb输出:

 整体内存布局如下:

可以看到SingleInhert_B的虚表中,f函数被覆盖,g函数继承下来,h是新的虚函数。

多重继承

// 16字节
class Base1 {
public:
    Base1() { cout << "Base1::Base1()" << endl; }
    virtual ~Base1() { cout << "Base1::~Base1()" << endl; }
    virtual void f() { cout << "Base1::f()" << endl; }
    virtual void g() { cout << "Base1::g()" << endl; }
    intptr_t a = 1;
};

// 16字节
class Base2 {
public:
    Base2() { cout << "Base2::Base2()" << endl; }
    virtual ~Base2() { cout << "Base2::~Base2()" << endl; }
    virtual void f() { cout << "Base2::f()" << endl; }
    virtual void g() { cout << "Base2::g()" << endl; }
    virtual void h() { cout << "Base2::h()" << endl; }
    intptr_t b = 2;
};

// 40字节
class Derived : public Base1, public Base2 {
public:
    Derived() { cout << "Derived::Derived()" << endl; }
    virtual ~Derived() { cout << "Derived::~Derived()" << endl; }
    void f() override { cout << "Derived::f()" << endl; }
    void h() override { cout << "Derived::h()" << endl; }
    virtual void k() { cout << "Derived::k()" << endl; }
    intptr_t c = 3;
};

int main() {
    Derived d;
    return 0;
}

VS2017

/d1 reportSingleClassLayoutDerived

可以看到对象布局:先是父类Base1的成员,然后是Base2的成员,最后是子类Derived的成员。

注意这里继承2个父类所以子类有2个虚表,Derived和Base1共享虚表,对象有2个虚表指针。

整体内存布局如下:

可以看到子类Derived的f()和析构函数覆盖了Base1,&Base1::g和&Base2::g直接继承下来,Derived新增的 &Derived::k追加到Base1虚表的末尾。Derived和Base1共享第一个虚表。 

多重继承需要解决什么问题?

多重继承需要this指针调整。

考虑下面的情况:

Base2* pb2 = new Derived;
delete pb2;

Derived对象赋值给Base2指针时,this指针需要向后调整sizeof(Base),这样才能调用Base2的成员。

delete pb2时this指针要向前调整sizeof(Base),这样才能调用Derived的析构函数。

VS2017下多重继承总结

  • 子类的虚函数如果存在覆盖(override),会修改父类的虚表(替换为子类的虚函数指针)
  • 子类和第一个父类共享虚表,子类独有的虚函数指针追加到第一个父类的虚表后面
  • 有n个父类,则子类共有n个虚表

gcc5.4.0

multiple_inhert.cpp.002t.class输出:

 gdb输出:

和VS虚表实现不同,gcc下:

两个虚表似乎合并了,第二个虚表是通过偏移量得到的。为了方便下面介绍时还是说成两个虚表。

Base2被覆盖的虚函数指针&Derived::h被放在了第一个虚表里。当通过Base1或Derived的指针指向Derived对象时,调用h()函数不需要调整this指针,而通过Base2指针指向Derived对象时反而要调整this指针。这一点和《深度探索C++对象模型》多重继承例子里的mumble函数刚好相反,我个人猜测是gcc实现变了。

注意第二个虚表的offset_to_top是-16,即虚表指针向后调整16是对象起始地址,即this-=16。

所以gcc下Base2的虚函数除非没有被覆盖,否则都会走this指针调整。

菱形继承

/**
 * @brief 16字节
 * vptr_A
 * a
*/
class A {
public:
    A() { cout << "A::A()" << endl; }
    virtual ~A() { cout << "A::~A()" << endl; }
    virtual void f() { cout << "A::f()" << endl; }
    intptr_t a = 1;
};

/**
 * @brief 24字节
 * vptr_B
 * a
 * b
*/
class B : public A {
public:
    B() { cout << "B::B()" << endl; }
    virtual ~B() { cout << "B::~B()" << endl; }
    virtual void g() { cout << "B::g()" << endl; }
    intptr_t b = 2;
};

/**
 * @brief 24字节
 * vptr_C
 * a
 * c
*/
class C : public A {
public:
    C() { cout << "C::C()" << endl; }
    virtual ~C() { cout << "C::~C()" << endl; }
    virtual void h() { cout << "C::h()" << endl; }
    intptr_t c = 3;
};

/**
 * @brief 56字节
 * --------------------
 * vptr_B
 * a
 * b
 * --------------------
 * vptr_C
 * a
 * c
 * --------------------
 * d
*/
class Tom : public B, public C {
public:
    Tom() { cout << "Tom::Tom()" << endl; }
    virtual ~Tom() { cout << "Tom::~Tom()" << endl; }
    void f() override { cout << "Tom::f()" << endl; }
    virtual void k() { cout << "Tom::k()" << endl; }
    intptr_t d = 4;
};

int main() {
    Tom t;
    t.a = 2; // Tom::a不明确
    return 0;
}

菱形继承存在问题

  1. 公共父类的数据存在两份拷贝,造成浪费。(公共基类会构造和析构两次)
  2. 存在二义性,不能直接访问公共父类的数据和函数,需要通过类名::访问

解决方法:使用虚继承

VS2017

/d1 reportSingleClassLayoutTom

对象布局:

虚表布局:

 从上面对象布局可以看到A的数据成员a有两份。因为二义性我们也不能直接通过t.a访问a。

gcc5.4.0

diamond_inhert.cpp.002t.class输出:

gdb输出: 

菱形继承gcc逻辑跟VS差不多。

菱形继承+虚继承

/**
 * @brief 16字节
*/
class VA {
public:
    VA() { cout << "VA::VA()" << endl; }
    virtual ~VA() { cout << "VA::~VA()" << endl; }
    virtual void f() { cout << "VA::f()" << endl; }
    intptr_t a = 1;
};

/**
 * @brief VS2017:40字节,gcc5.4.0:32字节
*/
class VB : virtual public VA {
public:
    VB() { cout << "VB::VB()" << endl; }
    virtual ~VB() { cout << "VB::~VB()" << endl; }
    //void f() override { cout << "VB::f()" << endl; }
    virtual void g() { cout << "VB::g()" << endl; }
    intptr_t b = 2;
};

/**
 * @brief VS2017:40字节,gcc5.4.0:32字节
*/
class VC : virtual public VA {
public:
    VC() { cout << "VC::VC()" << endl; }
    virtual ~VC() { cout << "VC::~VC()" << endl; }
    virtual void h() { cout << "VC::h()" << endl; }
    intptr_t c = 3;
};

/**
 * @brief VS2017:80字节,gcc5.4.0:56字节
 * --------------------
*/
class VTom : public VB, public VC {
public:
    VTom() { cout << "VTom::VTom()" << endl; }
    virtual ~VTom() { cout << "VTom::~VTom()" << endl; }
    void f() override { cout << "VTom::f()" << endl; }
    virtual void k() { cout << "VTom::k()" << endl; }
    intptr_t d = 4;
};

int main() {
    VTom v;
    cout << sizeof(VA) << endl;
    cout << sizeof(VB) << endl;
    cout << sizeof(VC) << endl;
    cout << sizeof(VTom) << endl;
    return 0;
}

虚继承可以解决菱形继承的问题,菱形继承改为虚继承后,A只有一份拷贝。

深度探索C++对象模型里建议不要在虚基类声明非静态数据成员。

公共基类VA是虚基类。

只有第一个直接基类会调用虚基类的构造函数。

VS2017

/d1 reportSingleClassLayoutVTom

对象布局:

两个直接基类多了vbptr,即虚基类指针。vbptr指向虚基类表vbtable。

接着是子类成员,然后是4字节的vtordisp,为了保持8字节对齐,前面保留4字节。

最后才是虚基类的成员。

虚表布局:

可以看到子类新增的虚函数指针&VTom::k在第一个直接基类VB里。

再来看虚基类表:

  1. 虚基类表的第一项是类首地址相对vbptr的偏移量,这里都是-8。
  2. 虚基类表的第二项是虚基类的虚表指针相对vbptr的偏移量,即64-32=32,64-8=56。

gcc5.4.0

virtual_inhert.cpp.002t.class输出:

Vtable for VTom
VTom::_ZTV4VTom: 21u entries
0     40u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI4VTom)
24    (int (*)(...))VTom::~VTom
32    (int (*)(...))VTom::~VTom
40    (int (*)(...))VB::g
48    (int (*)(...))VTom::f
56    (int (*)(...))VTom::k
64    24u
72    (int (*)(...))-16
80    (int (*)(...))(& _ZTI4VTom)
88    (int (*)(...))VTom::_ZThn16_N4VTomD1Ev
96    (int (*)(...))VTom::_ZThn16_N4VTomD0Ev
104   (int (*)(...))VC::h
112   18446744073709551576u
120   18446744073709551576u
128   (int (*)(...))-40
136   (int (*)(...))(& _ZTI4VTom)
144   (int (*)(...))VTom::_ZTv0_n24_N4VTomD1Ev
152   (int (*)(...))VTom::_ZTv0_n24_N4VTomD0Ev
160   (int (*)(...))VTom::_ZTv0_n32_N4VTom1fEv

Construction vtable for VB (0x0x7f648bdb1888 instance) in VTom
VTom::_ZTC4VTom0_2VB: 13u entries
0     40u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI2VB)
24    0u
32    0u
40    (int (*)(...))VB::g
48    0u
56    18446744073709551576u
64    (int (*)(...))-40
72    (int (*)(...))(& _ZTI2VB)
80    0u
88    0u
96    (int (*)(...))VA::f

Construction vtable for VC (0x0x7f648bdb1820 instance) in VTom
VTom::_ZTC4VTom16_2VC: 13u entries
0     24u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI2VC)
24    0u
32    0u
40    (int (*)(...))VC::h
48    0u
56    18446744073709551592u
64    (int (*)(...))-24
72    (int (*)(...))(& _ZTI2VC)
80    0u
88    0u
96    (int (*)(...))VA::f

VTT for VTom
VTom::_ZTT4VTom: 7u entries
0     ((& VTom::_ZTV4VTom) + 24u)
8     ((& VTom::_ZTC4VTom0_2VB) + 24u)
16    ((& VTom::_ZTC4VTom0_2VB) + 80u)
24    ((& VTom::_ZTC4VTom16_2VC) + 24u)
32    ((& VTom::_ZTC4VTom16_2VC) + 80u)
40    ((& VTom::_ZTV4VTom) + 144u)
48    ((& VTom::_ZTV4VTom) + 88u)

Class VTom
   size=56 align=8
   base size=40 base align=8
VTom (0x0x7f648be1dd90) 0
    vptridx=0u vptr=((& VTom::_ZTV4VTom) + 24u)
  VB (0x0x7f648bdb1888) 0
      primary-for VTom (0x0x7f648be1dd90)
      subvttidx=8u
    VA (0x0x7f648bd785a0) 40 virtual
        vptridx=40u vbaseoffset=-24 vptr=((& VTom::_ZTV4VTom) + 144u)
  VC (0x0x7f648bdb1820) 16
      subvttidx=24u vptridx=48u vptr=((& VTom::_ZTV4VTom) + 88u)
    VA (0x0x7f648bd785a0) alternative-path

 gdb输出:

虽然gdb输出VA的成员在最前面,但实际上的对象布局是这样的:

整体内存布局:

virtual base offsets表示虚基类虚表指针VA::vfptr相对于直接基类首地址的偏移量,即40和24。

可以看到整体上虚表布局显示类VB部分,接着是类VC部分,最后是虚基类VA部分。

虚基类部分都是使用trunk技术,即调整this指针来访问虚函数。

虚表开头是第一个直接基类VB的虚函数指针&VB::g,然后是覆盖虚基类的&VTom::f和子类新增的&VTom::k。

整体上虚表是一个连续的数组,但是逻辑上我们可以认为这里有3个虚表。

gcc下没有vbptr。

参考链接

C++虚函数的实现基本原理 - JackTang's Blog

面试系列之C++的对象布局【建议收藏】 - SegmentFault 思否

图说C++对象模型:对象内存布局详解 - melonstreet - 博客园

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++多态虚函数实现原理,对象和虚函数表的内存布局 的相关文章

  • 程序设计思维与实践 Week13 作业 (3/4/数据班)

    A TT 的神秘任务1 xff08 必做 xff09 这一天 xff0c TT 遇到了一个神秘人 神秘人给了两个数字 xff0c 分别表示 n 和 k xff0c 并要求 TT 给出 k 个奇偶性相同的正整数 xff0c 使得其和等于 n
  • 安装scikit-learn问题

    1 问题描述如下 xff1a pytorch root 64 cento conda install scikit learn Solving environment failed CondaHTTPError HTTP 000 CONNE
  • 火狐下setting a property that has only a getter 的错误, 真是诡异...

    刚刚遇到了个很诡异的问题 xff0c 有一段看似没有错误的js代码硬是在火狐下会报个 setting a property that has only a getter 错误 xff0c 而在其他浏览器下却可以正常运行 xff0c 代码大概
  • 最新社区版idea插件“intellij-spring-assistant”

    1 背景 idea社区版已经用了好长时间了 xff0c 其中丰富的插件库让社区版使用起来与专业版无差异 xff0c 完全能够满足当前工作需求 xff0c 但是随着版本的不断更新 xff0c 原作者已不再更新 xff0c 导致无法在新版本上使
  • VS2017/2019中默认编码问题,修改文本编码格式 为UTF-8

    1 修改VS中单个文本编码方式 VS2017 2019中默认格式为 GB2312 很多时候可能出现乱码情况 xff0c 就是编码问题 xff0c 接下来 xff0c 可以修改编码方式 xff1a 操作方法如下所示 xff1a 首先 xff0
  • 宏定义中的括号和自增自减运算(1)

    宏定义中容易引起许多运算优先级的问题 xff0c 需要用括号加以约束 例如 define abs x x gt 0 x x abs a b abs a 43 1 带入展开后 xff0c 结果如下 xff1a a b gt 0 a b a b
  • K-means算法

    算法描述 xff1a 1 gt 从N个数据中选出K个元素作为质心 xff0c 即数据将被分成K簇 2 gt 依次计算剩下的每一个元素到K个元素的相异度 xff0c 一般是计算距离 xff0c 将这些元素分别划分到相异度最低的簇中去 3 gt
  • 编程领域中的事务概念解析

    在编程领域 xff0c 事务 xff08 Transaction xff09 是一个非常重要的概念 它可以帮助我们处理分布式系统中的并发问题 保证数据的一致性和完整性 本文将详细解释事务的概念及其在编程中的应用 一 什么是事务 xff1f
  • C++流概述

    C 43 43 流概述 在程序设计中 xff0c 数据输入 输出 xff08 I O xff09 操作是必不可少的 xff0c C 43 43 语言的数据输入 输出操作是通过I O流库来实现的 C 43 43 中把数据之间的传输操作称为流
  • kNN(K-Nearest Neighbor)最邻近规则分类

    K最近邻分类算法 方法的思路 xff1a 如果一个样本在特征空间中的k个最相似 xff08 即特征空间中最邻近 xff09 的样本中的大多数属于这一类别 xff0c 则该样本也属于这个类别 KNN算法中 xff0c 所选择的邻居都是已经正确
  • SPOOLING技术

    SPOOLING技术 xff08 Simultaneous Peripheral Operating On Line 同时联机外围操作技术 xff0c 它是关于慢速字符设备 如何与计算机主机进行数据交换 的一种技术 xff0c 通常又称 假
  • Belady现象

    Belady现象 采用FIFO算法时 xff0c 如果对 个进程未分配它所要求的全部页面 xff0c 有时就会出现分配的页面数增多但缺页率反而提高的异常现象 Belady现象的描述 xff1a 一个进程P要访问M个页 OS分配N N lt
  • 计算结构体的字节数

    结构体中的成员可以是不同的数据类型 xff0c 成员按照定义时的顺序依次存储在连续的内存空间 和数组不一样的是 xff0c 结构体的大小不是所有成员大小简单的相加 xff0c 需要考虑到系统在存储结构体变量时的地址对齐问题 看下面这样的一个
  • 轻松搞定面试中的二叉树题目

    版权所有 xff0c 转载请注明出处 xff0c 谢谢 xff01 http blog csdn net walkinginthewind article details 7518888 树是一种比较重要的数据结构 xff0c 尤其是二叉树
  • 使用Anaconda Navigator无法成功创建虚拟环境问题的解决方案

    1 问题描述 使用anaconda Navigator创建虚拟环境时 xff0c 配置初始名称以及python版本 xff0c Fetching各种包成功后 xff0c 开始loading各种包的过程中闪过cmd黑色窗口 xff0c 然后左
  • QT 后台处理时间过长 主界面卡死解决办法

    之前用WPF开发 xff0c 处理逻辑就是1 xff0c 处理前显示等待窗口 xff0c 2 同步处理改未异步 xff0c 3 处理完毕后关闭等待窗口 Qt应该也是类似的处理逻辑 xff1a 一 创建等待处理窗口 xff08 采用了QMoi
  • 一圈n个人,1-3循环报数,报道3的退出,最后剩下的是几号

    import java util ArrayList import java util List import java util Scanner public class CirCle public static void main St
  • GCD【洛谷P2568】(小左的GCD)

    题目描述 给定整数N xff0c 求1 lt 61 x y lt 61 N且Gcd x y 为素数的数对 x y 有多少对 输入格式 一个整数N 输出格式 答案 输入输出样例 输入 1 复制 4 输出 1 复制 4 说明 提示 对于样例 2
  • C++中的weak_ptr深入解析

    引言 在C 43 43 的智能指针家族中 xff0c weak ptr是一种非常实用且独特的成员 它主要用于解决循环引用问题 xff0c 从而避免内存泄漏 在本文中 xff0c 我们将详细讨论weak ptr的基本概念 功能和应用场景 xf
  • 【Redis】解决WARNING overcommit_memory is set to 0 Background save may fail under low memory condition.

    问题说明 不管是linux直装 xff0c 还是在docker环境中 xff0c 启动redis时 xff0c 报如下错误 WARNING overcommit memory is set to 0 Background save may

随机推荐

  • 运动跟踪算法CMT(续)之层次凝聚聚类算法(HAC)

    熟悉CMT的都知道 xff0c 作者在聚类部分使用了层次凝聚聚类算法 xff08 Hierarchical Agglomerative Clustering xff09 并且使用的是单链 xff08 Single link xff09 xf
  • 使用mysql8.x版本设置远程连接

    主要步骤 xff0c 注意 xff1a 自mysql8 x版本 xff0c 密码的加密方式改为caching sha2 password 登录mysql账号修改root用户登录地址修改root用户密码加密方式 usr local mysql
  • jenkins基础配置之四:读取本地文件

    需要安装的插件 Extended Choice Parameter Plug span class token operator span In span class token operator span External Monitor
  • 初识Btrfs文件系统

    Btrfs 也有一个重要的缺点 xff0c 当 BTree 中某个节点出现错误时 xff0c 文件系统将失去该节点之下的所有的文件信息 而 ext2 3 却避免了这种被称为 错误扩散 的问题 Btrfs相关介绍 xff1a Btrfs 是一
  • 服务器使用笔记本网络连接外网

    由于服务器经常部署在机房 xff0c 并没有外网 xff0c 连不上外网 需要使用自己笔记本的网络供服务器使用 笔记本连接手机热点 xff0c 再分享给服务器 一 首先 xff0c 需要把服务器和笔记本连接到同一网络内 xff0c 可以选择
  • grafana接入openldap认证

    首先两个文件开启ldap的支持 文件1 xff1a etc grafana grafana ini auth ldap enabled 61 true config file 61 etc grafana ldap toml allow s
  • Wireshark的常见提示

    概述 本文主要介绍Wireshark中出现的一些常见提示 详细信息 Wireshark简介 Gerald Combs是堪萨斯城密苏里大学计算机科学专业的毕业生 1998年发布了第一版Ethereal工具 xff0c Ethereal工具使用
  • shell报错bad substitution 解决办法

    bin bash a 61 34 hello 34 b 61 34 hi is a 34 echo b echo a echo a echo a 1 2 执行脚本方式不同出现的结果不同 xff1a 方式1 xff1a sh shell sh
  • centos8软件安装dnf命令

    DNF是新一代的rpm软件包管理器 它首先出现在 Fedora 18 这个发行版中 而目前 xff0c 它取代了yum xff0c 正式成为从 Fedora 22 起 Fedora 版本的包管理器 DNF包管理器克服了YUM包管理器的一些瓶
  • 多目标规则在 Makefile 中的应用与示例

    在 Makefile 中 xff0c 如果一个规则有多个目标 xff0c 而且它们之间用空格分隔 xff0c 我们称之为 34 多目标规则 34 这意味着这个规则适用于列出的所有目标 在目标下面的命令是 C 64 xff0c 它通常与 ma
  • 计算机中内存、cache和寄存器之间的关系及区别

    1 寄存器是中央处理器内的组成部份 寄存器是有限存贮容量的高速存贮部件 xff0c 它们可用来暂存指令 数据和位址 在中央处理器的控制部件中 xff0c 包含的寄存器有指令寄存器 IR 和程序计数器 PC 在中央处理器的算术及逻辑部件中 x
  • dell 台式电脑设置每天定时开机和关机

    每天定时开机设置 xff1a 戴尔电脑通过CMOS设置实现自动开机的设置过程如下 xff1a 1 首先进入 CMOS SETUP 程序 大多数主板是在计算机启动时按DEL或F2键进入 xff1b 2 然后将光条移到 Power Manage
  • windows批处理自动获取电脑配置信息

    39 2 gt nul 3 gt nul amp cls amp 64 echo off 39 amp rem 获取本机系统及硬件配置信息 39 amp set 61 Any question amp set 64 61 WX amp se
  • Centos7搭建cisco ocserv

    一 安装的部分直接yum安装即可 yum y install ocserv 二 配置文件根据实际情况调整 auth方式有两种 1 系统账号认证 配置的话就是 xff1a auth 61 34 pam 34 2 本地文件认证 配置的话就是 x
  • 私有harbor部署(docker方式)

    环境准备 docker compose v Docker Compose version v2 14 2 wget https github com docker compose releases download v2 14 2 dock
  • ORACLE扩展表空间

    一 查询表空间使用情况 SELECT UPPER F TABLESPACE NAME 34 表空间名 34 D TOT GROOTTE MB 34 表空间大小 M 34 D TOT GROOTTE MB F TOTAL BYTES 34 已
  • Oracle 常用性能监控SQL语句

    1 查看表锁 SELECT FROM SYS V SQLAREA WHERE DISK READS gt 100 2 监控事例的等待 SELECT EVENT SUM DECODE WAIT TIME 0 0 1 34 Prev 34 SU
  • Nginx出现“ 413 (499 502 404) Request Entity Too Large”错误解决方法

    1 Nginx413错误的排查 修改上传文件大小限制 在使用上传POST一段数据时 xff0c 被提示413 Request Entity Too Large xff0c 应该是nginx限制了上传数据的大小 解决方法就是 打开nginx主
  • 查看弹出广告来自哪个软件

    打开VS的Spy 43 43 将指针移到广告处 xff0c 然后点OK xff0c 在Process标签页可以看到进程id和线程id将获得的16进制进程id xff08 例如 xff1a 000025F8 xff09 通过计算器转成10进制
  • C++多态虚函数实现原理,对象和虚函数表的内存布局

    基本概念 我们知道C 43 43 动态多态是用虚函数实现的 xff0c 而虚函数的实现方式虽说C 43 43 标准没有要求 xff0c 但是基本都是用虚函数表实现的 xff08 编译器决定 xff09 所以我们有必要了解一下虚函数表的实现原