C++常见面试题知识点

本文详细探讨了C++的内存区域,包括栈、堆、全局区、常量区和代码区,以及线程堆栈的关系。同时,深入讲解了C++中的动态内存管理,对比了malloc/new与free/delete的异同。此外,文章还分析了类的数据成员与函数,特别是this指针、虚函数、虚表指针和构造函数中调用虚函数的细节。最后,讨论了虚析构函数的重要性,强调了防止内存泄漏的必要性。
摘要由CSDN通过智能技术生成

以下仅为个人思路,有错还望大家及时指出。

1.C++内存空间

参考链接:C++函数调用内存分配机制_zhongguoren666的博客-CSDN博客

线程的堆和栈_JackLiu16的博客-CSDN博客_线程是在堆上还是栈上

C/C++变量在内存中的分布_MoreWindows的博客-CSDN博客

C/C++程序内存的分配_cherrydreamsover的博客-CSDN博客_c++内存分配

C/C++动态内存管理malloc/new、free/delete的异同_cherrydreamsover的博客-CSDN博客
C/C++动态内存管理malloc/new、free/delete的异同_cherrydreamsover的博客-CSDN博客

C++主要可分为静态区和动态区。其中C/C++程序内存的分配这篇博客是精髓。

1)栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
2)堆区(heap):一般由程序员自动分配,需要手动释放,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
3)全局区(静态区static):存放全局变量、静态数据、常量。程序结束后由系统释放。全局区分为已初始化全局区(data)和未初始化全局区(bss)。
4)常量区(文字常量区):存放常量字符串,程序结束后有系统释放。
5)代码区:存放函数体(类成员函数和全局区)的二进制代码。

补充:

1)每个线程一个栈,每个进程一个堆。线程允许共享的是堆中的数据。

参考:

堆栈 和 线程堆栈的问题-CSDN论坛

一个进程中各线程的堆和栈的关系_大爱李志的博客-CSDN博客_一个进程几个堆几个栈

2)char* 与char[]

char* 与char[]的内存分配方式比较古灵精怪。char* 分配在常量区,char[]分配在栈区。

参考:

C语言 char*和char[]用法_imxlw00的博客-CSDN博客_c char*

举个例子便可马上理解:

char* ch1() {
    char s[] = "123456";//分配在栈区
    s[1] = '9';
    return s;
}
char* ch2() {
    char* s = "123456";//分配在常量区
    //s[1]='2'//因为是分配在常量区,因此这个会报错
    return s;
}

int main()//int argc,char** argv
{
    
    char* s=ch1();
  //  cout << sizeof(s) << endl;
    for (int i = 0; i != 6; ++i)cout << s[i] << endl;
//无法正常打印,因为ch1()中返回的指针指向一个栈中的地址,而栈在离开作用域后内存会被释放
    cout << "*********************" << endl;
    s = ch2();
    for (int i = 0; i != 6; ++i)cout << s[i] << endl;
//可正常打印,因为ch2()中返回的指针指向一个常量区中的地址,其离开作用域后不会被释放
    return 0;
}

有可能char* s = "123456"语句会报错,具体原因可参考:

const char*(字符串常量)能否赋值给char*


2.类的数据成员与函数

首先要了解this指针,可参考:

【P115 33】C++ 对象特性—this指针和空指针_R-G-B的博客-CSDN博客_c++ this指针为空

c++中this指针的用法详解_程小二的博客-CSDN博客_c++this指针的用法

C++中this指针的理解和用法_小海歌哥的博客-CSDN博客_this指针的作用和用法

还有虚函数与虚表指针,可参考:

阿里云二面:C++ 虚表?

C++虚函数的调用过程_Currybeefer的博客-CSDN博客_c++虚函数调用

以及thiscall的函数调用方式,可参考:

_thiscall调用约定的简单概念_衣兜的博客-CSDN博客_thiscall调用约定

六. 函数调用约定与系统调用 - 知乎

还有最后这个强力推荐的文章,用C语言去详细描述了C++的封装继承和多态的特性:

看之前了解一下结构体指针的强转:计算结构体的大小 - 小乖不乖 - 博客园

struct s1 {
    int a = 1;
    int b = 0;
};
struct s2 {
    long long c = 0;
};
int main()//int argc,char** argv
{
    s1 ss;;
    s2* sss = (s2*)&ss;
    cout << sss->c << endl;//会打印1
}

参考链接:C语言的高级用法,面向对象

1)类的所有对象共享同一个成员函数的地址空间,而每个对象有独立的成员变量地址空间,可以说成员函数是类拥有的,成员变量是对象拥有的;不过如果类里有虚函数会多出一个虚函数表指针。

class vir {
public:
    int i;//占据一个数据成员
    int size = sizeof(*this);//占据一个数据成员
    void q() { cout << "q()" << endl; };//普通函数不占据
    virtual void q1() { cout << "vir" << endl; };//有虚函数,占据一个虚函数表指针
    virtual void q2() {};
    vir() {//构造函数不占据
        
    }
};
int main()//int argc,char** argv
{
    virson vs;
    cout << vs.size << endl;//最终打印为12字节
    return 0;
}

2)类的成员函数的默认形参中其实还隐含了一个this指针。

class B {
public:
    void p() { cout << "p()" << endl; };//其实是void p(B* this)
    virtual void vp() { cout << "vp()" << endl; };//其实是void vp(B* this)
};
int main()
{
    ((B*) nullptr)->p();//可正常打印,因为该函数中没有使用到成员变量,即使this为空指针也能允许
    ((B*) nullptr)->vp();//报错,因为使用虚函数先要从this指向的对象空间中索引出虚表指针
}

3)在构造函数中调用虚函数

最好不要这样干,但并非不行。

class vir {
public:
    int i;//占据一个数据成员
    int size = sizeof(*this);
    void q() { cout << "vir" << endl; };
    virtual void q1() { cout << "vir" << endl<<size<<endl; };
    virtual void q2() {};
    vir() {
        q1();//打印vir
    }
};
 class virson:public vir {
public:
   virtual void q1() { cout << "virson" << endl; };
    //virtual void q2() {};
    virson() {
        q1();//打印vson
    }
};
int main()
{
    vir* v = new virson();//vir类型的指针所允许的调用权限仅为vir中拥有的,具有截断性

}

上面可以说明在执行构造函数作用域之前就已经为this开辟好空间并进行初始化了,并且this指针指向的是new出来的类型的空间。

这说明那个时候this指向的空间中的虚函数表指针已经指向virson对应的虚函数表了。

进一步而参考:c++ 构造函数中调用虚函数的实现方法


3.虚析构函数与内存泄漏

析构函数没用好,很可能会造成内存泄漏。通过上面的分析能比较好地清楚为啥有的析构函数是要用虚函数了。可参考:

虚函数、虚析构函数的缺点 - CLive Studio - 博客园

C++中虚析构函数的作用及其原理分析_逐鹿之城的博客-CSDN博客_虚析构函数的作用

对于虚析构函数的理解_xld_hung的博客-CSDN博客_虚析构函数

大致总结一下:如果析构函数不加virtual,那么会按照普通成员函数的调用方式,即指针类型去索引到该函数,这样的话就会调用到基类的析构函数。但是当加上virtual之后,其是要通过指针对应的数据类型去里面调用虚函数表指针索引到相应的函数,这时候才会调用派生类所对应的析构函数了。而执行了派生类的析构函数会自动再去调用基类的析构函数,因此在基类中采用虚析构函数,在delete一个指向派生类的基类指针时会执行派生类跟基类的析构函数。

下面举个例子说明会内存泄漏的情况:

class vir {
public:
    int size = sizeof(*this);
    int i;
    
    void q() { cout << "q()" << endl; };
    virtual void q1() { cout << "vir" << endl<<size<<endl; };//有虚函数,占据一个虚函数表指针
    vir() {//构造函数不占据
        q1();
    }
     virtual ~vir() { 
         cout << "virdelete" << endl;
         q1();
         cout << i << endl;//能正常打印,i=90,说明执行virson的时候并没有把vir中的部分删除
     }
};
 class virson:public vir {
public:
   virtual void q1() {
   cout << "virson" << endl;
   };
   int* s = new int(3);
    virson() {
        
       // q1();
    }
    virtual ~virson() {
        q1();
        delete s;//如果不调用virson的析构函数,s指向的空间将变成野空间
        i = 90;
    }
};
int main()
{
    vir* vs = new virson;
    delete vs;
}

其他可参考:不用虚析构函数也不会造成内存泄漏的原因是什么?--CSDN问答

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值