被sizeof虐最早还是从toshi开始的。这货当初听我说我学C++也算入门了吧,他就拿一个sizeof的题来虐我,哈哈,好吧我必须承认当时连门都没看到,更谈不上入门了。
之前,我们先定义个宏,方便输出:
#define PRINT(a) cout << #a << ":" << a << endl
v1.0 最基本的sizeof
void printSize(char aInFunc[]){
PRINT(sizeof(aInFunc));
}
int main(int argc, _TCHAR* argv[])
{
char a[] = "abc";
char *b = "abc";
PRINT(sizeof(a));
PRINT(sizeof(b));
printSize(a);
getchar();
return 0;
}
非常简单。很多人一看就知道前两个输出应该是 4 4
第三个呢?
哇,前两个都是4了,难道第三个还能不是4?
恭喜你答对了。这题简单就简单在数组a和指针b居然的sizeof正好一样,所以即便不了解原理也能蒙对。
结果:
sizeof(a):4
sizeof(b):4
sizeof(aInFunc):4
v2.0开始说正事儿
刚才不是正事儿么?对,如果面试官出这题就是非常有诚意的想让你通过。。
void printSize(char aInFunc[]){
PRINT(sizeof(aInFunc));
}
int main(int argc, _TCHAR* argv[])
{
char a[] = "abcd";//modified
char *b = "abcd";//modified
PRINT(sizeof(a));
PRINT(sizeof(b));
printSize(a);
getchar();
return 0;
}
注意,这段程序跟1.0的基本一样,变化之处已经注明。
答案是多少呢?我当时拿2.0坑人的时候,已经开始有人犯迷糊了。
是5 5 5吧?对不起55555……
大家都能想到第一个是5,因为我们知道数组里面字符串后面会有一个\0。第二个可是一个指针哎,sizeof指针的时候输出的是指针这个变量占用的空间,所以还是4。
那第三个总该是5了吧---当然不是,是的话肯定就不这么考你了。在数组作为参数传入的时候实际上就退化为普通的指针了,不过这个指针实际上类似于const pointer,不可以其指向的内存空间:注意,还是有不同的!
const pointer | 指向的内容不一定能够被修改,比如例子当中的b指向“abcd”,这里的“abcd”实际上是字面量,不可以被修改! 如果指向的是堆内存,那当然随便你怎么样了。哈哈 |
数组 | 可以修改,栈内存 |
结果:
sizeof(a):5
sizeof(b):4
sizeof(aInFunc):4
v3.0来点儿猛料!
如果有高人看到2.0,肯定会笑道你这结果是有问题的。
太对了!因为我用的是32位的编译器!现在条件改变,编译器为64位,程序不变,请问结果几何?
……
这问题就是会处在哪儿呢?
原来32位也好,64位也好,说的是cpu的处理能力,即字长。32位下面的寻址最大为4个字节,所以就是32位嘛,多好理解;64位可以简单的理解为8个字节,我查了一些资料,其实64位的架构很多都是从32位演进的,这个很正常,不信你看看中国移动是怎么从gsm演进到tds-cdma又到hspa又到tdd-lte的。
参考文献:http://blog.youkuaiyun.com/jsufcz/article/details/3305073
其实说了半天,就是指针这种类型的变量的占用空间变了,变成了8个字节。
我用的是VS2013,一般默认的都是32位的编译环境,大家可以通过修改为64位试试。
结果:
sizeof(a):5
sizeof(b):8
sizeof(aInFunc):8
v4.0休息一下
刚说了半天这么热闹,其实这回该说点儿实用的了。我们知道sizeof一个指针只能得到它占用内存的大小,可是我想知道它指向的字符串大小怎么办?
……
答案揭晓:strlen函数。需要注意的是,这个函数返回的结果不包含\0,也就是说
strlen("abcd")---->4
v5.0死磕了它
struct S{
int a;
char b;
};
PRINT(sizeof(S));
首先说明一点,这样的语法在C里面可能是有问题的,因为我们可能需要到一个typedef,不过C++里面已经默认为我们做了这件事情。
输出多少?我们都知道结构体是元素大小的和。所以理所当然的认为 应该是 4+1=5。这样想对于开发其实是没有太大的问题的。不过编译器为了让程序跑起来更带劲儿,做了优化,这就是传说中的对齐。
我也不瞎扯了,结果是8。
简单说下我对对齐的认识:
对齐是为了内存访问速度而生的,如果内存不对齐,那么指针移动的时候就需要注意下一个变量的类型和大小,并作出准确的移动;对齐以后呢,移动的时候只需要移动到下一个对齐的位置就可以了吧。我说的比较简单,认识有待提高嘛,哈哈。
我们更关心类似于上面的结果输出。当内存对齐的值为8时,将结构体内所有的变量占用的空间加起来,不足8的倍数的,向上对齐。
struct S{
long long a;//8
char b;//1
int c;//4
int d;//4
};
这个时候sizeof的值为 8+1+4+4 = 17,所以结果为24。
再在S当中增加7个char,就像:
struct S{
long long a;
char b[8];
int c;
int d;
};
结果仍然是24。不过再增加就超过24了,所以结果就成了32。
参考文献:http://blog.youkuaiyun.com/zhangyang0402/article/details/5802876
我用的是vs2013,经过测试,vs2013的默认对齐值就像参考文献当中的vc6.0一样,也是8。
v6.0还没完?
其实我饿了,不过还是要写完再去吃!
6.1定义一个类Base,里面啥都没有。
class Base
{
public:
};
PRINT(sizeof(Base));
结果是1。因为你声明了一个类型,总得给人家个座位坐吧。亲,可千万别站着,给你一字节先。
6.2接着,加构造和析构,加了三个函数。
class Base
{
public:
Base();
void funcA(int );
void funcA(int ,int);
void funcA(int ,int,int);
~Base();
};
sizeof呢?仍然是1。因为没有实质性的东西在里面。
函数的代码不占位置吗?存在静态区。人家是特派员,哈哈。
参考文献:http://blog.youkuaiyun.com/jacqueslim/article/details/6801601
6.3函数加个virtual。
class Base
{
public:
Base();
void funcA(int );
void funcA(int ,int);
void funcA(int ,int,int);
virtual ~Base();
};
这时候结果是4。
因为virtual会导致Base类有一个指针指向虚函数表,这是C++的多态实现机制,子类在继承Base类以后应该覆写virtual函数,如果父类指针指向了一个子类的对象,那么这个指针可以通过查虚函数表来调用子类的函数,而不是调用父类的函数。
6.4好多个virtual函数。
算了,肯定还是4,这时候只是虚函数表变大了,指针又不会变。
6.5加成员变量
其实我已经很奇怪了,既然默认对齐到8字节,为什么虚函数表指针还胆敢输出4??
后来修改#pragma pack 试了一下发现当值设置到8的时候,仍然是按照4对齐的。为什么?
class Base0
{
public:
Base0();
void funcA(int);
void funcA(int, int);
virtual void funcA(int, int, int);
virtual ~Base0();
int a;
int b;
char str[5];
};
一开始我还以为这也算是struct和class的区别,不过我错了。后来突然反应过来,我这个类当中就没有一个字段超过了4个字节,所以你对齐到8字节有什么意义呢?对齐的字节值不仅跟你的设置有关,也跟你的具体字段有关。
这时的sizeof结果为 4+4+4+5 = 17--->20
把int a 和int b替换为一个8字节的类型,比如double类型,结果就是另一番景象:
4+8+5=17 --->24 对齐到8字节了!
总结:
1、指针的大小跟编译器有关,32位为4字节,64位为8字节。
2、数组作为形参时退化为指针。
3、对齐值跟pragma pack设置和结构体(或者类)当中的最大字段有关,前者的值大于后者时,取后者,小于后者时取前者。