led的网站建设,做网站需要准备哪些东西,东莞网站推广衣裙,郑州经纬网络做网站吗一、养成内存管理好习惯 1.1 养成动态对象创建、调用及释放好习惯 开发者手动接管内存分配时#xff0c;必须处理这两个任务。分配原始内存时#xff0c;必须在该内存中构造对象#xff1b;在释放该内存之前#xff0c;必须保证适当地撤销这些对象。如果你的项目是c项目必须处理这两个任务。分配原始内存时必须在该内存中构造对象在释放该内存之前必须保证适当地撤销这些对象。如果你的项目是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(NULLpi_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!(p1i1))//使用前先判断std::cout *(p1i1) *(p1i1) \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!(pc1)) //使用前先判断*(pc1) 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::vectorstd::string svec(10);std::vectorstd::string *pvec1 new std::vectorstd::string(10);std::vectorstd::string *pv1 svec;std::vectorstd::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(NULLpi_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(NULLpi_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_ptrint 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 i0; i10; i) //赋值了11次{vec[i] i;}for(int i0; i10; 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 i0; i10; i){vec[i] i;}for(int i0; i20; 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 i0; i30; i)//调整30{vec[i] i;}for(int i0; i20; 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 i0; i20; i) //越界不是事for(int i0; i10; i) //请遵循{vec[i] i;}//for(int i0; i20; i) //越界不是事for(int i0; i10; 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_castBClass*(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值的大小将不产生任何效果。 因此上面结构体对齐模数是8c1长度1偏移为0s长度2偏移取1时不能构成长度2的整数倍偏移所以取偏移2c2长度1紧接着s取得偏移4i长度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)n1,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。数据成员最大长度是4int型而对齐模数是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.hvoid func(int *pi)
{assert(NULL!pi); //函数的入口校使用前先判断//do somethingstd::cout *pi *pi \n;int *pi_new new int(100);if(NULLpi_new) return; //非参数的地方使用//do something
}void func1(void)
{try{int *pi_new new int(100);if(NULLpi_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(NULLpi_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_ptrint 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!(p1i1))std::cout *(p1i1) *(p1i1) \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!(pc1))*(pc1) 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::vectorstd::string svec(10);std::vectorstd::string *pvec1 new std::vectorstd::string(10);std::vectorstd::string *pv1 svec;std::vectorstd::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 i0; i20; i) //越界不是事for(int i0; i10; i) //请遵循{vec[i] i;}//for(int i0; i20; i) //越界不是事for(int i0; i10; 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_castBClass*(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;
}