C/C++开发,无可避免的内存管理(篇二)-约束好跳脱的内存
一、养成内存管理好习惯
1.1 养成动态对象创建、调用及释放好习惯
开发者手动接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。如果你的项目是c++项目,就采用new和delelte操作符来管理动态对象,就尽量不要掺杂malloc和free函数行为;如果是c项目,就不要习惯性地又掺杂new和delelte操作符。
C++ 中,使用 new表达式的时候,分配内存,并在该内存中构造一个对象:使用 delete 表达式的时候,调用析构函数撤销对象,并将对象所用内存返还给系统。
在动态创建对象时,尽可能明确指定其数据类型,并及时初始化,采用new 表达式返回指向新创建对象的指针,明确指针指向内容大小,通过该指针即可来访问此对象。
int iv[2] = {1,2};//int *p1i = NULL; p1i = iv; //不建议分开int *p1i = iv; //建议一步到位//int *p2i = NULL; p2i = new int(2); //不建议分开int *p2i = new int(2); //建议一步到位//char* pc = NULL; pc =(char*)malloc(2);//不建议分开char* pc = (char*)malloc(2); //建议一步到位
对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删除现存对象,如果没有现存对象,赋值操作符中的动作就会有灾难性效果。
在使用动态对象前,养成习惯性判断对象是否合法的习惯,毕竟内存分配失败、对象已经本删除、对象没初始化都是会发生的事,判断语句表达式最好是null在前:
void func(int *pi)
{assert(NULL!=pi); //函数的入口校,使用前先判断//do somethingstd::cout << "*pi = " << *pi <<"\n";int *pi_new = new int(100);if(NULL==pi_new) return; //非参数的地方使用//do something
}int main(int argc, char* argv[])
{int iv[2] = {1,2};//int *p1i = NULL; p1i = iv; //不建议分开int *p1i = iv; //建议一步到位if(NULL!=p1i) //使用前先判断std::cout << "*p1i = " << *p1i <<"\n";if(NULL!=(p1i+1))//使用前先判断std::cout << "*(p1i+1) = " << *(p1i+1) <<"\n";//int *p2i = NULL; p2i = new int(2); //不建议分开int *p2i = new int(2); //建议一步到位if(NULL!=p2i) //使用前先判断std::cout << "*p2i = " << *p2i <<"\n";//char* pc = NULL; pc =(char*)malloc(2);//不建议分开char* pc = (char*)malloc(2); //建议一步到位if(NULL!=pc) //使用前先判断*pc= 'a'; if(NULL!=(pc+1)) //使用前先判断*(pc+1) = 'b';if(NULL!=pc) //使用前先判断std::cout << "*pc = " << *pc <<"\n";func(p1i);//other codereturn 0;
}
在释放动态对象时,习惯把指针重新指向NULL,防止野指针(悬垂指针)出现,野指针往往导致程序错误,而且很难检测出来:
int iv[2] = {1,2};
int *p1i = iv; //建议一步到位
//delete p1i; //error: p1i refers to a local val
//free(p1i); //error: p1i refers to a local val
p1i = NULL; //不再使用时,重新指向对象指针为NULLint *p2i = new int(2);
delete p2i; p2i = NULL;//删除动态对象时,立刻重指向对象指针为NULLchar *pc = (char*)malloc(2);
free(pc); pc = NULL; //释放动态对象时,立刻重指向对象指针为NULL
对于容器类指针也是如此,指向引用对象的指针不允许进行对象删除,只能管理好自己的赋值操作。
//std::cout << "vector test\n";std::vector<std::string> svec(10);std::vector<std::string> *pvec1 = new std::vector<std::string>(10);std::vector<std::string> *pv1 = &svec;std::vector<std::string> *pv2 = pvec1;delete pvec1;pvec1 = NULL;//delete pv1; //errorpv1 = NULL;//delete pv2; //errorpv2 = NULL;
另外删除null指针也是可以的,虽然这样做没什么意义:
int *ip = NULL;
delete ip; //OK
//ip = NULL;
1.2 控制好内存在各分支逻辑上的回收
警惕动态对象应用时,逻辑分支绕开动态对象释放,造成内存泄漏。例如下面示例:
void func1(void)
{try{int *pi_new = new int(100);if(NULL==pi_new) return; //非参数的地方使用//1 do something,这里出现异常,将跳转到2,后面的对象释放将被绕开,造成内存泄漏delete pi_new;pi_new = NULL;}catch(...){//2 do something}
};//建议
void func2(void)
{int *pi_new = new int(100);if(NULL==pi_new) return; //非参数的地方使用try{//1 do something}catch(...){//2 do something}if(NULL!=pi_new){delete pi_new;pi_new = NULL;}
};
如果编译器版本支持智能指针的话,为了避免逻辑分支引起动态对象内存释放失败,建议采用智能指针替代:
void func3(void)
{std::auto_ptr<int> pai(new int(100));//只要函数调用结束,就自动释放try{std::cout << "*pai = " << *pai << "\n";//1 do something}catch(...){//2 do something}
};
但要注意智能指针的使用,把握好智能指针为处理动态分配的内存提供了安全性和便利性的尺度。
- 不要使用 auto_ptr 对象保存指向静态分配对象的指针,否则,当 auto_ptr 对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
- 永远不要使用两个 auto_ptrs 对象指向同一对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者 reset 两个不同的 auto_ptr 对象。另一种导致这个错误的微妙方式可能是,使用一个 auto_ptr 对象的 get 函数的结果来初始化或者 reset另一个 auto_ptr 对象。
- 不要使用 auto_ptr 对象保存指向动态分配数组的指针。当auto_ptr 对象被删除的时候,它只释放一个对象—它使用普通delete 操作符,而不用数组的 delete [] 操作符。
- 不要将 auto_ptr 对象存储在容器中。容器要求所保存的类型定义复制和赋值操作符,使它们表现得类似于内置类型的操作符:在复制(或者赋值)之后,两个对象必须具有相同值,auto_ptr 类不满足这个要求。
1.3 确保为动态对象分配了合适的内存
很多时候,我们定义了指针变量,但是常忘了为指针分配内存,即使得指针没有指向一块合法的内存,尤其是在自定义结构体或类的成员变量包含指针类型时。
class AClass
{public://AClass() {}; //pc既没初始化了指针,也没有分配空间,pv没初始化//AClass() : pc(NULL){} //初始化指针,没有分配空间,pv已经分配了空间,但没有初始化AClass() : pc(NULL) { //初始化指针pc = new char[10]; //分配空间memset(pv,0,sizeof(pv));//初始化};~AClass() {if(NULL!=pc) //记得析构函数要手动释放动态对象{delete[] pc;//delete pc;//pc = NULL;}//pv自动释放内存}char *pc;char pv[10];
};//main函数内AClass atest;if(NULL!=atest.pc){strcpy(atest.pc,"hello");std::cout << "*(atest.pc) = " << std::string(atest.pc) <<"\n";}
另外即使给动态内存分配了内存,也要注意在应用时,内存空间是否足够。例如下面代码,为指针分配了内存10,但是应用需要13的内存,内存大小不够,导致出现越界错误,虽然由于指针指向特性及数组连续存储特性,这段代码没有报警,也能运行,但埋下了隐患。
char str_vec[] = "hello world!";std::cout << "sizeof(str_vec) = " << sizeof(str_vec) << "\n";if(NULL!=atest.pc){strncpy(atest.pc,str_vec,sizeof(str_vec));//越界了,也能完成拷贝//strcpy(atest.pc,str_vec); //strcpystd::cout << "*(atest.pc) = " << std::string(atest.pc) <<"\n";//输出也正常,但安全隐患埋下了}//if(NULL!=atest.pv){ //转换为指针操作strncpy(atest.pv,str_vec,sizeof(str_vec));//越界了,也能完成拷贝//strcpy(atest.pv,str_vec); //strcpystd::cout << "*(atest.pv) = " << std::string(atest.pv) <<"\n";//输出也正常,但安全隐患埋下了}
上述代码越界了,但是由于指针指向特性,并没有告警出现,下面这段代码,是否刷新您的想象。
//int vec[10] = {0};//内存分配成功,且已经初始化try{for(int i=0; i<=10; i++) //赋值了11次{vec[i] = i;}for(int i=0; i<=10; i++) //遍历了11次{std::cout << vec[i] << " ";}std::cout << "\n";}catch(...){std::cout << "throw!\n";}
//out
0 1 2 3 4 5 6 7 8 9 10
调整打印输出宽度,依然没有报错,那是因为编译器忽略为任何数组形参指定的长度,因此编译没有问题,但是这两个调用都是错误的,可能导致运行可能成功,可能失败,取决于系统的包容都,但数据没有了保证。再调整看看:
//int vec[10] = {0};try{for(int i=0; i<=10; i++){vec[i] = i;}for(int i=0; i<=20; i++) //调整了这里{std::cout << vec[i] << " ";}std::cout << "\n";}catch(...){std::cout << "throw!\n";}
//out log
0 1 2 3 4 5 6 7 8 9 10 1869376613 1919907616 2188396 8367176 1819043176 1870078063 560229490 0 2 6422176
再调整设值时的宽度
//int vec[10] = {0};try{for(int i=0; i<=30; i++)//调整30{vec[i] = i;}for(int i=0; i<=20; i++)//调整20{std::cout << vec[i] << " ";}std::cout << "\n";}catch(...){std::cout << "throw!\n";}
哦,linux下终于给我报错了:

而win10下依然运行的稳当着:

但是,不管系统或编译器如何包容,请遵循半闭半开的区间范围-[a,b)写法吧:
//int vec[10] = {0};try{//for(int i=0; i<=20; i++) //越界不是事for(int i=0; i<10; i++) //请遵循{vec[i] = i;}//for(int i=0; i<=20; i++) //越界不是事for(int i=0; i<10; i++){std::cout << vec[i] << " ";}std::cout << "\n";}catch(...){std::cout << "throw!\n";}
1.4 为动态对象做合适的初始化
通常,除了对其赋值之外,对未初始化的对象所关联的值的任何使用都是没有定义的。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。但建议创建动态对象时做值初始化。
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized
//建议
string *ps = new string(); // initialized to empty string
int *pi = new int(); // pi points to an int value-initialized to 0
class cls{};
cls *pc = new cls(); // pc points to a value-initialized object of type cls
显式表明通过在类型名后面使用一对内容为空的圆括号,对动态创建的对象做值初始化。内容为空的圆括号表示虽然要做初始化,但实际上并未提供特定的初值。对于提供了默认构造函数的类类型(例如 string),没有必要对其对象进行值初始化:无论程序是明确地不初始化还是要求进行值初始化,都会自动调用其默认构造函数初始化该对象。而对于内置类型或没有定义默认构造
函数的类型,采用不同初始化方式则有显著的差别:
int *pi = new int; // pi 指向int型变量,该变量未初始化
int *pi = new int(); // pi 指向int型变量,该变量初始化值为0
1.5 const对象动态分配
C++ 允许动态创建 const 对象:
//const int* pci = new const int(100);//int *p5i = pci; //errorint *p5i = (int*)(pci); //类型转换p5i = NULL;delete pci; //OK: deletes a const objectpci = NULL;
与其他常量一样,动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改。上述 new 表达式返回指向 int 型 const 对象的指针。与其他 const 对象的地址一样,由于 new 返回的地址上存放的是 const对象,因此该地址只能赋给指向 const 的指针。
尽管不能改变 const 对象的值,但可撤销对象本身。如同其他动态对象一样, const 动态对象也是使用删除指针来释放的.
delete pci; //OK: deletes a const objectpci = NULL;
对于类类型的 const 动态对象,如果该类提供了默认的构造函数,则此对象可隐式初始化。
//const std::string *ps_const = new const std::string;delete ps_const;ps_const = NULL;
new 表达式没有显式初始化 ps_const所指向的对象,而是隐式地将 ps_const所指向的对象初始化为空的 string 对象。内置类型对象或未提供默认构造函数的类类型对象必须显式初始化。
1.6 dynamic_cast向下转型时内存错误
dynamic_cast 支持运行时识别指针或引用所指向的对象,但是对于从父类转向子类时,稍有不慎就引擎内存错误:
//AClass见前文定义
class BClass : public AClass
{public:BClass(): AClass(),pbc(NULL){pbc = new char[10]; //分配10个字节空间};~BClass(){if(NULL!=pbc) //记得析构函数要手动释放动态对象{delete[] pbc;//delete pbc;//pbc = NULL;}//默认调用~AClass()}char *pbc;
};//main函数内
AClass *pa = new AClass();BClass *pb = dynamic_cast<BClass*>(pa);if(NULL!=pb->pbc){//error,本质上是pa,BClass构造函数没被调用,pbc没分配空间及初始化strcpy(pb->pbc,"hello");std::cout << "*(pb->pbc) = " << std::string(pb->pbc) <<"\n";}
因此强烈建议程序员避免使用强制类型转换,尤其要使用ynamic_cast和reinterpret_cast时,请三思再三思。当然还有旧式的强制类型转换也是如此。
1.7 千万不要返回局部对象的引用
前一篇博文就阐述了局部指针变量引起的内存问题,这探讨一下局部对象引用的问题:当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。
const string& mapStr(const string& s)
{string ret = s;ret += ":";return ret; // error: 返回局部对象的引用!
}
这个函数会在运行时出错,因为它返回了局部对象的引用。当函数执行完毕,字符串 ret 占用的储存空间被释放,函数返回值指向了对于这个程序来说不再有效的内存空间。确保返回引用有效的方法就是,和指针指向一样,确保引用指向在此之前存在的对象,而非局部变量。
二、 内存对齐
2.1 内存对齐是编译器的效率优化要求
内存对齐通常都是编译器的职责范围,但C/C++语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。缺省情况下,编译器默认将结构、栈中的成员数据进行内存对齐。每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。这个对齐模数要求可以是2^n[0,N)=1,2,4,8,16,32,64...中的一个。一般的编译器默认对齐模数是8。
对齐模数是8时,一个字或双字操作数跨越了 4 字节边界,或者一个四字操作数跨越了 8 字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨
越字边界被认为是对齐的,能够在一个总线周期中被访问。
为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。因为,为
了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。因此,编译器会将未对齐的成员向后移,将每一个都成员对齐到自然边界上,从而也导致了整个结构的尺寸变大。尽管会牺牲一点空间(成员之间有部分内存空闲),但提高了性能。
typedef struct Struct1
{char c1;short s;char c2;int i;
}Test1;//Test1 t1;std::cout << "sizeof(t1) = " << sizeof(t1) << "\n";//sizeof(t1) = 12
上述结构体的内存大小并不是各成员变量内存大小之和,而是12,这是因为编译器为了内存对齐给成员变量做了后移。下面这段代码打印各个成员变量与结构体首地址的偏移量:
//printf("c1 %p, s %p, c2 %p, i %p\n",(unsigned long int)(void*)&t1.c1 - (unsigned long int)(void*)&t1,(unsigned long int)(void*)&t1.s - (unsigned long int)(void*)&t1,(unsigned long int)(void*)&t1.c2 - (unsigned long int)(void*)&t1,(unsigned long int)(void*)&t1.i - (unsigned long int)(void*)&t1);
//out log
c1 00000000, s 00000002, c2 00000004, i 00000008
前面就说到,编译器将未对齐的成员向后移,将每一个都成员对齐到自然边界上,其对齐规则为:
1)首先是数据成员变量的对齐规则,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的对齐模数和这个数据成员自身长度中,比较小的那个进行,每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍。
2)然后是结构(或联合)的整体对齐规则,在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3)当#pragma pack的n值等于或超过所有数据成员变量长度的时候,这个n值的大小将不产生任何效果。
因此上面结构体,对齐模数是8,c1长度1,偏移为0;s长度2,偏移取1时,不能构成长度2的整数倍偏移,所以取偏移2;c2长度1,紧接着s取得偏移4;i长度4,不能紧接着c2取5,而是偏移8,为4的整数倍。整体对齐上,对齐模数是8,最大数据成员长度是4,按4整体对齐。他们的偏移布局如下(1为成员占位)。
t1:111111111111
------------------------------
c1:1*s: 11
c2: 1***i: 1111
如果调整一下成员变量的次序,其对齐发生了变化,内存大小也跟着被改变:
typedef struct Struct2
{char c1;char c2;short s;int i;
}Test2;//Test2 t2;std::cout << "sizeof(t2) = " << sizeof(t2) << "\n";//sizeof(t2) = 8//printf("c1 %p, c2 %p, s %p, i %p\n",(unsigned long int)(void*)&t2.c1 - (unsigned long int)(void*)&t2,(unsigned long int)(void*)&t2.c2 - (unsigned long int)(void*)&t2,(unsigned long int)(void*)&t2.s - (unsigned long int)(void*)&t2,(unsigned long int)(void*)&t2.i - (unsigned long int)(void*)&t2);
//out log
sizeof(t2) = 8
c1 00000000, c2 00000001, s 00000002, i 00000004
新的结构体中,每个成员都自然地对齐在自然边界上,避免了编译器自动对齐。其对齐如下:
t1:11111111
------------------------------
c1:1
c2: 1s: 11i: 1111
2.2 自行设置对齐模数
程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。而使用指令#pragma pack (),编译器将取消自定义字节对齐方式。在#pragma pack (n)和#pragma pack ()之间的代码按我们指定的 n 个字节对齐。
#pragma pack(2)typedef struct Struct3{char c1;short s;char c2;int i;}Test3;Test3 t3;std::cout << "sizeof(t3) = " << sizeof(t3) << "\n";//sizeof(t3) = 12//printf("c1 %p, s %p, c2 %p, i %p\n",(unsigned long int)(void*)&t3.c1 - (unsigned long int)(void*)&t3,(unsigned long int)(void*)&t3.s - (unsigned long int)(void*)&t3,(unsigned long int)(void*)&t3.c2 - (unsigned long int)(void*)&t3,(unsigned long int)(void*)&t3.i - (unsigned long int)(void*)&t3);#pragma pack()//out log
sizeof(t3) = 10
c1 00000000, s 00000002, c2 00000004, i 00000006
上述代码指定对齐模数为2,各个数据成员可以按2的整数倍偏移piany对齐,那么6也能作为自然对齐边界,当i对齐6时,结构体长度为10。数据成员最大长度是4(int型),而对齐模数是2,所以取2做对齐,10是2的整数倍,因此结构体对齐满足,所以综合得到整个结构体的内存大小是10。
三、本文源代码
g++ main.cpp -o test

main.cpp
#include <iostream>
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <memory>
#include <stdio.h>void func(int *pi)
{assert(NULL!=pi); //函数的入口校,使用前先判断//do somethingstd::cout << "*pi = " << *pi <<"\n";int *pi_new = new int(100);if(NULL==pi_new) return; //非参数的地方使用//do something
}void func1(void)
{try{int *pi_new = new int(100);if(NULL==pi_new) return; //非参数的地方使用//1 do something,这里出现异常,将跳转到2,后面的对象释放将被绕开,造成内存泄漏delete pi_new;pi_new = NULL;}catch(...){//2 do something}
};void func2(void)
{int *pi_new = new int(100);if(NULL==pi_new) return; //非参数的地方使用try{//1 do something,这里出现异常,将跳转到2,后面的对象释放将被绕开,造成内存泄漏}catch(...){//2 do something}if(NULL!=pi_new){delete pi_new;pi_new = NULL;}
};void func3(void)
{std::auto_ptr<int> pai(new int(100));//只要函数调用结束,就自动释放try{std::cout << "*pai = " << *pai << "\n";//1 do something}catch(...){//2 do something}
};class AClass
{public://AClass() {}; //pc既没初始化了指针,也没有分配空间,pv没初始化//AClass() : pc(NULL){}; //初始化指针,没有分配空间,pv已经分配了空间,但没有初始化AClass() :pc(NULL) { //pc = new char[10]; //分配10个字节空间memset(pv,0,sizeof(pv));//初始化};virtual ~AClass() {if(NULL!=pc) //记得析构函数要手动释放动态对象{delete[] pc;}//pv自动释放内存}char *pc;char pv[10];
};class BClass : public AClass
{public:BClass(): AClass(),pbc(NULL){pbc = new char[10]; //分配10个字节空间};~BClass(){if(NULL!=pbc) //记得析构函数要手动释放动态对象{delete[] pbc;}//默认调用~AClass()}char *pbc;
};
//内存对齐
typedef struct Struct1
{char c1;short s;char c2;int i;
}Test1;typedef struct Struct2
{char c1;char c2;short s;int i;
}Test2;int main(int argc, char* argv[])
{int iv[2] = {1,2};//int *p1i = NULL; p1i = iv; //不建议分开int *p1i = iv; //建议一步到位if(NULL!=p1i)std::cout << "*p1i = " << *p1i <<"\n";if(NULL!=(p1i+1))std::cout << "*(p1i+1) = " << *(p1i+1) <<"\n";//int *p2i = NULL; p2i = new int(2); //不建议分开int *p2i = new int(2); //建议一步到位if(NULL!=p2i)std::cout << "*p2i = " << *p2i <<"\n";//char* pc = NULL; pc =(char*)malloc(2);//不建议分开char* pc = (char*)malloc(2); //建议一步到位if(NULL!=pc)*pc= 'a'; if(NULL!=(pc+1))*(pc+1) = 'b';if(NULL!=pc)std::cout << "*pc = " << *pc <<"\n";func(p1i);func2();func3();//p1i = NULL; //重新指向对象指针为NULLdelete p2i; p2i = NULL; //删除动态对象时,立刻重指向对象指针为NULLfree(pc); pc = NULL; //释放动态对象时,立刻重指向对象指针为NULL//std::cout << "vector test\n";std::vector<std::string> svec(10);std::vector<std::string> *pvec1 = new std::vector<std::string>(10);std::vector<std::string> *pv1 = &svec;std::vector<std::string> *pv2 = pvec1;delete pvec1;pvec1 = NULL;//delete pv1; //errorpv1 = NULL;//delete pv2; //errorpv2 = NULL;//AClass atest;if(NULL!=atest.pc){strcpy(atest.pc,"hello");std::cout << "*(atest.pc) = " << std::string(atest.pc) <<"\n";}char str_vec[] = "hello world!";std::cout << "sizeof(str_vec) = " << sizeof(str_vec) << "\n";if(NULL!=atest.pc){strncpy(atest.pc,str_vec,sizeof(str_vec));//越界了,也能完成拷贝//strcpy(atest.pc,str_vec); //strcpystd::cout << "*(atest.pc) = " << std::string(atest.pc) <<"\n";//输出也正常,但安全隐患埋下了}//if(NULL!=atest.pv){strncpy(atest.pv,str_vec,sizeof(str_vec));//越界了,也能完成拷贝//strcpy(atest.pv,str_vec); //strcpystd::cout << "*(atest.pv) = " << std::string(atest.pv) <<"\n";//输出也正常,但安全隐患埋下了}//int vec[10] = {0};try{//for(int i=0; i<=20; i++) //越界不是事for(int i=0; i<10; i++) //请遵循{vec[i] = i;}//for(int i=0; i<=20; i++) //越界不是事for(int i=0; i<10; i++){std::cout << vec[i] << " ";}std::cout << "\n";}catch(...){std::cout << "throw!\n";}//std::cout << "const ptr test\n";const int* pci = new const int(100);//int *p5i = pci; //errorint *p5i = (int*)(pci); //类型转换p5i = NULL;delete pci; // ok: deletes a const objectpci = NULL;//const std::string *ps_const = new const std::string;delete ps_const;ps_const = NULL;AClass *pa = new AClass();BClass *pb = dynamic_cast<BClass*>(pa);/*if(NULL!=pb->pbc){ //errorstrcpy(pb->pbc,"hello");std::cout << "*(pb->pbc) = " << std::string(pb->pbc) <<"\n";}*/Test1 t1;std::cout << "sizeof(t1) = " << sizeof(t1) << "\n";//sizeof(t1) = 12//printf("c1 %p, s %p, c2 %p, i %p\n",(unsigned long int)(void*)&t1.c1 - (unsigned long int)(void*)&t1,(unsigned long int)(void*)&t1.s - (unsigned long int)(void*)&t1,(unsigned long int)(void*)&t1.c2 - (unsigned long int)(void*)&t1,(unsigned long int)(void*)&t1.i - (unsigned long int)(void*)&t1);//Test2 t2;std::cout << "sizeof(t2) = " << sizeof(t2) << "\n";//sizeof(t2) = 8//printf("c1 %p, c2 %p, s %p, i %p\n",(unsigned long int)(void*)&t2.c1 - (unsigned long int)(void*)&t2,(unsigned long int)(void*)&t2.c2 - (unsigned long int)(void*)&t2,(unsigned long int)(void*)&t2.s - (unsigned long int)(void*)&t2,(unsigned long int)(void*)&t2.i - (unsigned long int)(void*)&t2);#pragma pack(2)typedef struct Struct3{char c1;short s;char c2;int i;}Test3;Test3 t3;std::cout << "sizeof(t3) = " << sizeof(t3) << "\n";//sizeof(t3) = 12//printf("c1 %p, s %p, c2 %p, i %p\n",(unsigned long int)(void*)&t3.c1 - (unsigned long int)(void*)&t3,(unsigned long int)(void*)&t3.s - (unsigned long int)(void*)&t3,(unsigned long int)(void*)&t3.c2 - (unsigned long int)(void*)&t3,(unsigned long int)(void*)&t3.i - (unsigned long int)(void*)&t3);#pragma pack()return 0;
}
相关文章:
C/C++开发,无可避免的内存管理(篇二)-约束好跳脱的内存
一、养成内存管理好习惯 1.1 养成动态对象创建、调用及释放好习惯 开发者手动接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。如果你的项目是c项目&am…...
【Java】让我们对多态有深入的了解(九)
目录 (1)接口的基本介绍编辑 (2)接口的注意事项和细节 1.接口不能被实例化 2.接口中所有方法是public方法,接口中的抽象方法,可以不用abstract修饰 3.一个普通类实现接口,必须将接口所有…...
12 个适合做外包项目的开源后台管理系统
1.D2admin 开源地址:https://github.com/d2-projects/d2-admin 文档地址:https://d2.pub/zh/doc/d2-admin/ 效果预览:https://d2.pub/d2-admin/preview/#/index 开源协议:MIT 2.vue-element-admin 开源地址:https…...
鼠标更换指针图案和更改typora的主题
鼠标更换指针图案 由此偶然看见好几个朋友都使用了新的图案替换掉了原有的鼠标图案,今天寻思自己也换一个图案 主要是觉得鼠标大一点儿会好看一些,所以就找了一些教程 官方教程,小的变动 当然最多的是官方教程,如果你只是想要…...
【洛谷 P1563】[NOIP2016 提高组] 玩具谜题(模拟+结构体数组+指针)
[NOIP2016 提高组] 玩具谜题 题目背景 NOIP2016 提高组 D1T1 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业。 有一天, 这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图: 这时 singer 告诉小南一个谜…...
阿里测试经验7年,从功能测试到自动化测试,我整理的超全学习指南
做测试七年多,有不少人问过我下面问题: 现在的手工测试真的不行了吗? 测试工程师,三年多快四年的经验,入门自动化测试需要多久? 自学自动化测试到底需要学哪些东西? 不得不说,随着…...
Educational Codeforces Round 143 (Rated for Div. 2)
Educational Codeforces Round 143 (Rated for Div. 2) D. Triangle Coloring 思路: 每个环都需要取最大值,那么我们讨论一个环获得最大值选的两条边的可能取法: 显然:如果三边相等,这个环有3种取法。如…...
业务代码编写过程中如何「优雅的」配置隔离
思考 不同的处理方式 1.常规的处理方式,通过某种规则判断区分代码环境 // 获取环境标识 const env getCurrentEnv();if (env dev) {// do something } else if (env test) {// do something } else if (env prod) {// do something } 分析: 1.此种…...
English Learning - L2-2 英音地道语音语调 2023.02.23 周四
English Learning - L2-2 英音地道语音语调 2023.02.23 周四查音标的工具怎么练习效果好准备工作大小声练习大元音开口度的对比舌位对比复习后元音 /ɑː/ /ɔː/ /uː//ɑː//ɔː//uː/前元音 /iː/发音技巧对应单词的发音对应句子的发音常见的字母组合中元音 /ɜː/发音技巧…...
java:线程等待与唤醒 - Object的wait()和notify()
java:线程等待与唤醒 - Object的wait()和notify() 1 前言 java使用Object类的wait()和notify()方法,可以实现线程等待和唤醒(Object类为所有类的父类,即所有类天然具有线程等待和唤醒的方法,一般使用Object类的wait(…...
实现弹窗功能并修改其中一个系数
把鼠标放在number-info上面,会是一个delon/chart的类库,可以在NG-ALAIN上找到阅读NG ALAIN的图表,以及number-info样式,数据文本 它拥有[title] [subtitle]两个可以是TemplateRef类型的,而template可以在里面放一些东西,比如按钮,所以可以放一个修改按钮 这里刚开始把template放…...
vue-draggable浏览器拖拽event事件对象拖动时 DragEvent path undefined
场景: 在做组件拖拽过程中,需要获取到触发元素冒泡过程中的所有元素,所以使用了event.path属性。在Chrome下正常运行,但是在FireFox下测试时发现,完犊子,失效了,通过问题排查,发现了…...
【云原生】搭建k8s高可用集群—20230225
文章目录多master(高可用)介绍高可用集群使用技术介绍搭建高可用k8s集群步骤1. 准备环境-系统初始化2. 在所有master节点上部署keepalived3.1 安装相关包3.2 配置master节点3.3 部署haproxy错误解决3. 所有节点安装Docker/kubeadm/kubelet4. 部署Kuberne…...
LeetCode121_121. 买卖股票的最佳时机
LeetCode121_121. 买卖股票的最佳时机 一、描述 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最…...
收割不易,五面Alibaba终拿Java岗offer
前言 前段时间有幸被阿里的一位同学内推,参加了阿里巴巴Java岗位的面试,本人19年双非本科软件工程专业,目前有一年半的工作经验,面试前就职于一家外包公司。如果在自己本人拿到offer之前,如果有人告诉我一年工作经验可…...
【离线数仓-4-数据仓库设计-分层规划构建流程】
离线数仓-4-数据仓库设计-分层规划&构建流程离线数仓-4-数据仓库设计-分层规划&构建流程1.数据仓库分层规划2.数据仓库构建流程1.数据调研1.业务调研2.需求分析3.总结2.明确数据域3.构建业务总线矩阵&维度模型设计4.明确统计指标1.指标体系相关概念1.原子指标2.派生…...
SQL零基础入门学习(十一)
SQL零基础入门学习(十) SQL NOT NULL 约束 NOT NULL 约束强制列不接受 NULL 值。 NOT NULL 约束强制字段始终包含值。这意味着,如果不向字段添加值,就无法插入新记录或者更新记录。 下面的 SQL 强制 “ID” 列、 “LastName” …...
排序基础之插入排序
目录 前言 一、什么是插入排序 二、实现插入排序 三、插入排序优化 四、插入排序的特性 前言 上一篇中我们说到了《排序基础之选择排序》,这一篇我们来学习一下排序算法中的另一种基础排序算法——插入排序。 一、什么是插入排序 简单来说就是:每…...
LabVIEW控制DO通道输出一个精确定时的数字波形
LabVIEW控制DO通道输出一个精确定时的数字波形如何使用数据采集板卡的DO通道输出一个精确定时的数字波形?解答:产生一个数字波形首先需要创建一个布尔数组,把波形序列信息放到该布尔数组中,然后通过一个布尔数组至数字转换vi来产生数字波形。…...
openpnp - 零碎记录
文章目录openpnp - 零碎记录概述笔记配置文件保存无效必须在查找问题之后, 才能保存配置文件如果想找出配置动作引起的配置内容变化, 还是要尝试保存后, 比对变化才行ENDopenpnp - 零碎记录 概述 这段时间, 正在配置校准手头的openpnp设备, 用的官网最新的openpnp2.0. 由于o…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
