原文:http://krystism.is-programmer.com/posts/41468.html

学过c的都知道sizeof运算符。不过还是需要注意以下几点。先从c的sizeof说起:

1. sizeof 是运算符,而不是函数。虽然我们习惯sizeof(...),但( )并不是必需的,它只是表示优先级。我们把sizeof后面的目标叫对象或者操作数。本文约定就叫sizeof对象。

2. 当sizeof 的对象是表达式时,求的大小是表达式返回值的类型大小,但并不计算表达式的值,比如

1
2
3
4
char c = 1;
int i = 2;
cout << sizeof(c + i) << endl;
cout << sizeof(c = c + i) << endl;

前者c + i会隐式类型转化为int类型(类型提升),因此返回4(32位系统), 而后者虽然运算时也是转化为int,但赋值给c时又会转化为char,因此返回的是1。同样如果对象是函数,则返回函数返回值类型大小,如:

1
2
3
4
5
6
7
8
9
10
11
long long foo()
{
    printf("'%s' has been called.\n", __func__);
    return 0;
}
int main(int argc, char **argv)
{
     
    cout << sizeof(foo()) << endl;
    return 0;
}

执行后输出8, 不会输出 'foo' has been called.说明函数没有真正执行,而只是判断了下返回类型。

3.注意sizeof 对象是指针和数组的区别。

当sizeof的对象是数组时,返回数组总大小,而当对象是指针时,返回指针本身的大小,而不是指示内存空间的大小。因为指针本身就是一个无符号整型数,因此int *p ,sizeof(p)返回的大小是sizeof(void *), 32 位系统返回4,即32位。但注意当数组名作为实参传入函数时,会自动转化为指针类型,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void foo(int a[])
{
    cout << sizeof(a) << endl; /* 4 */
}
int main(int argc, char **argv)
{
    int a[] = {1, 2, 3, 4};
    int *p = a;
    cout << sizeof(a) << endl; /* 16 */
    cout << sizeof(p) << endl; /* 4 */
    foo(a);
    return 0;
}

4. sizeof 无法获取动态分配的内存大小,即使用malloc动态的分配内存,无法使用sizeof获取其大小。

5. 注意c_style字符串末尾有一个\0结束符,也需要占一个char空间,因此sizeof("1") 返回2。而strlen返回的是字符数,不包括\0结束符。

6.关于结构体类型。

理论上一个结构体所占空间是所有成员的大小总和,但由于考虑到对齐问题,会有填充字节。

1
2
3
4
5
struct node
{
    int a;
    char c;
};

大小为8字节而不是5字节,填充了3字节。

注意:c语言中空struct大小为0, 而c++中空struct 大小为1, 具体看后面关于空类的讨论。另外,c99中结构体后面的动态数组,即不指定大小的数组,sizeof 时不包括动态数组的大小,即

1
2
3
4
5
6
struct node
{
    int a;
    char c;
    int d[];
};

返回依然是8。

下面关于c++类的讨论。除了struct ,以上讨论关于c的sizeof同样适合于c++。首先说说c++ 中的struct类型,注意和c中的struct是不一样的,c中的struct只是一种把各种基本数据类型包装的组合类型,而c++的struct本质上是类,即类有的东西,struct基本都有,即struct也有构造函数、析构函数、成员函数等等,不过它的默认成员是public的,而class定义的类成员默认是private的。另外,struct继承默认也是public,而class定义的类默认是private。另外注意:class可以定义模板参数,但struct不可以!因此,struct本质就是类。

下面主要讨论类的大小:

1. 空类的大小。空类型实例中不包含任何信息,应该大小为0. 但是当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。g++中每个空类型的实例占1字节空间。注意空struct即空类,这就是为什么c++的空struct占一个字节的原因。

2. 构造函数、析构函数、成员函数调用时只需知道函数地址即可,而这些函数的地址之与类型相关,而与具体的实例无关,因此不会在实例中额外添加任何信息。

3. 静态数据成员放在全局数据成员中,它不占类实例大小,多个类实例只有一个实体。可以看作是一种特殊的全局变量。

综上1,2,3:

1
2
3
4
5
6
7
8
9
class A
{
    public:
        static int a;
        static char c;
        A(){};
        ~A(){};
        void foo(){};
};

类A的大小为1字节,等于空类大小,因此静态数据成员a,c和成员函数都不占类的大小。

4. 类的非静态数据成员和c语言中的struct类似,也需要对齐,可能需要字节填充。

1
2
3
4
5
6
7
8
9
class A
{
    public:
        int a;
        char c;
        A(){};
        ~A(){};
        void foo(){};
};

类A的大小为8字节,a占4B,c占1B,填充3B。

5. 如果一个类中有虚函数,则该类型会生成一个虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针,因此类大小必须加上一个指针所占的空间。如果是普通继承,子类和基类共享这个指针。

1
2
3
4
5
6
7
8
9
10
class A
{
    public:
        int a;
        char c;
        A(){};
        ~A(){};
        void foo(){};
        void virtual bar(){};
};

类A的大小为12B。数据成员8B,加上指向虚拟函数表的指针。注意,是在32位系统上。如果是64位机器,一个指针占8B。

6.虚继承时,派生类会生成一个指向虚基类表的指针,占一个指针大小空间。如果还有虚函数,不增加额外指针大小空间,原因不太清楚,如果谁知道,请一定要告诉我!如下:

1
2
3
4
5
6
7
8
9
class A
{
    int a;
};
class B: public virtual A
{
    int b;
    virtual void foo(){};
};

