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 <stdint.h>  
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 处理不了的。
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; 自动转换为指针和自动解引用
//就是我们呢可以把引用看成对变量的一种标签,同一个变量值不通过名字
4、堆和栈的区别
  • 申请方式不同。
    • 栈由系统自动分配。
    • 堆是自己申请和释放的。
  • 申请大小限制不同。
    • 栈顶(esp)和栈底(ebp)是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a查看,由ulimit -s修改。
    • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
  • 申请效率不同。
    • 栈由系统分配,速度快,不会有碎片。
    • 堆由程序员分配,速度慢,且会有碎片
  • 栈空间默认是4M, 堆区一般是 1G - 4G
    在这里插入图片描述
  • 栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
  • 堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大
5、区别以下指针类型?
int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • 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));//编译无错误
    
    • new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用。
    • 后者需要库文件支持(#include <stdio.h>),前者不用
    • 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、宏定义和函数有何区别?
  • 宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。
  • 宏定义属于在结构中插入代码,没有返回值;函数调用具有返回值。
  • 宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
  • 宏定义不要在最后加分号。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值