1. The function reserve and resize for vector:
int a[] = {1,2,3,4,5};
vector<int> vec(a, a+5);
//vec.resize(3); //Drop the numbers out of range, capacity = 5, size = 3
//printf("%d\n",vec[4]); //Out of range error
//vec.resize(6); //Size to 6,Capacity = 6
vec.reserve(3); //Nothing to do
vec.reserve(6); //capacity = 6, size = 5
printf("%d", vec[2]);
2. Strcpy与memcpy两函数的经典实现,哈哈,不过,不是偶写的!^_^………… 转自: http://blog.sina.com.cn/s/blog_4d3a41f40100cvz8.html
char *strcpy(char *des, const char *src){
return add;
}
void
}
已知strcpy函数的原型是
char* strcpy(char* strDest,const char* strSrc);
1.不调用库函数,实现strcpy函数
2.解释为什么要返回char*;
1.strcpy的实现代码
char* strcpy(char* strDest,const char* strSrc)
{
}
[1]
(A)不检查指针的有效性,说明设计者不注重程序的健壮性
(B)检查指针的有效性时使用((!strDest) || (!strSrc))或(!(strDest && strSrc)),说明对C语言中
类型隐式转换没有深刻认识,在本例中char*转换为bool即是类型隐式转换,这种功能虽然灵活,但
是更多的是导致出错概率的增大和维护成本的增高
(C)检查指针的有效性时使用((strDest==0)||(strSrc==0))说明答题者不知道使用常量的好处。直接使用字面常量(如本例中的0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错误,很难排除。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。
[2]
(A)return new string("Invalid arguments");,说明答题者根本不知道返回值得用途,并且它对内存泄露也没有警惕心,从函数体中返回函数体内分配的内存是十分危险的做法,他把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄露
(B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。
[3]
(A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。
[4]
(A)循环写成while (*strDest =*strSrc );,同[1](B)。
(B)循环写成while (*strSrc!='\0') *strDest =*strSrc ;,说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'\0'。
2.返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。链式表达式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是错误的。其一,源字符串肯定是已知的,返回它没有意义。其二,不能支持形如第二例的表达式。其三,为了保护源字符串,形参用const限定strSrc所指的内容,把const char *作为char *返回,类型不符,编译报错。
strcpy和memcpy主要有以下3方面的区别。
复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
复制的方法不同。strcpy不需要指定长度,它遇到字符串结束符"\0"便结束。memcpy则是根据其第3个参数决定复制的长度。
用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
memcpy 中的destination可能是new或者malloc生成的,因此一定要对new和malloc是否成功执行做出检查:
3. new和malloc的检查:以下转自: http://blog.youkuaiyun.com/arcsinsin/article/details/12676087
一、C语言中的malloc/calloc/realloc/valloc/alloca/memalign函数:
这样的内存分配函数在内存分配失败时都返回空指针,因此,在调用返回时,检查返回值的方法比较简单,只需要与空指针比较即可;
如:
char* p = (char*)malloc(1204);
if(p == NULL)
{
//error handle;
};
char* pp = (char*)calloc(3,1024);
if(pp == NULL)
{
//error handle;
};
二、C++中的new操作符:
C++中的new操作符在分配内存失败时默认的操作是抛出一个内置的异常,而并不是直接返回空指针;这样的话,再把返回值与空指针比较,就没有什么意义了;因为,C++抛出异常之后,就直接跳出new操作符所在的那一行代码,而不再执行后续的代码行了,所以,对new操作符返回值的判断代码就执行不到了;当然,标准C++也提供了抑制抛出异常的方法,使之不再排除内存分配失败的异常,转而直接返回空指针,这是因为比较古老的编译器里面可能没有异常处理机制,不能捕获到异常;如:
int* p = new int[SIZE];
if(p == 0) //检查p是否是空指针;这个判断没有意义;
{
return -1;
}
所以,在C++中有两种方法来处理new操作符分配内存失败的错误;
1、通过捕获new操作符抛出的异常:
char* p = NULL;
try
{
p = new char[1024];
}
catch(const std::bad_alloc& ex)
{
//exception handle;
return -1;
}
2、抑制异常的抛出:
char* p = NULL;
p = new(std::nothrow)char[1024]; //这样的话,如果new分配内存失败,就不会再抛出异常,而是返回空指针了;
if(p == NULL) //这样的判断就有意义了;
{
//error handle;
return -1;
}
三、执行free或delete释放内存之后,还需要做什么?
被free()掉的指针,我们通常叫野指针,野指针是非常危险的哦,可能导致多次释放内存或者对野指针进行访问,
这两种情况都可能导致严重的安全风险。
最好的也是最简单的解决办法就是在释放后,把指针设置为NULL或者指向另一个合法的对象。
就像这样:
free(ptr);
ptr = NULL;
- #include<iostream>
- using namespace std;
- int main()
- {
- int *p = new int[5];
- //_________使用new没有必要检查内存分配失败
- delete p;
- //此处为了验证释放p指针指向的内存之后,指针是否为NULL。答案是不会输出Yes
- if (p == NULL)
- cout<<"Yes"<<endl;
- //为了避免“野指针”,必须给指针p重新赋值。此处置为NULL
- p = NULL;
- int *pp = (int *)malloc(sizeof(int)*5);
- //使用malloc有必要检测
- if (pp == NULL)
- {
- cout<<"error"<<endl;
- }
- free(pp);
- pp = NULL;
- return 0;
- }
相同点:都可用于申请动态内存和释放内存
不同点:
(1)操作对象有所不同。
malloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符。对于非内部数据类的对象而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数, 对象消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free。
(2)用法上也有所不同。
函数malloc 的原型如下:
void * malloc(size_t size);
用malloc 申请一块长度为length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。
1、malloc 返回值的类型是void *,所以在调用malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
2、 malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
函数free 的原型如下:
void free( void * memblock );
为什么free 函数不象malloc 函数那样复杂呢?这是因为指针p 的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p 是NULL 指针,那么free
对p 无论操作多少次都不会出问题。如果p 不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误。
new/delete 的使用要点:
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
这是因为new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new 的语句也可以有多种形式。
如果用new 创建对象数组,那么只能使用对象的无参数构造函数。例如
Obj *objects = new Obj[100]; // 创建100 个动态对象
不能写成
Obj *objects = new Obj[100](1); // 创建100 个动态对象的同时赋初值1
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法
后者相当于delete objects[0],漏掉了另外99 个对象。
/
1、new自动计算需要分配的空间,而malloc需要手工计算字节数
2、new是类型安全的,而malloc不是,比如:
int* p = new float[2]; // 编译时指出错误
int* p = malloc(2*sizeof(float)); // 编译时无法指出错误
new operator 由两步构成,分别是 operator new 和 construct
3、operator new对应于malloc,但operator new可以重载,可以自定义内存分配策略,甚至不做内存分配,甚至分配到非内存设备上。而malloc无能为力
4、new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
5、malloc/free要库文件支持,new/delete则不要。
/
1、本质区别
malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。
对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。
- class Obj
- {
- public:
- Obj( )
- { cout << "Initialization" << endl; }
- ~ Obj( )
- { cout << "Destroy" << endl; }
- void Initialize( )
- { cout << "Initialization" << endl; }
- void Destroy( )
- { cout << "Destroy" << endl; }
- }obj;
- void UseMallocFree( )
- {
- Obj * a = (Obj *) malloc( sizeof ( obj ) ); // allocate memory
- a -> Initialize(); // initialization
- // …
- a -> Destroy(); // deconstruction
- free(a); // release memory
- }
- void UseNewDelete( void )
- {
- Obj * a = new Obj;
- // …
- delete a;
- }
类Obj的函数Initialize实现了构造函数的功能,函数Destroy实现了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成“构造”与“析构”。所以我们不要用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。
2、联系
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete、malloc/free必须配对使用。
From http://www.cppblog.com/w57w57w57/archive/2011/08/09/152845.html
摘要:
Sizeof的作用非常简单:求对象或者类型的大小。然而sizeof又非常复杂,它涉及到很多特殊情况,本篇把这些情况分门别类,总结出了sizeof的10个特性:
(0)sizeof是运算符,不是函数;
(1)sizeof不能求得void类型的长度;
(2)sizeof能求得void类型的指针的长度;
(3)sizeof能求得静态分配内存的数组的长度!
(4)sizeof不能求得动态分配的内存的大小!
(5)sizeof不能对不完整的数组求长度;
(6)当表达式作为sizeof的操作数时,它返回表达式的计算结果的类型大小,但是它不对表达式求值!
(7)sizeof可以对函数调用求大小,并且求得的大小等于返回类型的大小,但是不执行函数体!
(8)sizeof求得的结构体(及其对象)的大小并不等于各个数据成员对象的大小之和!
(9)sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小!
概述:
Sizeof是C/C++中的关键字,它是一个运算符,其作用是取得一个对象(数据类型或者数据对象)的长度(即占用内存的大小,以byte为单位)。其中类型包含基本数据类型(不包括void)、用户自定义类型(结构体、类)、函数类型。数据对象是指用前面提到的类型定义的普通变量和指针变量(包含void指针)。不同类型的数据的大小在不同的平台下有所区别,但是c标准规定所有编译平台都应该保证sizeof(char)等于1。关于sizeof的更多概述你可以在msdn总输入sizeof进行查询。
看了上面这些,或许你看了没有多少感觉。没关系,下面我将详细列出sizeof的诸多特性,这些特性是造成sizeof是一个较刁钻的关键字的原因:
十大特性:
特性0:sizeof是运算符,不是函数
这个特性是sizeof的最基本特性,后面的很多特性都是受到这个特性的影响,正因为sizeof不是函数,因此我们不把它所要求得长度的对象叫做参数,我本人习惯上叫做操作数(这不严谨,但是有助于我记住sizeof是个操作符)。
特性1:sizeof不能求得void类型的长度
是的,你不能用sizeof(void),这将导致编译错误:illegal sizeof operand。事实上你根本就无法声明void类型的变量,不信你就试试void a;这样的语句,编译器同样会报错:illegal use of type 'void'。或许你要问为什么,很好,学东西不能只知其然,还要知其所以然。我们知道声明变量的一个重要作用就是告诉编译器该变量需要多少存储空间。然而,void是“空类型”,什么是空类型呢,你可以理解成不知道存储空间大小的类型。既然编译器无法确定void类型的变量的存储大小,那么它自然不让你声明这样的变量。当然了,声明void类型的指针是可以的!这就是特性2的内容。
特性2:sizeof能求得void类型的指针的长度
在特性1中说过,可以申明void类型的指针,也就是说编译器可以确定void类型的指针所占用的存储空间。事实上确实如此,目前,几乎所有平台上的所有版本的编译器都把指针的大小看做4byte,不信你试试sizeof(int*);sizeof(void*);sizeof(double*);sizeof(Person*);等等,它们都等于4!为什么呢?问得好,我将尽全力对此作出解释:其实指针也是变量,只不过这个变量很特殊,它是存放其他变量的地址的变量。又由于目前32位计算机平台上的程序段的寻址范围都是4GB,寻址的最小单元是byte,4GB等于232Byte,这么多的内存其地址如果编码呢,只需要用32个bit就行了,而32bit = 32/8 = 4byte,也就是说只需要4byte就能存储这些内存的地址了。因此对任何类型的指针变量进行sizeof运算其结果就是4!
特性3:sizeof能求得静态分配内存的数组的长度!
Int a[10];int n = sizeof(a);假设sizeof(int)等于4,则n= 10*4=40;特别要注意:char ch[]=”abc”;sizeof(ch);结果为4,注意字符串数组末尾有’\0’!通常我们可以利用sizeof来计算数组中包含的元素个数,其做法是:int n = sizeof(a)/sizeof(a[0]);
非常需要注意的是对函数的形参数组使用sizeof的情况。举例来说,假设有如下的函数:
void fun(int array[10])
{
int n = sizeof(array);
}
你会觉得在fun内,n的值为多少呢?如果你回答40的话,那么我很遗憾的告诉你,你又错了。这里n等于4,事实上,不管形参是int的型数组,还是float型数组,或者其他任何用户自定义类型的数组,也不管数组包含多少个元素,这里的n都是4!为什么呢?原因是在函数参数传递时,数组被转化成指针了,或许你要问为什么要转化成指针,原因可以在很多书上找到,我简单说一下:假如直接传递整个数组的话,那么必然涉及到数组元素的拷贝(实参到形参的拷贝),当数组非常大时,这会导致函数执行效率极低!而只传递数组的地址(即指针)那么只需要拷贝4byte。
特性4:sizeof不能求得动态分配的内存的大小!
假如有如下语句:int* a = new int[10];int n = sizeof(a);那么n的值是多少呢?是40吗?答案是否定的!其实n等于4,因为a是指针,在特性2中讲过:在32位平台下,所有指针的大小都是4byte!切记,这里的a与特性3中的a并不一样!很多人(甚至一些老师)都认为数组名就是指针,其实不然,二者有很多区别的,要知详情,请看《c专家编程》。通过特性3和特性4,我们看到了数组和指针有着千丝万缕的关系,这些关系也是导致程序潜在错误的一大因素,关于指针与数组的关系问题我将在《C/C++刁钻问题各个击破之指针与数组的秘密》一文中进行详细介绍。
特性3指出sizeof能求静态分配的数组的大小,而特性4说明sizeof不能求的动态分配的内存的大小。于是有人认为sizeof是编译时进行求值的,并给出理由:语句int array[sizeof(int)*10];能编译通过,而很多书上都说过数组大小是编译时就确定下来的,既然前面的语句能编译通过,所以认为sizeof是编译时进行求值的。经过进一步测试我发现这个结论有些武断!至少是有些不严谨!因为在实现了c99标准的编译器(如DEV C++)中可以定义动态数组,即:语句:int num;cin>>num; int arrary[num];是对的(注意在vc6.0中是错的)。因此我就在DEV C++中对刚才的array利用语句int n =sizeof(array);cout<<n<<endl来求大小,结果编译通过,运行时输入num的值10之后,输出n等于40!在这里很明显num的值是运行时才输入的,因此sizeof不可能在编译时就求得array的大小!这样一来sizeof又变成是运行时求值的了。
那么到底sizeof是编译时求值还是运行时求值呢?最开初c标准规定sizeof只能编译时求值,后来c99又补充规定sizeof可以运行时求值。但值得注意的是,即便是在实现了c99标准的DEV C++中仍然不能用sizeof求得动态分配的内存的大小!
特性5:sizeof不能对不完整的数组求长度!
在阐述该特性之前,我们假设有两个源文件:file1.cpp和file2.cpp,其中file1.cpp中有如下的定义:
int arrayA[10] = {1,2,3,4,5,6,7,8,9,10};
int arrayB[10] = {11,12,13,14,15,16,17,18,19,20};
file2.cpp包含如下几个语句:
extern arrayA[];
extern arrayB[10];
cout<<sizeof(arrayA)<<endl; //编译出错!!
cout<<sizeof(arrayB)<<endl;
在file2.cpp中第三条语句编译出错,而第条语句正确,并且能输出40!为什么呢?原因就是sizeof(arrayA)试图求不完整数组的大小。这里的不完整的数组是指数组大小没有确定的数组!sizeof运算符的功能就是求某种对象的大小,然而声明:extern int arrayA[]只是告诉编译器arrayA是一个整型数组,但是并没告诉编译器它包含多少个元素,因此对file2.cpp中的sizeof来说它无法求出arrayA的大小,所以编译器干脆不让你通过编译。
那为什么sizeof(arrayB)又可以得到arraryB的大小呢?关键就在于在file2.cpp中其声明时使用extern int arrayB[10]明确地告诉编译器arrayB是一个包含10个元素的整型数组,因此大小是确定的。
到此本特性讲解差不多要结束了。其实本问题还能引申出连接和编译等知识点,但是目前我暂时还没自信对这两个知识点进行详细的,彻底的讲解,因此不便在此班门弄斧,不久的将来我会在本系列中加上相关问题的阐述。
特性6:当表达式作为sizeof的操作数时,它返回表达式的计算结果的类型大小,但是它不对表达式求值!
为了说明这个问题,我们来看如下的程序语句:
char ch = 1;
int num=1;
int n1 = sizeof(ch+num);
int n2 = sizeof(ch = ch+num);
假设char占用1byte,int占用4byte,那么执行上面的程序之后,n1,n2,ch的值是多少呢?我相信有不少人会认为n1与n2相等,也有不少人认为ch等于2,事实这些人都错了。事实上n1等于4,n2等于1,ch等于1,为什么呢?请看分析:
由于默认类型转换的原因,表达式ch+num的计算结果的类型是int,因此n1的值为4!而表达式ch=ch+num;的结果的类型是char,记住虽然在计算ch+num时,结果为int,但是当把结果赋值给ch时又进行了类型转换,因此表达式的最终类型还是char,所以n2等于1。n1,n2的值分别为4和1,其原因正是因为sizeof返回的是表达式计算结果的类型大小,而不是表达式中占用最大内存的变量的类型大小!
对于n2=sizeof(ch =ch+num);乍一看该程序貌似实现了让ch加上num并赋值给ch的功能,事实并非如此!由于sizeof只关心类型大小,所以它自然不应该对表达式求值,否则有画蛇添足之嫌了。但是,在支持变长数组定义的(即实现了c99标准的)编译器(dev C++)中执行了int len = 3;cout<<sizeof(int [++len])<<”,”<<len;输出是多少呢?答案是16,4!这里的++len却执行了!很不可理喻吧?这到底是为什么呢?我翻阅了《The New C Standard》一书,这主要是由于可变长度的数组的长度需要在其长度表达式求值之后才能确定大小,因此上述情况下,sizeof中的++len执行了。
正是因为sizeof的操作数中的某些表达式会被执行,而有些表达式不会被执行,这里告诫各位,尽量不要在sizeof中直接对表达式求大小,以免出现错误,你可以将sizeof(ch = ch+num);改写成 ch = ch +num;sizeof(ch);虽然多了一条语句,看似冗余了,其实好处多多:首先更加清晰明了,其次不会出现ch等于1这样的错误(假设程序的逻辑本身就是要执行ch = ch +num;)。
特性7:sizeof可以对函数调用求大小,并且求得的大小等于返回类型的大小,但是不执行函数体!
假设有如下函数(是一个写得很不好的函数,但是能很好的说明需要阐述的问题):
int fun(int& num,const int& inc)
{
float div = 2.0;
double ret =0;
num = num+inc;
ret = num/div;
return ret;
}那么语句:
int a = 3;
int b = 5;
cout<<sizeof(fun(a,b))<<endl;
cout<<a<<endl;输出多少呢?不同的人会给出不同的答案,我将对sizeof(fun(a,b))的值和a的值分别进行讨论:
首先sizeof(fun(a,b))的值:其正确是4,因为用sizeof求函数调用的大小时,它得到的是函数返回类型的大小,而fun(a,b)的返回类型是int,sizeof(int)等于4。很多人把函数的返回类型和返回值的类型弄混淆了,认为sizeof(fun(a,b))的值是8,因为函数返回值是ret,而ret被定义成double,sizeof(doube)等于8。注意,虽然函数返回值类型是double,但是在函数返回时,将该值进行了类型转换(这里的转换不安全)。也有人错误的认为sizeof(fun(a,b))的值是12,它们的理由是:fun内部定义了两个局部变量,一个是float一个是double,而sizeof(float)+sizeof(doube)= 4+8=12。这样的答案看似很合理,其实他们是错误地认为这里的sizeof是在求函数内部的变量的大小了。这当然是错误的。
接下来看a的值:其正确答案是3!还记得特性6吗?这里很类似,sizeof的操作对象是函数调用时,它不执行函数体!为此,建议大家不要把函数体放在sizeof后面的括号里,这样容易让人误以为函数执行了,其实它根本没执行。
既然对函数条用使用sizeof得到的是函数返回类型的大小,那么很自然能得出这样的结论:不能对返回类型为void的函数使用sizeof求其大小!原因请参考特性1。同理,对返回类型是任何类型的指针的函数调用使用sizeof求得的大小都为4,原因请参考特性2。
最后我们来看看这样的语句:cout<<sizeof(fun);其答案是多少呢?其实它得不到答案,原因是编译就通不过!最开始,我以为能输出答案4,因为我认为fun是函数名,而我知道函数名就是函数的地址,地址就是指针,于是我认为sizeof(fun)其实就是对一个指针求大小,根据特性2,任何指针的大小都是4。可是当我去验证时,编译器根本不让我通过!这个是为什么呢?我一时半会想不到,所以还请朋友们补充!
特性8:sizeof求得的结构体(及其对象)的大小并不等于各个数据成员对象的大小之和!
结构体的大小跟结构体成员对齐有密切关系,而并非简单地等于各个成员的大小之和!比如对如下结构体两个结构体A、B使用sizeof的结果分别是:16,24。可以看出sizeof(B)并不等于sizeof(int)+sizeof(double)+sizeof(int)=16。
struct A{ int num1; int num2; double num3; }; | struct B{ int num1; double num3; int num2; }; |
如果您不了解结构体的成员对齐,你会感到非常惊讶:结构体A和B中包含的成员都一样,只不过顺序不同而已,为什么其大小不一样呢?要解释这个问题,就要了解结构体成员对齐的规则,由于结构体成员对齐非常复杂,我将用专题——C/C++刁钻问题各个击破之位域和成员对齐——进行讲解,这里我只简单地介绍其规则:
1、 结构体的大小等于结构体内最大成员大小的整数倍
2、 结构体内的成员的首地址相对于结构体首地址的偏移量是其类型大小的整数倍,比如说double型成员相对于结构体的首地址的地址偏移量应该是8的倍数。
3、 为了满足规则1和2编译器会在结构体成员之后进行字节填充!
基于上面三个规则我们来看看为什么sizeof(B)等于24:首先假设结构体的首地址为0,第一个成员num1的首地址是0(满足规则2,前面无须字节填充,事实上结构体绝对不会在第一个数据成员前面进行字节填充),它的类型是int,因此它占用地址空间0——3。第二个成员num3是double类型,它占用8个字节,由于之前的num1只占用了4个字节,为了满足规则2,需要使用规则3在num1后面填充4个字节(4——7),使得num3的起始地址偏移量为8,因此num3占用的地址空间是:8——15。第三个成员num2是int型,其大小为4,由于num1和num3一共占用了16个字节,此时无须任何填充就能满足规则2。因此num2占用的地址空间是16——19。那么是不是结构体的总大小就是0——19共20个字节呢?请注意,别忘了规则1!由于结构体内最大成员是double占用8个字节,因此最后还需要在num2后面填充4个字节,使得结构体总体大小为24。
按照上面的三个规则和分析过程,你可以很容易地知道为什么sizeof(A)等于16。特别需要说明的是,我这里给出了三个结论性的规则,而没有阐述为什么要这样。你或许有很多疑问:为什么要结构体成员对齐,为什么要定义规则1等。如果你有这样的疑问,并尝试去弄清楚的话,那么我敢断言,不久的将来你必定会有大成就,至少在学习c++上是这样。前面说过,我会再写一篇专题:C/C++刁钻问题各个击破之位域和成员对齐来详细回答这些问题,如果你急于要弄明白,那么你可以参考其他资料,比如说《高质量c++程序设计指南》。
最后再提醒一点,在进行设计时,最好仔细安排结构体中各个成员的顺序,因为你已经看到了上面的结构体B与结构体A包含的成员相同,只不过顺序略有差异,最终就导致了B比A多消耗了50%的空间,假如在工程中需要定义该结构体的数组,多消耗的空降将是巨大的。即使将来内存降价为白菜价格,你也不要忽视这个问题,勤俭节约是中国人民的优良传统,我们应该继承和保持!
特性9:sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小!
首先解释一下什么是位域:类型的大小都是以字节(byte)为基本单位的,比如sizeof(char)为1byte,sizeof(int)为4byte等。我们知道某个类型的大小确定了该类型所能定义的变量的范围,比如sizeof(char)为1byte,而1byte等于8bit,所以char类型的变量范围是-128——127,或者0——255(unsigned char),总之它只能定义28=256个数!然而,要命的是bool类型只取值true和false,按理所只用1bit(即1/8byte)就够了,但事实上sizeof(bool)等于1。因此我们可以认为bool变量浪费了87.5%的存储空间!这在某些存储空间有限的设备(比如嵌入式设备)上是不合适的,为此需要提供一种能对变量的存储空间精打细算的机制,这就是位域。简单来说,在结构体的成员变量后面跟上的一个冒号+一个整数,就代表位域,请看如下的结构体:
Struct A
{
Bool b:1;
char ch1:4;
char ch2:4;
}item; 其中b,ch1,ch2都是位域成员,而i是普通成员。该结构体的试图让bool类型的变量b只占用1个bit,让ch1和ch2分别只占用4个bit,以此来达到对内存精打细算的功能(事实上使用位域对内存精打细算有时候能成功,有时候却未必,我将《C/C++刁钻问题各个击破之位域和成员对齐》进行论述)。另外需要特别注意的是:c语言规定位域只能用于int,signed int或者unsigned int类型,C++又补充了char和long类型!你不能这样使用位域:float f:8;这是不能通过编译的。并且位域变量不能在函数或者全局区定义,只能在结构体,自定义类,联合(union)中使用!
基于上面的结构体,语句sizeof(item.b)和sizeof(item.ch1)等对位域成员求大小的语句均不能通过编译。其原因能再本篇的概论中找到:sizeof以byte为单位返回操作数的大小!
那么爱学好问的你可能要问,sizeof(A)能否通过编译呢?如何能,其结果又是多少呢?这是两给非常好的问题,事实上我之前没有看到任何关于这方面的论述(可能是我看的资料不足),我正是在看到sizeof(item.b)不能通过编译时想到了这两个问题,然后通过验证得出了后面的结论:对包含位域的结构体是可以使用sizeof求其大小的,但其求值规则比较复杂,不仅涉及到成员对齐,还与具体编译环境有关!在这里你只需要知道可以对包含位域的结构体使用sizeof求其大小,对于sizeof是根据什么规则来求这个大小的问题,我将会在专题:《C/C++刁钻问题各个击破之位域和成员对齐》中进行详细阐述。
后记:
至此,本专题差不多该结束了,需要说明的是,这里并没有包含所有关于sizeof的知识点,但是也几乎包含了所有的容易出错的特性。为了完成该文,我花了断断续续3天半时间,想想效率实在是底下。由于是本系列的第一个专题,我格外慎重,深怕讲错了误导大家。即便如此,也难免错误或不妥之处,还请各位朋友指正!
另外,我有几句话要对大学生朋友们说:教科书通常只是教授很基础的知识,要想深入学习,还需要翻阅其他资料,比如论文、网络资料、论坛博文,最重要的一点是要在学习时经常总结、记录、归纳,积少成多,这样坚持下来一定受益匪浅。 @import url(http://www.cppblog.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);5. Size of basic variables: From http://blog.youkuaiyun.com/zhangxinbin5/article/details/7929591
32位编译器:
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int:
unsigned int : 4个字节
float:
double:
long:
long long:
unsigned long:
64位编译器:
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int:
unsigned int : 4个字节
float:
double:
long:
long long:
unsigned long: