sizeof小览

一、文章来由—一道面试题迁出的探究

我发现我已经形成一种习惯写来由了,以后看博客的时候可以让我回忆起为什么出现这个问题,我用什么方法解决的,既然形成习惯就让这个习惯保持下去吧。今天实验室师姐在看书,一处不解,是关于sizeof的,大家讨论此问题后,我一向信服做了才知道答案,于是有了这篇文章。但是只能叫小览,因为不可能总结完sizeof的用法,欢迎补充和讨论。

二、从这道题目说起

我直接将问题的关键部分提出来:

string strArr1[] = { "Trend", "Micro", "Soft" };
cout << "sizeof(strArr1) == " << sizeof(strArr1) << endl;

请问输出多少,书上的答案这样写道:

而字符串strArr1是由3段构成的,所 以sizeof(strArr1)大小是12。
首先要明确sizeof不是函数,也不是一元运算符,它是个类似宏定义的特殊关键字,特别是sizeof(string)=4

这个不去试是不知道的,因为编译器那么多,编译器做什么事情不去试怎么可能知道

果然,在vs2013 release Win32模式(x64模式还要更大)下结果是

sizeof(string) == 24
sizeof(strArr1) == 72

debug模式

sizeof(string) == 28
sizeof(strArr1) == 84

也是3倍关系,因为这是一个string数组,里面有三个string对象

那么为什么string有不同的结果?

查阅了相关资料得出结论:我们知道char*肯定是4字节,string里面可能不止包含一个char*那么简单,还包含有长度信息等其他信息,string的实现在各库中可能有所不同,但是在同一库中相同一点是,无论你的string里放多长的字符串,它的sizeof()都是固定的,字符串所占的空间是从堆中动态分配的,与sizeof()无关。

  sizeof(string)=4可能是最典型的实现之一,不过也有sizeof()为12、32字节的库实现。 但是VC6.0测试后sizeof(string)=16.还是跟编译器有关

为了完全测试一些东西,我测试越写越多,然后我来了威力加强版,见代码:

#include<iostream>

using namespace std;

int main()
{
    char p[] = { 'a', 'b', 'c', 'a', 'b', 'c' };
    char *p1 = "abcabc";
    char p2[] = "abcabc";
    char p3[][2] = { { 'a', 'b' }, { 'c', 'a' }, { 'b', 'c' } };
    printf("p == %s\n", p);
    cout << p << endl;
    cout << "sizeof(p) == " << sizeof(p) << endl;
    cout << "sizeof(p1) == " << sizeof(p1) << endl;
    cout << "sizeof(p2) == " << sizeof(p2) << endl;
    cout << "sizeof(p3) == " << sizeof(p3) << endl;

    cout<<"sizeof(string) == " << sizeof(string) << endl;

    string strArr1[] = { "Trend", "Micro", "Soft" };
    cout << "sizeof(strArr1) == " << sizeof(strArr1) << endl;

    int a = 0;
    cout <<"sizeof(a = 3) == " << sizeof(a = 3) << endl;
    cout << "a == " << a << endl;

    cout << "sizeof(999999) == " << sizeof(999999) << endl;
    cout << "sizeof(9999999999999999999) == " << sizeof(9999999999999999999) << endl;
    cout << "sizeof(9 / 5) == " << sizeof(9 / 5) << endl;
    cout << "sizeof((double)9 / 5) == " << sizeof((double)9 / 5) << endl;

    return 0;
}

运行结果如图所示:

我来一一解释我的这些测试在做什么:
(1)首先p的这种初始化方式,在末尾不会加’\0’,所以 sizeof(p) == 6;而且一个有趣的问题是printf和cout直接输出p是不同的,printf是要碰到’\0’结束,我也看了printf和cout的汇编代码,但是没有细究,之后又空详细研究一下
(2)p1和p2的sizeof不同,因为一个是指针,一个是字符数组,指针在Win32编译环境下的sizeof都是4,因为是4字节的地址,32bits可寻址空间
(3)p3是另一个有趣的问题,p3不能写成p3[3][]来初始化,因为这样初始化要保证二维数组每一行的个数相同,也就是不能出现“参差不齐”的情况,那种情况要动态分配
(4)这是一个陷阱

