[转]关于sizeof()的一些思考

关键字:sizeof,字节对齐,多继承,虚拟继承,成员函数指针

前向声明:
    sizeof,一个其貌不扬的家伙,引无数菜鸟竟折腰,小虾我当初也没少犯迷糊,秉着“辛苦我一个,幸福千万人”的伟大思想,我决定将其尽可能详细的总结一下。
    但当我总结的时候才发现,这个问题既可以简单,又可以复杂,所以本文有的地方并不适合初学者,甚至都没有必要大作文章。但如果你想“知其然,更知其所以然”的话,那么这篇文章对你或许有所帮助。
    菜鸟我对C++的掌握尚未深入,其中不乏错误,欢迎各位指正啊

1. 定义:
    sizeof是何方神圣sizeof乃C/C++中的一个操作符(operator)是也,简单的说其作用就是返回一个对象或者类型所占的内存字节数。

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.
    其返回值类型为size_t,在头文件stddef.h中定义。这是一个依赖于编译系统的值,一般定义为
typedef unsigned int size_t;
    世上编译器林林总总,但作为一个规范,它们都会保证char、signed char和unsigned
char的sizeof值为1,毕竟char是我们编程能用的最小数据类型。

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
    既然写法3可以用写法1代替,为求形式统一以及减少我们大脑的负担,第3种写法,忘掉它吧!实际上,sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一致的。这里,对象可以进一步延伸至表达式,即sizeof可以对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,一般不会对表达式进行计算。如:
sizeof( 2 ); // 2的类型为int,所以等价于 sizeof( int );
sizeof( 2 + 3.14 );  // 3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );

    sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用,我们来看一个完整的例子:
char foo()
{
    printf("foo() has been called.\n");
    return 'a';
}
int main()
{
    size_t sz = sizeof( foo() ); // foo() 的返回值类型为char,所以sz = sizeof(char ),foo()并不会被调用
    printf("sizeof( foo() ) = %d\n", sz);
}

    C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值,即下面这些写法都是错误的:
    sizeof( foo );// error
    void foo2() { }
    sizeof( foo2() );// error
    struct S
    {
        unsigned int f1 : 1;
        unsigned int f2 : 5;
        unsigned int f3 : 12;
    };
    sizeof( S.f1 );// error

3. sizeof的常量性
    sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,如:
char ary[ sizeof( int ) * 10 ]; // ok
    最新的C99标准规定sizeof也可以在运行时刻进行计算,如下面的程序在Dev-C++中可以正确执行:
int n;
n = 10; // n动态赋值
char ary[n]; // C99也支持数组的动态定义
printf("%d\n", sizeof(ary)); // ok. 输出10
    但在没有完全实现C99标准的编译器中就行不通了,上面的代码在VC6中就通不过编译。所以我们最好还是认为sizeof是在编译期执行的,这样不会带来错误,让程序的可移植性强些。

4. 基本数据类型的sizeof
    这里的基本数据类型指short、int、long、float、double这样的简单内置数据类型,由于它们都是和系统相关的,所以在不同的系统下取值可能不同,这务必引起我们的注意,尽量不要在这方面给自己程序的移植造成麻烦。
    一般的,在32位编译环境中,sizeof(int)的取值为4。

5. 指针变量的sizeof
    学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。

char* pc = "abc";
int* pi;
string* ps;
char** ppc = &pc;
void (*pf)();// 函数指针
sizeof( pc ); // 结果为4
sizeof( pi ); // 结果为4
sizeof( ps ); // 结果为4
sizeof( ppc ); // 结果为4
sizeof( pf );// 结果为4
    指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。

6. 数组的sizeof
    数组的sizeof值等于数组所占用的内存字节数,如:
char a1[] = "abc";
int a2[3];
sizeof( a1 ); // 结果为4,字符 末尾还存在一个NULL终止符
sizeof( a2 ); // 结果为3*4=12(依赖于int)

    一些朋友刚开始时把sizeof当作了求数组元素的个数,现在,你应该知道这是不对的,那么应该怎么求数组元素的个数呢Easy,通常有下面两种写法:
int c1 = sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度
int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 总长度/第一个元素的长度

    写到这里,提一问,下面的c3,c4值应该是多少呢
void foo3(char a3[3])
{
    int c3 = sizeof( a3 ); // c3 ==
}
void foo4(char a4[])
{
    int c4 = sizeof( a4 ); // c4 ==
}
    也许当你试图回答c4的值时已经意识到c3答错了,是的,c3!=3。这里函数参数a3已不再是数组类型,而是蜕变成指针,相当于char* a3,为什么仔细想想就不难明白,我们调用函数foo1时,程序会在栈上分配一个大小为3的数组吗不会!数组是“传址”的,调用者只需将实参的地址传递过去,所以a3自然为指针类型(char*),c3的值也就为4。

