• 1-10=c++知识点


    1、在main执行之前和之后执行的代码可能是什么?
    main函数在执行前,主要就是初始化系统相关资源:

    • 设置栈指针
    • 初始化静态static变量和global全局变量,即.data段的内容
    • 将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL等等,即.bss段的内容
    • 全局对象初始化,在main之前调用构造函数,这是可能会执行前的一些代码
    • 将main函数的参数argc,argv等传递给main函数,然后才真正运行main函数
    • __attribute__((constructor))

    main函数执之后:

    • 全局对象的析构函数会在main函数之后执行
    • 可以用atexit注册一个函数,它会在main之后执行
    • attribute((constructor))

    2、结构体内存对齐问题?

    • 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
    • 未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)
    • c++11以后引入两个关键字alignas与alignof。其中alignof可计算出类型的对齐方式,alignas可以指定结构体的对齐方式。
    //但是alignas在某些情况下是不能使用的,具体见下面的例子://
    #include   
    using namespace std;
    /*
    char	1B=8bit  = -2^8 ~ 2^8-1
    int     4B=32bit = -2^31 ~ 2^31-1
    long long 	8B 
    float		4B
    double  	8B
    */
    typedef unsigned char uint8_t 
    typedef unsigned short int uint16_t 
    typedef unsigned int uint32_t 
    //alignas生效情况
    struct Info{
    	uint8_t a;//unsigned char
    	uint16_t b;//unsigned short int
    	uint8_t c;//unsigned int
    };
    cout<<sizeof(Info)<<endl;
    cout<<alignof(Info)<<endl;//类型对齐alignof
    struct alignas(4)Info2{//结构体对齐alignas
    	uint8_t a;
    	uint16_t b;
    	uint8_t c;
    };
    cout<<sizeof(Info2)<<endl;//8 4+4
    cout<<alignof(Info2)<<endl;//4
    
    //alignas将内存对齐调整为4个字节。所以sizeof(Info2)的值变为了8//
    //alignas失效的情况
    struct Info{
    	uint8_t a;
    	uint32_t b;
    	uint8_t c;
    };
    cout<<sizeof(Info2)<<endl;//12 4+4+4
    cout<<alignof(Info2)<<endl;//4(类型对齐)
    
    //若alignas小于自然对齐的最小单位,则被忽略//如alignas(1),默认uint16_t还是2
    //如果想使用单字节对齐的方式,使用alignas是无效的。应该使用#pragma pack(push,1)或者使用__attribute__((packed))
    //可以使用#pragma   pack(4) ,最后又想使用默认对齐方式时,可以使用#pragma pack() ;
    #if defined(__GNC__) || defined(__GNUC__)
    	#define ONEBYTE_ALIGN __attribute__((packed))
    #elif defined(_MSC_VER)
    	#define ONEBYTE_ALIGN
    	#define pack(push,1)
    #endif
    
    struct Info{
    	uint8_t a;
    	uint16_t b;
    	uint8_t c;
    }ONEBYTE_ALIGN;
    
    #if defined(__GNUC__) || defined(__GNUG__)
    	#undef ONEBYTE_ALIGN
    #elif defined(_MSC_VER)
    	#pragma pack(pop)
    	#undef ONEBYTE_ALIGN
    #endif
    
    cout<<sizeof(Info)<<endl;//6 1+4+1
    cout<<alignof(Info)<<endl;//6
    
    //确定结构体中每个元素大小可以通过下面这种方法
    /*#pragma pack(push) //保存对齐状态
      #pragma pack(4)//设定为4字节对齐
      相当于 #pragma  pack (push,4) */
    #if defined(__GNUC__) || defined(__GNUG__)
    	#define ONEBYTE_ALIGN __attribute__((packed))
    #elif defined(_MSC_VER)
    	#define ONEBYTE_ALIGN
    	#pragma pack(push,1)//#pragma  pack (push,1)作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为一个字节对齐
    #endif
    /**
    * 
    * 0 1   3     6   8 9           15
    * +-+---+-----+---+-+-------------+
    * | |   |     |   | |             |
    * |a| b | c | d |e|     pad     |
    * | |   |     |   | |             |
    * +-+---+-----+---+-+-------------+
    *
    */
    struct Info {
     uint16_t a : 1;
     uint16_t b : 2;
     uint16_t c : 3;
     uint16_t d : 2;
     uint16_t e : 1;
     uint16_t pad : 7;
    } ONEBYTE_ALIGN;
    
    #if defined(__GNUC__) || defined(__GNUG__)
    	#undef ONEBYTE_ALIGN
    #elif defined(_MSC_VER)
    	#pragma pack(pop)//#pragma pack(pop)作用:恢复对齐状态
    	#undef ONEBYTE_ALIGN
    #endif
    std::cout << sizeof(Info) << std::endl;   // 2
    std::cout << alignof(Info) << std::endl; // 1
    //这种处理方式是 alignas 处理不了的。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    3、指针和引用的区别

    • 指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是原变量的别名
    • 指针可以有多级,引用(相当于是原变量一种标签)只有一级
    • 指针可以为空,引用不能为NULL且在定义时必须初始化
    • 指针在初始化后可以改变指向,而引用在初始化之后不可再改变
    • sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小
    • 当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以。
    • 引用本质是一个指针,同样会占4字节内存;指针是具体变量,需要占用存储空间(,具体情况还要具体分析)。
    • 引用在声明时必须初始化为另一变量,一旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量
    • 引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用);指针变量可以重新指向别的变量。
    • 不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。
    void test(int* p)//为改变p的地址
    {
    	int a=1;
    	p=&a;
    	cout<<p<<" "<<*p<<endl;
    }
    void main(void)
    {
    	int* p=NULL;
    	test(p);
    	if(p==NULL)  cout<<"指针p为NULL"<<endl;'
    }
    
    //====================
    void testPTR(int* p)//函数内部栈申请的内存,用完即释放
    {
    	int a = 12;
    	p = &a;
    }
    void testREFF(int& p)
    {
    	int a = 12;
    	p = a;
    }
    void main(void)
    {
    	int a = 10;
    	int* b = &a;
    	testPTR(b);
    	cout << a << endl;// 10
    	cout << *b << endl;// 10
    
    	a = 10;
    	testREFF(a);
    	cout << a << endl;//12
    }
    //在编译器看来,int a=10;int &b=a;
    //等价于:int* const b=&a;//b的地址不可变
    //等价于 *b = 20; 自动转换为指针和自动解引用
    //就是我们呢可以把引用看成对变量的一种标签,同一个变量值不通过名字
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    4、堆和栈的区别

    • 申请方式不同。
      • 栈由系统自动分配。
      • 堆是自己申请和释放的。
    • 申请大小限制不同。
      • 栈顶(esp)和栈底(ebp)是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a查看,由ulimit -s修改。
      • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
    • 申请效率不同。
      • 栈由系统分配,速度快,不会有碎片。
      • 堆由程序员分配,速度慢,且会有碎片
    • 栈空间默认是4M, 堆区一般是 1G - 4G
      在这里插入图片描述
    • 栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
    • 堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大

    5、区别以下指针类型?

    int *p[10]
    int (*p)[10]
    int *p(int)
    int (*p)(int)
    
    • 1
    • 2
    • 3
    • 4
    • int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
    • int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
    • int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
    • int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

    6、基类的虚函数表存放在内存的什么区,虚表指针vptr的初始化时间

    • 首先整理一下虚函数表的特征:
      • 虚函数表是全局共享的元素,即全局仅有一个,在编译时就构造完成
      • 虚函数表类似一个数组,类对象中存储vptr指针,指向虚函数表(函数指针的数组),即虚函数表不是函数,不是程序代码,不可能存储在代码段
      • 虚函数表存储虚函数的地址,即虚函数表的元素是指向类成员函数的指针,而类中虚函数的个数在编译时期可以确定,即虚函数表的大小可以确定,即大小是在编译时期确定的,不必动态分配内存空间存储虚函数表,所以不在堆中
    • 根据以上特征,虚函数表类似于类中静态成员变量.静态成员变量也是全局共享,大小确定,因此最有可能存在全局数据区,测试结果显示:
    • 虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),这与微软的编译器将虚函数表存放在常量段存在一些差别
    • 由于虚表指针vptr跟虚函数密不可分,对于有虚函数或者继承于拥有虚函数的基类,对该类进行实例化时,在构造函数执行时会对虚表指针进行初始化,并且存在对象内存布局的最前面。
    • 一般分为五个区域:栈区、堆区、函数区(存放函数体等二进制代码)、全局静态区、常量区
    • C++中虚函数表位于只读数据段(.rodata),也就是C++内存模型中的常量区;而虚函数则位于代码段(.text),也就是C++内存模型中的代码区。
    • 简述虚函数表的原理c++虚函数表

    7、new / delete 与 malloc / free的异同

    • 相同点:
      • 都可用于内存的动态申请和释放
    • 不同点:
      • 前者是C++运算符,后者是C/C++语言标准库函数
      • new自动计算要分配的空间大小,malloc需要手工计算
      • new是类型安全的,malloc不是。例如
      int *p=new float[2];//error
      int *p=(int*)malloc(2*sizeof(double));//编译无错误
      
      • 1
      • 2
      • new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用。
      • 后者需要库文件支持(#include ),前者不用
      • new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象

    8、new和delete是如何实现的?

    • new的实现过程是:首先调用名为operator new的标准库函数,分配足够大的原始为类型化的内存,以保存指定类型的一个对象;接下来运行该类型的一个构造函数,用指定初始化构造对象;最后返回指向新分配并构造后的的对象的指针
    • delete的实现过程:对指针指向的对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存

    9、malloc和new的区别?

    • malloc和free是标准库函数,支持覆盖(函数覆盖);new和delete是运算符,并且支持重载。(运算符重载
    • malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。
    • malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。int* p=(int*)malloc(200*sizeof(int));
    • delete只会调用一次析构函数。delete[]会调用数组中每个元素的析构函数。

    10、宏定义和函数有何区别?

    • 宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。
    • 宏定义属于在结构中插入代码,没有返回值;函数调用具有返回值。
    • 宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
    • 宏定义不要在最后加分号。
  • 相关阅读:
    记一次JVM内存占用过高的优化经验
    Python----33个保留字列表、内置函数列表、datetime库 、random库、jieba库、math库
    影像组学特征提取代码错误
    深度学习:如何静悄悄地改变我们的日常生活
    基于LEAP模型的能源环境发展、碳排放建模预测及不确定性分析
    LeetCode111. Minimum Depth of Binary Tree
    Java Package用法:组织与管理类的利器
    tp5事务和加锁
    智云通CRM:理解客户是销售成功的开始?
    Python数据攻略-Pandas时间序列数据处理
  • 原文地址:https://blog.csdn.net/weixin_47397155/article/details/126247956