int a = 0;
cout<<sizeof(a=3)<<endl;
cout<<a<<endl;

输出为什么是4,0 而不是期望中的4,3???就在于sizeof在编译阶段处理的特性。由于sizeof不能被编译成机器码,所以sizeof作用范围内,也就是()里面的内容不能被编译,而是被替换成类型。=操作符返回左操作数的类型,所以a=3相当于int,而代码也被替换为:

cout<<4<<endl;
cout<<a<<endl;

所以,sizeof是不可能支持链式表达式的,这也是和一元操作符不一样的地方。

不要把sizeof当成函数,也不要看作一元操作符,把他当成一个特殊的编译预处理。

(5)这样的原因是999999是一个编译器int型可以搞定的数,所以按int来处理,太大的数不能用int搞定,但是8个字节一定可以搞定的

sizeof(999999) == 4
sizeof(9999999999999999999) == 8

(6)最后是看了(9 / 5)编译器是作为int看待的,强制转换后才是double

三、sizeof 能总结多少是多少

sizeof博大精深,即使看了很多资料,一口气总结完也是不可能了,总结常用的就好。

1、什么是sizeof

首先看一下sizeof在msdn上的定义:

The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t.

看到return这个字眼,是不是想到了函数?错了,sizeof不是一个函数,你见过给一个函数传参数,而不加括号的吗?sizeof可以,所以sizeof不是函数。网上有人说sizeof是一元操作符,但是我并不这么认为,因为sizeof更像一个特殊的宏,它是在编译阶段求值的。举个例子:

cout<<sizeof(int)<<endl; // 32位机上int长度为4
cout<<sizeof(1==2)<<endl; // == 操作符返回bool类型,相当于 cout<<sizeof(bool)<<endl;
在编译阶段已经被翻译为:
cout<<4<<endl;
cout<<1<<endl;

2、语法

sizeof有三种语法形式,如下:

1) sizeof( object ); // sizeof( 对象 );
2) sizeof( type_name ); // sizeof( 类型 );
3) sizeof object; // sizeof 对象;
所以,

int i;
sizeof( i ); // ok
sizeof i; // ok
sizeof( int ); // ok
sizeof int; // error
既然写法1可以完全代替写法3,为求形式统一以及减少我们大脑的负担,第3种写法,忘掉它吧!

实际上,sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一致的。这里,对象可以进一步延伸至表达式,即sizeof可以对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,一般不会对表达式进行计算。如:

sizeof( 2 ); // 2的类型为int,所以等价于 sizeof( int );
sizeof( 2 + 3.14 ); // 3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );

3、函数的sizeof

函数类型

考虑下面的问题:
int f1(){return 0;};
double f2(){return 0.0;}
void f3(){}

cout<<sizeof(f1())<<endl; // f1()返回值为int,因此被认为是int
cout<<sizeof(f2())<<endl; // f2()返回值为double,因此被认为是double
cout<<sizeof(f3())<<endl; // 错误!无法对void类型使用sizeof
cout<<sizeof(f1)<<endl;   // 错误!无法对函数指针使用sizeof
cout<<sizeof*f2<<endl;   // *f2,和f2()等价,因为可以看作object,所以括号不是必要的。被认为是double

结论:对函数使用sizeof,在编译阶段会被函数返回值的类型取代

4、数组的sizeof

char a[] = "abcdef";
int b[20] = {3, 4};
char c[2][3] = {"aa", "bb"};

cout<<sizeof(a)<<endl; // 7
cout<<sizeof(b)<<endl; // 20*4=80
cout<<sizeof(c)<<endl; // 6