7. 结构体的sizeof
    这是初学者问得最多的一个问题,所以这里有必要多费点笔墨。让我们先看一个结构体:
struct S1
{
    char c;
    int i;
};
    问sizeof(s1)等于多少聪明的你开始思考了,char占1个字节,int占4个字节,那么加起来就应该是5。是这样吗你在你机器上试过了吗也许你是对的,但很可能你是错的!VC6中按默认设置得到的结果为8。

    Why为什么受伤的总是我
    请不要沮丧,我们来好好琢磨一下sizeof的定义——sizeof的结果等于对象或者类型所占的内存字节数,好吧,那就让我们来看看S1的内存分配情况:
S1 s1 = { 'a', 0xFFFFFFFF };
    定义上面的变量后,加上断点,运行程序,观察s1所在的内存,你发现了什么
    以我的VC6.0为例,s1的地址为0x0012FF78,其数据内容如下:
    0012FF78: 61 CC CC CC FF FF FF FF

    发现了什么怎么中间夹杂了3个字节的CC看看MSDN上的说明:
When applied to a structure type or variable, sizeof returns the actual size, which may include padding bytes inserted for alignment.
    原来如此,这就是传说中的字节对齐啊!一个重要的话题出现了。
    为什么需要字节对齐计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
    让我们交换一下S1中char与int的位置:
struct S2
{
    int i;
    char c;
};
    看看sizeof(S2)的结果为多少,怎么还是8再看看内存,原来成员c后面仍然有3个填充字节,这又是为什么啊别着急,下面总结规律。

    字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
    1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
    2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
    3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

    对于上面的准则,有几点需要说明:
    1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。

    结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:
  #define offsetof(s,m) (size_t)&(((s *)0)->m)
    例如,想要获得S2中c的偏移量,方法为
size_t pos = offsetof(S2, c);// pos等于4

    2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。
    这里叙述起来有点拗口,思考起来也有点挠头,还是让我们看看例子吧(具体数值仍以VC6为例,以后不再说明):
struct S3
{
    char c1;
    S1 s;
    char c2;
};
    S1的最宽简单成员的类型为int,S3在考虑最宽简单类型成员时是将S1“打散”看的,所以S3的最宽简单类型为int,这样,通过S3定义的变量,其存储空间首地址需要被4整除,整个sizeof(S3)的值也应该被4整除。
    c1的偏移量为0,s的偏移量呢这时s是一个整体,它作为结构体变量也满足前面三个准则,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到sizeof(S3)的值为16。

    通过上面的叙述,我们可以得到一个公式:
    结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:

sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( trailing padding )

转载于:https://www.cnblogs.com/WuCountry/archive/2007/09/26/907049.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/464058.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RCF的优势--分布式应用

RCF(远程调用框架)是一个可以移植的C进程间通信框架,使用C语言特性,提供了一个简单高效的编写分布式C软件的途径。RCF利用编译时多态清晰分开了接口和实现. 和传统的RPC框架如CORBA,DCOM,.NETRemoting,WCF,WS-*,等所支持的面向对象方式不一样&#xff0c…

深漂结束

早上起来看了勇士和凯尔特人的比赛,勇士还不能适应凯尔特人的防守,除了防守之外,凯尔特人这场比赛的三分球着实太高了。说到篮球,突然想起一个刚离开深圳去惠州工作的朋友。上周二中午,吃饭的时候我打开手机&#xff0…

RS 学习笔记 3-1

3-1 fence climb onto/jump off salad towel napkin ladder 转载于:https://blog.51cto.com/mingii/776041

JS的八大数据类型

