在C++中使用sizeof运算符并不会带来一致的结果,sizeof的用法像是一个函数,在C++中它被定义为一个关键字,从测试结果来看它不是以函数的方式实现,并且也不应该以函数的方式实现,因为在标准库函数中大量用到sizeof运算符,如果是以函数的方式实现必将带来严重的效率问题。如下是一段测试代码,用G++和MS VC++分别编译出现不同的结果。
#include<iostream>
#include<string>
using namespace std;
class NewString:public string
{
public:
NewString(string str):
string(str)
{
}
private:
int new_member;
};
int main()
{
string str1("abcd");
string str2("abcd3");
NewString str3("abcd");
#if defined(__GNUC__)
cout<<"Under gnu c++ compiler:"<<endl;
cout<<"sizeof(str1)="<<sizeof(str1)<<endl;
cout<<"sizeof(str2)="<<sizeof(str2)<<endl;
cout<<"sizeof(str3)="<<sizeof(str3)<<endl;
#endif
#if defined(_MSC_VER)
cout<<"Under MS c++ compiler:"<<endl;
cout<<"sizeof(str1)="<<sizeof(str1)<<endl;
cout<<"sizeof(str2)="<<sizeof(str2)<<endl;
cout<<"sizeof(str3)="<<sizeof(str3)<<endl;
#endif
return 0;
}
输出:
(用 GNU C++编译器)
Under gnu c++ compiler:
sizeof(str1)=4
sizeof(str2)=4
sizeof(str3)=8
(用微软 MS VC+编译器)
Under MS c++ compiler:
sizeof(str1)=28
sizeof(str2)=28
sizeof(str3)=32
我们由上面的测试代码可以推断出C++中的sizeof运算符是在编译期间由编译器以递归方式计算出类成员所占的空间,整个类的空间为各类成员所占空间之和,就是说这个sizeof的结果是可以静态的计算出来而不是在运行期间再花费CPU时间来推导。如是一个类中有一个成员为指向一块内存的指针,那么这个类成员就只占有相应机器上的指针的大小,而不再加上指针指向的内存的大小。对于那此担心对容器类使用sizeof运算符来申请固定空间之后会因为容器的变大而使程序出现内存错误的程序员,这个完全是没有必要的,容器容量的增长并不会使容器类实例的sizeof大小增大。
以上来自 [原创]整理关于sizeof 运算符http://saturnman.blog.163.com/blog/static/5576112010129857634/
----以计算数组长度为例
摘要
sizeof运算符是C++是提供的一个关键字,其用法虽和普通函数用法没有区别,但其工作原理与普通函数却有十分大的区别,其计算是在编译期完成,是不会占用运行时时间和空间的一种十分高效的工具。其功能与C++中元编程相结合更能达到十分强大又不失效率的效果。本文想通过对于一个分配在栈上的数组的长度的计算来讨论一下其精妙用法,由于用到元编程,代码免不了晦涩难懂,希望初学者不要盲目效仿,可以说这不是写给C++初学者的东西J。
原先整理过一篇关于sizeof的日志,昨天有人在群是发问题,发现自己对于C++的一个十分重要的部分还没有认识,于是有了这篇日志。原来那篇在这里,感兴趣的可以看看。
http://saturnman.blog.163.com/blog/static/5576112010129857634/
sizeof的参数是一个类型,如果是变量,测由编译器推到这个类型,之后计算这个类型的大小,下面我们来看一份代码:
#include<iostream>
using namespace std;
void double_1(int* arr)
{
cout<<"in double_1() Length="<<sizeof(arr)/sizeof(arr)<<endl;
for(int i=0;i<10;++i)
{
arr[i] *= 2;
}
}
void double_2(int arr[])
{
cout<<"in double_2() Length="<<sizeof(arr)/sizeof(arr[0])<<endl;
for(int i=0;i<10;++i)
{
arr[i] *= 2;
}
}
void double_3(int (&arr)[10])
{
cout<<"in double_3() Length="<<sizeof(arr)/sizeof(arr[0])<<endl;
for(int i=0;i<10;++i)
{
arr[i] *= 2;
}
}
int main()
{
int arr[]={0,1,2,3,4,5,6,7,8,9};
double_1(arr);
double_2(arr);
double_3(arr);
cout<<"in main Length="<<sizeof(arr)/sizeof(arr[0])<<endl;
return 0;
}
这个代码的数组长度是10,我在函数中不再检查长度,这份代码只是为了测试之用,在实际工程中千万不要出现这种垃圾代码J
下面是运行的结果:
double_1与double_2给出的数组长度都是不正确的,这个是C++的基础问题,这两个函数其实是等效的,在main函数中,一般认为arr的语义是一个指向int的指针,是数组首地址。其实这种说法是不准确的,在C++中十分重要的一点就类型信息,这些信息在编译期严格控制,细微的差别也会造所十分不同的结果。这个在main函数中arr的真实语义是 int[] &(这里使用这种写法在C++中并不支持,这样写是为了看起来比较清楚,后面会解释),意为一个int类型数组的引用,这种说法可以比较别扭,可是事实如此。在把arr传入到double_1中和double_2中时,在参数传递时发生了隐式类型转换,把int[] &转化为int*, 这种转把使得在double_1和double_2函数中,arr的语义发生转化,sizeof在double_1和double_2中进行运算时这句sizeof(arr)实际计算的是一个指针的大小,计算出来的数组长度当然是不正确的。注意我们最终的结果,数组的内容还是被放大了2倍(一共放大8倍),这里其实double_1和double_2函数没有任何不同,参数类型int[]和int* 完全等效。下面我们来分析一个double_3这个看似有点奇怪的函数,它的参数类型为 int(&)[10] 这个类型就是前面写的int[] &,可是后面的杜撰的语法在C++是中不对的,这个语法可参照函数指针,差不多一个样子,如果起上参数名的话就这样写int(&arr) [10],这个的语义就是指向int数组(长度为10)的引用,这样在函数传递参数时不会发生类型转换,这样在double_3和main函数中的sizeof(arr)就会计算出数组的占用字节数。除以每个数组元素的字节数就计算出数组长度了。这里有一点要指出,double_1 double_2 是按值传递指针,double_3是传递引用,这里按值传递是指向数组的指针,这样可以在函数中修改数组元素,把数值加倍。希望在此不要与原始变量的传值与传引用混淆。
下面我们来看一下两种计算数组长度的方法。
#include<iostream>
#include<vector>
using namespace std;
//a class definition to for testing
class A
{
public:
int x;
double y;
vector<int> z;
};
using namespace std;
//using c++ template technique to compute length
template<class T,int N>
char(&len_byte(T(&) [N]))[N];
#define arr_len_1(_array) (sizeof(len_byte(_array)))
//using normal C macro to compute length
#define arr_len_2(_array) (sizeof(_array)/sizeof(_array[0]))
void comp_length(int(&arr) [10])
{
cout<<"arr_len_1(arr)="<<arr_len_1(arr)<<endl;
cout<<"arr_len_2(arr)="<<arr_len_2(arr)<<endl;
}
int main()
{
A a[20];
int b[10];
cout<<"arr_len_1(a)="<<arr_len_1(a)<<endl;
cout<<"arr_len_2(a)="<<arr_len_2(a)<<endl;
cout<<"arr_len_1(b)="<<arr_len_1(b)<<endl;
cout<<"arr_len_2(b)="<<arr_len_2(b)<<endl;
//compute array length in function comp_length
comp_length(b);
return 0;
}
下面是运行结果:
arr_len_2是直接利用C语言宏来用sizeof计算数组长度,这个也是以前广为应用的方法,arr_len_1是利用模板来完成数组长度计算,这个代码看起来较为难懂,显然需要解释一下。它是一个利用了len_byte的一个宏,而len_byte是模板下定义的是一个函数,这个函数只定义了原型(我们不需要定义它的实体,因为这个函数根本不会被真的调用),这个函数不算返回值是这样的,len_byte(T(&) [N]) 它的参数为一个数组的引用,它的返回值是一个char类型数组的引用,这个数组的长度利用模板来使它与原数组长度相等,这个char数组的引用返回值的写法也是十分难懂,不过有了前面的数组引用参数的写法,这个也不难理解。注意我们的函数是声名,我们没有必要给参数起名子,如果起名子了是可以的,不过根本不会用到。下面我们来看一下它是如何工作的,当我们用宏arr_len_1(arr)时,它被替换为sizeof(len_byte(arr)),这时编译器检查arr的类型,用来实例化一个模板,并推导出返回类型(主要是推断出返回的char数组引用的长度),之后的sizeof运算符检查出这个类型,计算它的大小,这样就把数组长度计算出来。在最终的可执行文件中并不存在len_byte这个函数的调用,因此我们也就不用为这个函数写出实现的代码,只要声名部分就完全可以了。
C语言的忠实爱好者可能会质疑这种新的方法有无必要,毕竟原来的arr_len_2的方法好像在任何时候都可以正常工作,而两者都没有运行时开消,而模板实现又十分难写难看。但是我要告诉你新的方法有极大的好处,这就是C++的思想,make things under compiler’s control. 模板的实现方式可以把代码交给编译器控制并检查错误,而不是交给预处理器来处理之后再交给编译器,预处理器是没有类型检查的,如果我们不是把数组传给它,就可能产生错误,下面我们来构造一个这样的错误。我们可以构造一个类,它有重载一个操作符operator[int],以使sizeof(arr[0])不会出错。下面是代码。
#include<iostream>
#include<vector>
using namespace std;
//a class definition to for testing
class A
{
public:
char operator[](int input)
{
return 'a';
}
int x;
double y;
vector<int> z;
};
using namespace std;
//using c++ template technique to compute length
template<class T,int N>
char(&len_byte(T(&) [N]))[N];
#define arr_len_1(_array) (sizeof(len_byte(_array)))
//using normal C macro to compute length
#define arr_len_2(_array) (sizeof(_array)/sizeof(_array[0]))
int main()
{
A a;
cout<<"arr_len_2(a)="<<arr_len_2(a)<<endl;
//if using this,a compile time error will show up
//cout<<"arr_len_1(a)="<<arr_len_1(a)<<endl;
return 0;
}
这份代码编译后运行的结果如下:
如果使用模板实现的版本,就会出错下面的错误:
编译器帮助我们实现了数组类型的检查。
好的就写到这里,码字不易,转载请注出处,谢谢!本人查找资料时找到wildfrog和cpjust的回贴,收获很大,一并感谢!
以上来自 初识元编程,再次认识sizeof运算符[原创] http://saturnman.blog.163.com/blog/static/55761120107215259147/
一、sizeof运算符的工作过程序。
1. sizeof运算符是接受的参是类型,或是变量(如果是表达式,则按表达式结果的类型计算),如果是实参是类型那么sizeof得到的结果是此类型的大小,以byte为单位,如是实参是变量,那么sizeof得到的结果是此变量的类型的大小,以byte为单位(这里不讨论typeid这种,因为它在不同的编译器上的实现方式差异很大,难以找到统一的结论)。sizeof的返回值类型是const std::size_t,但是它永远会比0大。
2. sizeof真正计算的时间是编译期,由编译器把sizeof的结果计算好放入相对应的位址,过量使用sizeof运算符也不会对系统性能产生不利影响。
考虑如下代码:
//code #1
const size_t val = 4;
int arr[val];
//code #2
int arr[sizeof(int)];
如果sizeof(int)的结是为4那么上面的code #1和code #2几乎没有差别(差别还是有一些,尽管有时它们产生的汇编代码都是相同的,但有时可能不同)。
3. C++中的类型其实十分微妙细小,有时会有不经意的转换,这些在某些情况下会产生误解。也许只有对其深入理解才能准确把握其中的奥妙吧,在这里我只举一个十分常见的而且不认真的学习者经常犯的错误,只给出代码,请读者自行体会。
#include<iostream>
using namespace std;
//fun #1
int foo1(int* arr)
{
return sizeof(arr);
}
//fun #2
int foo2(int arr[])
{
return sizeof(arr);
}
//fun #3
template<int N>
int foo3(int(&arr) [N])
{
return sizeof(arr);
}
int main()
{
int arr[]={1,2,3,4,5,6};
int* p_arr = arr; //type conversion
cout<<"sizeof(arr)="<<sizeof(arr)<<endl;
cout<<"sizeof(p_arr)="<<sizeof(p_arr)<<endl;
cout<<"call #1 ="<<foo1(arr)<<endl;//type conversion
cout<<"call #2 ="<<foo2(arr)<<endl;//type conversion
cout<<"call #3 ="<<foo3(arr)<<endl;
return 0;
}
结果:
sizeof(arr)=24
sizeof(p_arr)=4
call #1 =4
call #2 =4
call #3 =24
二、sizeof运算符的使用限制
如果牢记sizeof运算符的运算的静态性和用法,一般不易犯什么错,不过还时有几点指出,一般这些错误编译器都会帮你找出的,不用担心。
常规函数指针和数据指针的大小一般是一样大的,如果是类的非静态成员函数则可能会出现比一般指针大的情况,这个一般不是问题。但是有时粗心的人会把函数指针和表达式搞错。如下面的代码,同样不解释,自行体会。
#include<iostream>
using namespace std;
void test_1()
{
}
int test_2()
{
}
int main()
{
void (*pf1)();
int (*pf2)();
cout<<"sizeof(pf1)="<<sizeof(pf1)<<endl; //function pointer
cout<<"sizeof(pf2)="<<sizeof(pf2)<<endl; //function pointer
cout<<"sizeof(test_1())="<<sizeof(test_1())<<endl;//expression
//same as sizeof(void)
cout<<"sizeof(test_2())="<<sizeof(test_2())<<endl;//expression
//same as sizeof(int)
cout<<"sizeof(void)="<<sizeof(void)<<endl; //void
return 0;
}
结果:
sizeof(pf1)=4
sizeof(pf2)=4
sizeof(test_1())=1
sizeof(test_2())=4
sizeof(void)=1
这个给出了一些warning
C:\Users\saturnman\cpp>g++ function_sizeof.cpp
function_sizeof.cpp: In function 'int main()':
function_sizeof.cpp:17:44: warning: invalid application of 'sizeof' to a void type
function_sizeof.cpp:21:36: warning: invalid application of 'sizeof' to a void type
2. 对于递归sizeof的违例
由于sizeof是在编译期计算,想想如果一个类成员大小由一个sizeof表达式计算得到,并且此sizeof计算的就是本类的大小,那么将形成递归的sizeof,此时sizeof运算符是不能完成工作的。考虑如下代码:
#include<iostream>
using namespace std;
class A
{
public:
int a[sizeof(*this)];//error
void test()
{
int a[sizeof(*this)];
}
};
int main()
{
return 0;
}
编译时会产生如下错误:
sizeof_class.cpp:6:19: error: invalid use of 'this' at top level
而处在类成员函数中有的相同代码就没有出错,读者可以想想这是为什么。
3. 对于编译期和运行期计算相同表达式的结果可能并不相同,考虑如下代码:
bool f()
{
char array[1+int(1+0.2-0.1-0.1)];
int size = 1+int(1+0.2-0.1-0.1);
return sizeof(array)==size;
}
// it is unspecified whether the value of f() will be true or false.
sizeof(array)的值要Re
sizeof(array)的值要在编译时计算int(1+0.2-0.1-0.1),但是size变量的值可能是运行期才计算出来的,并没有人或是什么标准可以保证这两个时时期计算相同的表达式会产生相同的结果,因此上面的函数的返回值是不确定的。
[1] Programming Language C++ ISO/IEC JTC1 SC22 WG21 N3092
源文档:http://saturnman.blog.163.com/blog/static/55761120101113115756305/
以上来自 http://www.cnblogs.com/rollenholt/articles/1907421.html