全面解析sizeof(下) 分类: C/C++ StudyNotes 2015-06-15 10:45 263人阅读 评论(0) 收藏
以下代码使用平台是Windows7 64bits+VS2012。
sizeof作用于基本数据类型,在特定的平台和特定的编译中,结果是确定的,如果使用sizeof计算构造类型:结构体、联合体和类的大小时,情况稍微复杂一下。
1.sizeof计算结构体
考察如下代码:
struct S1
{
    char c;
    int i;
};
cout<<”sizeof(S1)=”<<sizeof(S1)<<endl;
sizeof(S1)结果是8,并不是想象中的sizeof(char)+sizeof(int)=5。这是因为结构体或者类成员变量具有不同类型时,需要进程成员变量的对齐,计算机组成原理中说明,对齐的目的是减少访存指令周期,提高CPU存储速度。
1.1内存对齐的原则
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 
(2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节; 
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。有了以上三个内存对齐的原则,就可以轻松应对嵌套结构体类型的内存对齐。如下:
struct S2
{
    char c1;
    S1 s;
    char c2
};
在寻找S2的最宽基本数据类型时,包括其嵌套的结构体中的成员,从S1中寻找出最宽结构体数据类型是int,因此S2的最宽数据类型是int。S1 s在结构体S2中的对齐也是按前面三个准则进行,因此sizeof(S2)=sizeof(char)+pad(3)+sizeof(S1)+1+pad(3)=1+3+8+1+3=16字节,其中pad(3)表示填充3个字节。 
结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下: 
    #define offsetof(s,m) (size_t)&(((s *)0)->m) 
例如,想要获得S2中c的偏移量,方法为 
    size_t pos = offsetof(S2, c); // pos等于4
1.2预处理编译器指导指令#pragma pack
#pragma pack( n ),n为字节对齐数,其取值为1、2、4、8、16,默认是8,表明最宽数据类型不超过8。如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值,公式如下: 
    offsetof( item ) = min( n, sizeof( item ) )。 
考察如下代码:
#pragma pack(push) // 将当前pack设置压栈保存
#pragma pack(2) // 必须在结构体定义之前使用
struct S1
{
    char c;
    int i;
};
struct S2
{
    char c1;
    S1 s;
    char c2
};
#pragma pack(pop) // 恢复先前的pack设置
因此,sizeof(S2)=sizeof(char)+pad(1)+sizeof(S1)+1+pad(1)=1+1+6+1=10字节。
1.3空结构体
C/C++中不允许长度为0的数据类型存在。对于“空结构体”(不含数据成员)的大小不为0,而是1。“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。如下:
struct S3 { };
sizeof( S3 ); // 结果为1
1.4位域结构体
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为”位域”或”位段”。以位域变量成员变量结构体的叫做位域结构体。 
位域结构体的定义形式: 
struct 位域结构名  
{ 位域列表 }; 
位域列表的形式为: 类型说明符 位域名:位域长度。 
使用位域的主要目的是压缩存储,其大致规则为: 
(1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止; 
(2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 
(3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式; 
(4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 
(5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。 
(6)位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。 
例如:
struct k {
    int a:1
    int :2 //该2位不能使用
    int b:3 int c:2
 };
考察如下代码:
struct BFS1
{
    char f1 : 3;
    char f2 : 4;
    char f3 : 5;
};
struct BFS2
{
    char f1 : 3;
    int i : 4;
    char f2 : 5;
};
struct BFS3
{
    char f1 : 3;
    char f2;
    char f3 : 5;
};
考察以上代码,得出: 
(1)sizeof(BFS1)==2。当相邻位域类型不同,在VS中其sizeof(BFS2)=1+4+1+pad(2)=8,位域变量i的偏移量无需是4的倍数,紧随f1的后一字节存储,但位域结构体BFS2的总大小必须是sizeof(int)的整数倍。在Dev-C++中为sizeof(BFS2)=2,相邻的位域字段的类型不同时,采取了压缩存储。 
(2)sizeof(BFS3)==3,当非位域字段穿插在其中,不会产生压缩,在VS和Dev-C++中得到的大小均为3。
2.sizeof计算联合体
结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是构造类型,这里,构造类型成员是被作为整体考虑的。所以,下面例子中,U的sizeof值等于sizeof(s)。
union U
{
    int i;
    char c;
    S1 s;
};
3.sizeof计算类
考察如下代码:
#include <iostream>
using namespace std;
class Small{};
class LessFunc{
int num;
void func1(){};
};
class MoreFunc{
int num;
void func1(){};
int func2(){return 1;};
};
class NeedAlign{
char c;
double d;
int i;
};
class Virtual{
int num;
virtual void func(){};
};
int main(int argc,char* argv[])
{
    cout<<sizeof(Small)<<endl;   //输出1
    cout<<sizeof(LessFunc)<<endl;//输出4
    cout<<sizeof(MoreFunc)<<endl;//输出4
    cout<<sizeof(NeedAlign)<<endl;//输出24
    cout<<sizeof(Virtual)<<endl; //输出8
    return 0;
}
注意一点,C++中类同结构体没有本质的区别,结构体同样可以包含成员函数,构造函数,析构函数,虚函数和继承,但一般不这么使用,沿用了C的结构体使用习惯。类与结构体唯一的区别就是结构体的成员的默认权限是public,而类是private。 
基于以上这点,再考察从程序的输出结果,得出如下结论: 
(1)类同结构体一样,C++中不允许长度为0的数据类型存在,虽然类无任何成员,但该类的对象仍然占用1个字节。 
(2)类的成员函数并不影响类对象占用的空间,类对象的大小是由它数据成员决定的。 
(3)类和结构体一样,同样需要对齐,具体对齐的规则见上文结构体的内存对齐。 
(4)类如果包含虚函数,编译器会在类对象中插入一个指向虚函数表的指针,以帮助实 
现虚函数的动态调用。所以,该类的对象的大小至少比不包含虚函数时多4个字节。如果考虑内存对齐,可能还要多些。如果使用数据成员之间的对齐,当类对象至少包含一个数据成员,且拥有虚函数,那么该对象的大小至少是8B,读者可自行推导。
参考文献
[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008. 
[2]http://blog.csdn.net/freefalcon/article/details/54839
版权声明:本文为博主原创文章,未经博主允许不得转载。
全面解析sizeof(下) 分类: C/C++ StudyNotes 2015-06-15 10:45 263人阅读 评论(0) 收藏的更多相关文章
- NYOJ-86 找球号(一)AC                                                    分类:            NYOJ             2014-02-02 10:45    160人阅读    评论(0)    收藏
		
NO.1 单纯的傻傻的代码: #include<stdio.h> long long num[100000005]={0}; int main(){ int n, m, k; scanf( ...
 - 树莓派(raspberry)启用root账户                                                    分类:            服务器搭建             Raspberry Pi             2015-04-12 18:45    95人阅读    评论(0)    收藏
		
树莓派使用的linux是debian系统,所以树莓派启用root和debian是相同的. debian里root账户默认没有密码,但账户锁定. 当需要root权限时,由默认账户经由sudo执行,Ras ...
 - ubuntu14.04使用root用户登录桌面                                                    分类:            学习笔记             linux             ubuntu             2015-07-05 10:30    199人阅读    评论(0)    收藏
		
ubuntu安装好之后,默认是不能用root用户登录桌面的,只能使用普通用户或者访客登录.怎样开启root用户登录桌面呢? 先用普通用户登录,然后切换到root用户,然后执行如下命令: vi /usr ...
 - 窗体控件 回车事件                                                    分类:            WinForm             2014-11-21 10:45    233人阅读    评论(0)    收藏
		
说明: (1)设置窗体控件的TabIndex属性,(按回车顺序设置TabIndex的大小) (2)修改窗体的一个属性:KeyPreview=true //protected override void ...
 - iOS越狱包                                                    分类:            ios相关             app相关             2015-06-10 10:53    152人阅读    评论(0)    收藏
		
编译完了的程序是xxx.app文件夹,我们需要制作成ipa安装包,方便安装 找一个不大于500*500的png图片(程序icon图标即可),改名为:iTunesArtwork,注意不能有后缀名. 建立 ...
 - 全面解析sizeof(上)                                                    分类:            C/C++             StudyNotes             2015-06-15 10:18    188人阅读    评论(0)    收藏
		
以下代码使用平台是Windows7 64bits+VS2012. sizeof是C/C++中的一个操作符(operator),其作用就是返回一个对象或者类型所占的内存字节数,使用频繁,有必须对齐有个全 ...
 - Windows中的DNS服务——正向解析&反向解析配置                                                       分类:            AD域             Windows服务             2015-07-16 20:21    19人阅读    评论(0)    收藏
		
坚信并为之坚持是一切希望的原因. DNS服务是AD域不可或缺的一部分,我们在部署AD域环境时已经搭建了DNS服务(windows server 2008 R2域中的DC部署),但是DNS服务的作用还是 ...
 - C#多线程(下)                                                    分类:            C# 线程             2015-03-09 10:41    153人阅读    评论(0)    收藏
		
四.多线程的自动管理(线程池) 在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPool(线 ...
 - __int64 与long long 的区别                                                    分类:            Brush Mode             2014-08-14 10:22    64人阅读    评论(0)    收藏
		
//为了和DSP兼容,TSint64和TUint64设置成TSint40和TUint40一样的数 //结果VC中还是认为是32位的,显然不合适 //typedef signed long int ...
 
随机推荐
- Ubuntu下mysql-server的安装
			
(1)更新 #apt-get update (2)安装 #apt-get install mysql-server 出现窗口设置"root"用户的密码为"456456&q ...
 - php 错误处理函数
			
eval() 把子符串当做php 代码执行 // 回调函数function a($b, $c) { echo $b; echo $c; } call_user_func_array('a', ar ...
 - php web系统多域名登录失败解决方法
			
下面只是简单的逻辑结构,对于正式的系统需要做具体的处理. 这里需要注意的是:加解密一定需要做安全验证.但是这个方法也不够完美,两个站点必须有相同一级域名:另外这种完全基于cookie的方式,安全性不够 ...
 - C语言文法分析
			
程序 → <外部声明>|<程序><外部声明> <外部声明> → <函数定义> | <声明> <函数定义> → < ...
 - 如何解决Selenium中"Cannot find function addEventListener in object [object HTMLDocument]"的错误
			
project: blog target: how-to-resolve-cannot-find-function-addEventListener-error-in-selenium.md stat ...
 - hash表C语言实现
			
算法参考<算法导论>第11章散列表.采用链地址法解决冲突. #include <stdio.h> #include <stdlib.h> #include < ...
 - js-innerHTML
			
innerHTML的使用: 首先看一下这个单词的表面意思:inner是内部.内部的:HTML相信大家都懂. 那么,innerHTML的意思就是设置xxxx的内部内容,并且识别HTML的标签.用法格式: ...
 - 【转】IP协议详解之子网寻址、子网掩码、构造超网
			
子网寻址 1. 从两级IP地址到三级IP地址 <1>. IP地址利用率有时很低. <2>. 给每一个物理网络分配一个网络号会使路由表变得太大而使网络性能变坏. <3> ...
 - 数据挖掘系列(1)关联规则挖掘基本概念与Aprior算法
			
整理数据挖掘的基本概念和算法,包括关联规则挖掘.分类.聚类的常用算法,敬请期待.今天讲的是关联规则挖掘的最基本的知识. 关联规则挖掘在电商.零售.大气物理.生物医学已经有了广泛的应用,本篇文章将介绍一 ...
 - 硬件初始化,nand flash固化操作,系统启动简单流程
			
2015.3.27星期五 晴 链接脚本定义代码的排放顺序 硬件系统初始化:一:arm核初始化:(里面有指令)初始化ARM核的时候需要看arm核的手册指令:1.异常向量(最起码有个复位异常,初始化模式- ...