js中的数据类型,包括基本数据类型(Number,String,Boolean, Undefined,Null)和 复杂(引用)数据类型(Object,Array,Function) 基本数据类型 基本数据类型指的是简单的数据段 (Number,S…

嵌入式软件是这样debug的

【单步调试】【多线程调试】调试过多线程的都懂的。【断点调试】【递归调试】调试过递归代码的都有这种耳鸣目眩的感觉。【老旧代码调试】对于早期已经缝缝补补的庞大软件代码进行调试,还不如推倒重来。【生产中代码调试】【用户等待调试结果】【掩耳盗铃式调试】【…

可以直接在C++里面写类似RSL的shader了

可以直接在C里面写类似RSL的shader了 今天我搞定了一个新技术 可以直接在C里面写类似RSL的shader了 我简要介绍一下这个新技术,我还在考虑是否要在我们的项目中应用 这个技术的优势是可以借助现有的C编译器,将shader直接编译成机器码执行,这样…

UDT中select异常

转载:http://blog.csdn.net/seebit/article/details/6050428 select, selectEx的参数采用了stl的容器,在DLL方式下,会发生异常。 select传入的容器内的对象在DLL内部发生释放操作时,会抛出异常,因为主程序和DLL申请的内…

iOS中有两种支持机制:Notification和KVO(Key-Value Observing)

KVO是iOS中的一个核心概念,简单理解就是:关注Model某个数据(Key)的对象可以注册为监听器,一旦Model某个Key的Value发生变化,就会广播给所有的监听器(http://blog.chinabyte.com/a/1557371.html&…

Apache与Tomcat有什么关系和区别

常在用apache和tomcat等这些服务器,可是总感觉还是不清楚他们之间有什么关系,在用tomcat的时候总出现apache,总感到迷惑,到底谁是主谁是次,因此特意在网上查询了一些这方面的资料,总结了一下: …

“电脑人才”是怎么炼成的

好长一阵子没有写什么了,其实自己一直想一些电脑方面的书籍,希望给那些想要学习,而没有人指点的朋友们做个参考,或者给那些低学历的朋友做指导,总之希望把不要像我走了那么多弯路。可惜的是要生存,要工作&a…

UDT源代码下载链接

对UDT有兴趣的可以下载UDT对应的源码进行研究,下面给出了一个开源中国的连接: UDT最新源码下载地址:http://sourceforge.net/projects/udt/files/latest/download?sourcedirectory.

声音与年龄

大家好,我是写代码的篮球球痴,最近一年一直从事音频相关的工作,今天刚好看到卓老师的一篇和声音相关的文章,分享给大家。摘要:今天布置了这学期信号与系统的小论文作业。有参加了上海中学生的科技节作品评选。他们的一…

深入Django(1): 通用视图 (generic views)

如果对Django的基础部分尚不熟悉,请参考《Django实战》系列。 内容提要 1. 回顾Django的视图函数(view function) 2. 在视图函数中使用模板 3. 简化视图函数的两个工具 4. 使用通用视图函数 5. Django提供的通用视图 1. 回顾Django的视图函数…

系统字体的Regular、Light等几种名称的区别

以苹果系统中的PingFang SC系列字体为例,其中常见的有下面几种类型可以细分如下。 PingFang SC ExtraLight 苹方 特细 PingFang SC Light 苹方 细体 PingFang SC Regular 苹方 常规 PingFang SC Medium 苹方 中等 Pi…

J2EE WEBWORK FRAMEWORK安全隐患

TEAM : I.S.T.OAUTHOR : KJ021320转载需注明作者,未经作者同意,不得用于任何形式的商业活动 WebWork是由OpenSymphony组织开发的,致力于组件化和代码重用的拉出式MVC模式J2EE Web框架。框架本身就是抽象空实现的!一般很少会给程序带来0day漏洞,但是因为W…

使用 servlet 连接数据库

1.项目工程 web.xml 配置 1 <servlet> 2 <description>This is the description of my J2EE component</description> 3 <display-name>This is the display name of my J2EE component</display-name> 4 <servlet-name>DBSer…

我用涂鸦模块做的报警系统

在上次发的涂鸦的文章之后涂鸦的这套宠物SDK设计&#xff0c;真香最近刚好有一个朋友需要做一个DEMO&#xff0c;这个DEMO是和图像识别相关的&#xff0c;我这个朋友在一个烟火报警的设备&#xff0c;大家都知道我们住的小区里面都会有一个监控系统吧&#xff0c;监控系统里面是…

Lnmp环境安装禅道项目管理软件

1、本地环境 CentOS Linux release 7.5.1804 (Core) PHP 7.1.0-dev (cli) mysql Ver 14.14 Distrib 5.7.22 nginx version: nginx/1.11.1 2、下载禅道开源版安装包并安装 神道官网地址&#xff1a;https://www.zentao.net/开源代码下载地址&#xff1a;https://www.zentao.net…

二叉树的非递归操作

这里涉及到二叉树的非递归操作有&#xff1a;先序遍历、中序遍历、后序遍历数据结构树结点&#xff1a;structNode { chardata; Node *left; Node *right; };标志&#xff1a;enumTag{goLeft, goRight, goBack };goLeft指示访问左子树goLeft指示访问右子树&#xf…

UDT源码剖析(二):UDT自带例程recvfile注释

这几天学习了UDT自带的例子&#xff0c;结合源码进行了测试如下&#xff1a; #ifndef WIN32 #include <arpa/inet.h>#include <netdb.h>#else#include <winsock2.h>#include <ws2tcpip.h>#endif#include <fstream>#include <iostream>#…