类B的大小为12B,数据成员b占4B,从A中继承a也占4B,另外一个由于virtual存在,额外加一个指针大小4B,共12B。所以:只要有virtual,无论是在成员函数,还是在继承上,都额外加一个指针大小空间。

基本就这些了,如果有纰漏,请指出,谢谢!

c/c++ sizeof运算符详解以及对象大小的更多相关文章

  1. js对象详解(JavaScript对象深度剖析,深度理解js对象)

    js对象详解(JavaScript对象深度剖析,深度理解js对象) 这算是酝酿很久的一篇文章了. JavaScript作为一个基于对象(没有类的概念)的语言,从入门到精通到放弃一直会被对象这个问题围绕 ...

  2. Net is as typeof 运行运算符详解 net 自定义泛型那点事

    Net is as typeof 运行运算符详解   概述 在了解运行运算符的前提我们需要了解什么是RTTI ,在任何一门面向对象的语言中,都有RTTI这个概念(即 运行时). RTTI(Run-Ti ...

  3. php开发面试题---php面向对象详解(对象的主要三个特性)

    php开发面试题---php面向对象详解(对象的主要三个特性) 一.总结 一句话总结: 对象的行为:可以对 对象施加那些操作,开灯,关灯就是行为. 对象的形态:当施加那些方法是对象如何响应,颜色,尺寸 ...

  4. dom对象详解--document对象(二)

       dom对象详解--style对象 style对象 style对象和document对象下的集合对象styleSheets有关系,styleSheets是文档中所有style对象的集合,这里讲解的 ...

  5. dom对象详解--document对象(一)

     document对象 Document对象代表整个html文档,可用来访问页面中的所有元素,是最复杂的一个dom对象,可以说是学习好dom编程的关键所在. Document对象是window对象的一 ...

  6. Struts功能详解——ActionMapping对象

    Struts功能详解——ActionMapping对象 ActionMapping描述了struts中用户请求路径和Action的映射关系,在struts中每个ActionMapping都是通过pat ...

  7. linux dd命令参数及用法详解---用指定大小的块拷贝一个文件(也可整盘备份)

    linux dd命令参数及用法详解---用指定大小的块拷贝一个文件 日期:2010-06-14 点击:3830 来源: 未知 分享至:            linux dd命令使用详解 dd 的主要 ...

  8. JavaScript逻辑and、or、not运算符详解

    一.AND详解: 在JavaScript中,逻辑 AND 运算符用双和号(&&)表示. 需要说明的是:逻辑AND运算的运算数可以是任何类型的,不止是Boolean值,如果某个运算数不是 ...

  9. 18.Java 封装详解/多态详解/类对象转型详解

    封装概述 简述 封装是面向对象的三大特征之一. 封装优点 提高代码的安全性. 提高代码的复用性. "高内聚":封装细节,便于修改内部代码,提高可维护性. "低耦合&quo ...

随机推荐

  1. 设置Azure WebSite黑白名单

    Azure WebSite服务默认是不提供黑白名单,也就是说任何Internet用户都可以访问Azure WebSite,那么我们如何来给我们的网站设置黑白名单? 这里有一种方式,可以通过配置网站的配 ...

  2. ARM DEBUGGER FOR NEARLY ONE DOLLAR

    http://hackaday.com/2014/01/23/arm-debugger-for-nearly-one-dollar/ Oh that title is so misleading. B ...

  3. Lodop客户端本地和集中打印 [是否安装][操作系统]

    前面有些博文或图片介绍了这两种方式,可能文字太多,耐心看完的人不多,这里简略描述+图片的方式解释一下. 客户端本地打印: 用户访问某网站,用自己本地的打印机打出来. 集中打印方式: 用户访问某网页,用 ...

  4. quartz 2.0 与1.0功能对比

    日常开发来说,相对于1.0版,2.0版在使用上有以下几点需要注意的变化 变化一 比1.0多引用了C5.dll C5.dll 一个C#和其他CLI语言的泛型集合类..Net2.0及以上才可以使用.简介地 ...

  5. redis哈希缓存数据表

    redis哈希缓存数据表 REDIS HASH可以用来缓存数据表的数据,以后可以从REDIS内存数据库中读取数据. 从内存中取数,无疑是很快的. var FRedis: IRedisClient; F ...

  6. C#编程(二十二)----------继承的类型

    继承的类型 在面向对象的编程中,有两种截然不同的集成类型:实现继承和接口继承 实现继承:表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数.在实现继承中,派生类型采用基类型的每个函数的实 ...

  7. .NET:强签名程序集的加载问题 之 版本重定向

    背景 多数解决方案会包含多个项目,某些支持插件架构的解决方案中,更是包含多个插件项目,这些项目会使用一些第三方NuGet Packages,如果管理不慎,解决方案中会出现多个版本的引用,这在编译期间不 ...

  8. Linux学习7-tomcat部署多个项目(多个端口)

    前言 前面已经在tomcat上搭建了jenkins的环境,如果我们有多个项目需要部署的话,如何在一个tomcat下部署多个项目呢? 前面是直接在:8080/jenkins访问的,如果有其它项目部署的话 ...

  9. 清除和新建WordPress数据库

    由于在本地已经安装过一次wordpress.所以假设第二次安装的时候 localhost/wp-admin/install.php 会显示你已经安装过了.所以须要把MySQL的数据库信息清除掉,或者另 ...

  10. 手机端可以和PC端同时在线-java QRCode 实现网站扫码登录(即支持同帐号多设备同时登录)

    微信扫码测试地址:: http://sms.reyo.cn 用户名:aa 密码:123456 扫码登录实现方式很多,比如ajax轮询,http长连接(comet...),websocket,event ...