数组a的大小在定义时未指定,编译时给它分配的空间是按照初始化的值确定的,也就是7。c是多维数组,占用的空间大小是各维数的乘积,也就是6。可以看出,数组的大小就是他在编译时被分配的空间,也就是各维数的乘积*数组元素的大小。

结论:数组的大小是各维数的乘积*数组元素的大小。

这里有一个陷阱:

int *d = new int[10];

cout<<sizeof(d)<<endl; // 4

d是我们常说的动态数组,但是他实质上还是一个指针,所以sizeof(d)的值是4。
再考虑下面的问题:

double* (*a)[3][6];
cout<<sizeof(a)<<endl;   // 4
cout<<sizeof(*a)<<endl;   // 72
cout<<sizeof(**a)<<endl; // 24
cout<<sizeof(***a)<<endl; // 4
cout<<sizeof(****a)<<endl; // 8

a是一个很奇怪的定义,他表示一个指向 double*[3][6]类型数组的指针。既然是指针,所以sizeof(a)就是4。

既然a是指向double*[3][6]类型的指针,*a就表示一个double*[3][6]的多维数组类型,因此sizeof(*a)=3*6*sizeof(double*)=72。同样的,**a表示一个double*[6]类型的数组,所以sizeof(**a)=6*sizeof(double*)=24。***a就表示其中的一个元素,也就是double*了,所以sizeof(***a)=4。至于****a,就是一个double了,所以sizeof(****a)=sizeof(double)=8。

差不多也要结束了,如果更进一步了解,需要查阅更多的资料~~~

—END—


参考文献

[1] http://blog.csdn.net/freefalcon/article/details/54839
[2] http://www.cnblogs.com/wanghetao/archive/2012/04/04/2431760.html

时间: 2024-10-26 02:11:42

sizeof小览的相关文章

C++抽象类小览

一.文章来由 virtual 方法和 virtual 类可以说是c++语言的一大特性,甚至有人说是c++语言的精髓,其实这么说也是有一定道理的,因为运行时多态在c++中体现淋漓尽致,而 virtual 就是为多态服务的.这也是一个一定要搞懂的c++问题,所以有了这篇文章.同时,我觉得这类底层问题不可能一文以蔽之,而且我也相信真正想搞懂这个问题的读者,不会只读我这一篇文章,所以只是小览,同时欢迎讨论和指正. 二.引入原因 其实,引入纯虚函数的原因我在我另一篇文章虚函数与多态小览就有写,不过重要的话

小览CallStack(调用栈)(三)-用调试器脚本查看调用栈信息

在这一系列之前的两篇文章中,我介绍了如何在windbg中查看调用栈的相关 信息(详见小览call stack(调用栈)(一)),以及调用约定(详见小览call stack(调用栈) (二)--调用约定).今天的这篇博客在二者的基础 之上,介绍如何使用调式器脚本程序来观察调用栈.对CallStack感兴趣的朋友 可以在此基础上开发更加详尽的脚本来观察CallStack的信息:对调试感兴趣的 朋友则可以看一下DScript的用处. 我们先来看一个例子,下面的程序并不是一个优美的程序片段,但是它能够

虚函数与多态小览

一.文章来由 Bill又写文章来由了哇~~早就想好好搞清这个问题了,这是c++领域里面比较难搞定的一块知识点,而且最近在看设计模式,里面有涉及这块,之前学过的不用容易玩忘记,于是就干脆研究透一点,也好碰到.用到的时候不心慌~于是有了这篇文章. 二.从编译时和运行时说起 2.1 编译时: 顾名思义就是正在编译的时候.就是编译器帮你把源代码翻译成机器能识别的代码.(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.比如Java只有JVM识别的字节码,C#中只有CLR能识别的MSIL)

工厂模式与抽象工厂模式小览(二)

一.文章来由 就等啦~~还记得工厂模式与抽象工厂模式小览(一)第一部吗?我们在第一部中,分别详细的描述了(1)简单工厂(2)工厂模式(3)抽象工厂模式,但是并没有描述他们之间的关系,也没有比较工厂模式和抽象工厂模式,这对难舍难分的好基友之间的异同点,这些工作我们在第二部中完成~ 二.工厂模式和简单工厂 话说十年前,有一个暴发户,他家有三辆汽车--Benz奔驰.Bmw宝马.Audi奥迪,还雇了司机为他开车.不过,暴发户坐车时总是怪怪的:上Benz车后跟司机说"开奔驰车!",坐上Bmw后他

小览call stack(调用栈) (二)——调用约定

在上一篇博客中小览call stack(调用栈) (一)中,我展示了如何在windbg中 观察调用栈的相关信息:函数的返回地址,参数,返回值.这些信息都按照一定 的规则存储在固定的地方.这个规则就是调用约定(calling convention). 调用约定在计算机界不是什么新鲜的概念,已经有许多相关的文献给予详细 的介绍.比较全面的介绍可以参见wikipedia上的相关页面.然而,如果你和我 一样,在第一次接触调用约定的时候,觉得这个概念是个高深神秘的冬冬,那么 就请跟随我一起,在这篇博客中看

小览call stack(调用栈) (一)

栈在计算机领域中是个经常提到的名词,数据结构中有栈:网络传输中有协 议栈.今天我们讨论的调用栈(call stack),指的是在程序的执行过程中存储函 数调用信息的动态数据结构. 这个定义可能太抽象了一些,在给出具体的例子之前,请大家先思考一个问 题,哪些信息是函数调用过程中所需要的?或者这么问,一个编译器,在面对一 个函数的调用指令时,该生成哪些代码? 首先,函数的返回地址要保存下来.就好像你和你的小狗玩仍飞碟游戏,每 一个函数调用好比扔一个飞碟,当你的狗狗哼兹哼兹的捡来飞碟,函数完执行的 时

浏览器小览【欢迎讨论】

本人喜欢玩各类电子产品,自然各种浏览器也不会被我错过 我应该说用过很多浏览器,开始的IE,搜狗,遨游,世界之窗,猎豹,后来的Opera,谷歌chrome,火狐,百度影音浏览器,百度浏览器,360浏览器,360极速浏览器...怎么算起来都有那么个上十个的样子,所以有资格说话,哈哈(我个人的理念是没用过.没经历过就没有资格吐槽,不过不是说大家不能讨论哦,O(∩_∩)O哈哈~),浏览器都做的各有千秋. 雷布斯的小米我推荐,但是猎豹还是算了吧 火狐上有很多开发者很好用的小插件,我之前进行iOS开发的时候

搜索技巧小览-搜索引擎技术

    擅用搜索,是互联网时代必备的技能之一,也许将来在计算机应用的课程中,应该加入一门课程,那就是"在互联网上搜索".的确,很多人的生活和工作都已经和搜索紧密相连,如果他习惯使用的搜索引擎突然出现问题,会令他有一种无所适从的感觉.这也正是Google文化给互联网时代的人们打上的新的烙印.    2003年,搜索引擎之争将是激烈的,门户网站的搜索方式已经越来越不能满足网民的需要,以Google为代表的搜索方式将为更多的人所认同.其中中国的"百度"(www.baidu

C++数据结构与算法专题

快速排序算法的C++实现 详解qsort函数的用法 C++求二个数的最大公约数与最小公倍数实例 小览CallStack(调用栈)(三)-用调试器脚本查看调用栈信息 小览call stack(调用栈) (二)--调用约定 小览call stack(调用栈) (一) C++/CLI中栈对象的设计问题 POJ 1694 C++ (排序) 高效实现Josephus算法 利用堆排序实现学生成绩管理 C++双向循环链表的操作与实现 基于Crtpto++的RSA签名算法 自定义函数使用map排序 C++数据结