详解C++ sizeof(下)

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)

例如获得S1中的偏移量,方法为

size_t pos = offsetof(S1, i); //pos等于4

1.2修改对齐方式

1.2.1#pragma pack

#pragma pack(n)中n为字节对齐数,其取值为1、2、4、8、16,默认是8。结构体对齐时,

(1)成员的偏移量为成员本身大小和n二者最小值的整数倍;
(2)结构体最终大小是结构体中最宽基本类型成员大小和n二者中的最小值的整数倍。

考察如下代码:

#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设置

//或者
#pragma pack(2)
...
#pragma pack()

因此,sizeof(S2)=sizeof(char)+pad(1)+sizeof(S1)+1+pad(1)=1+1+6+1=10字节。

注意,#pragma pack不能指定变量的存储地址,变量的首地址默认为最大基本成员类型大小的整数倍。

1.2.2__declspec(align(#))

VC++支持__declspec(align(#)),在GNU C++并不支持。#的取值为1~8192,为2的幂。使用示例如下:

__declspec(align(256)) struct TestSize
{
char a;
int i;
};
cout<<sizeof(TestSize)<<endl; //输出256

__declspec(align(#))要求#为2的整数次幂,作用主要有两个方面:
(1)使结构体或类成员按#pragma pack确定内存布局之后,在末尾填充内存使得整个对象的大小至少是#的整数倍。
(2)作用于变量时,强制要求编译器将变量放置在地址是#整数倍的内存位置上。这点在调用原生API等要求严格对齐的方法时十分重要。

1.3空结构体

C/C++中不允许长度为0的数据类型存在。对于“空结构体”(不含数据成员)的大小不为0,而是1。“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。如下:

struct S3 { };
sizeof(S3); // 结果为1

1.4位域结构体

有些信息在存储时,并不需要占用一个完整的字节, 而只需占一个或多个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位即可表示。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为”位域”或”位段”。包含位域变量的结构体叫作位域结构体。位域结构体的定义形式:

struct 位域结构体名
{
类型说明符 位域名:位域长度;
...
};

注意,位域长度不应该大于该类型说明符对应的数据类型的位长度。
使用位域的主要目的是压缩存储,其大致规则为:

(1)如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
(2)如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
(3)如果相邻位域字段的类型不同,则各编译器的具体实现有差异,VC++采取不压缩方式,GNU C++采取压缩方式;
(4)如果位域字段之间穿插着非位域字段,则不进行压缩;
(5)整个结构体的总大小为最宽基本类型成员大小的整数倍;
(6)位域可以无位域名,这时它只用作填充或调整位置,不能使用。例如:

struct BitFiledStruct
{
int a:1;
int :2; //该2位不能使用
int b:3;
int c:2;
};

关于位域结构体的sizeof大小,考察如下代码:

#include <iostream>
using namespace std;

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;
};

int main()
{
cout<<sizeof(BFS1)<<endl;
cout<<sizeof(BFS2)<<endl;
cout<<sizeof(BFS3)<<endl;
}

运行上面的程序,VC++和GNU C++输出结果如下:

//VC++输出结果
2
12
3

//GNU C++输出结果
2
4
3

考察以上代码,得出:

(1)sizeof(BFS1)==2。当相邻位域类型不同,在VC++中sizeof(BFS2)=1+pad(3)+4+1+pad(3)=12,采用不压缩方式,位域变量i的偏移量需要是4的倍数,并且位域结构体BFS2的总大小必须是sizeof(int)的整数倍。在GNU C++中为sizeof(BFS2)=4,相邻的位域字段的类型不同时,采取了压缩存储,位域变量i紧随位域变量f1的剩余位进行存储,位域变量f2同样是紧随位域变量i的剩余位进行存储,并且位域结构体BFS2的总大小必须是sizeof(int)的整数倍,所以最终结果sizeof(BFS2)=1+pad(3)=4。

(2)sizeof(BFS3)==3,当非位域字段穿插在其中,不会产生压缩,在VC++和GNU C++中得到的大小均为3,如果压缩存储,则sizeof(BFS3)==2。

2.sizeof计算共用体

结构体在内存组织上是顺序式的,共用体则是重叠式,各成员共享一段内存,所以整个共用体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是构造类型,这里,构造类型成员是被作为整体考虑的。所以,下面例子中,假设sizeof(s)的值大于sizeof(i)和sizeof(c),那么sizeof(U)等于sizeof(s)。

union U
{
int i;
char c;
S1 s;
};

3.sizeof计算类

类是C++中常用的自定义构造类型,有数据成员和成员函数组成,进行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,读者可自行推导。

以上就是详解C++ sizeof(下)的详细内容,更多关于C++ sizeof的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解C++ sizeof(上)

    sizeof是C/C++中的一个操作符(operator),其作用是返回一个对象或者类型所占的内存字节数,使用频繁,有必须对其有个全面的了解. 1.sizeof的基本语法 sizeof有三种语法形式. (1)sizeof(object); //sizeof(对象); (2)sizeof(type_name); //sizeof(类型); (3)sizeof object; //sizeof对象; 第三种语法结构虽然简约,但并不常见,为简单统一,建议使用第一和第二种写法. int i; sizeo

  • C/C++ 中sizeof('a')对比详细介绍

    C/C++ 中sizeof('a')的值对比详细介绍 C语言: char a = 'a'; sizeof(char) = 1 sizeof(a) = 1 sizeof('a') = 4 C++语言: char a = 'a'; sizeof(char) = 1 sizeof(a) = 1 sizeof('a') = 1 字符型变量是1字节这个没错,奇怪就奇怪在C语言认为'a'是4字节,而C++语言认为'a'是1字节. 原因如下: C99标准的规定,'a'叫做整型字符常量(integer char

  • 详解C++编程中的sizeof运算符与typeid运算符

    sizeof 运算符 产生与 char 类型的大小有关的操作数大小. 语法 sizeof unary-expression sizeof ( type-name ) 备注 sizeof 运算符的结果为 size_t 类型,它是包含文件 STDDEF.H 中定义的整数类型.利用此运算符,你可以避免在程序中指定依赖于计算机的数据大小. sizeof 的操作数可以是下列项之一: 类型名称.若要将 sizeof 用于类型名称,则该名称必须用括号括起. 一个表达式.当用于表达式时,无论是否使用括号都可指定

  • C/C++中的sizeof运算符和size_t类型的详解

    sizeof的作用 sizeof是c的运算符之一,用于获取操作数被分配的内存空间,以字节单位表示. 这里指的操作数,可以是变量,也可以是数据类型,如int,float等.所以就可以通过它来获取本地c库定义的基本类型的范围. sizeof的使用 1.对于一般变量,形式2种:sizeof a 或 sizeof(a); 2.对于数据类型,必须使用带括号的方式,如sizeof(int). size_t的说明 size_t是标准C库中定义的,应为unsigned int,在64位系统中为 long uns

  • C++无法重载点符号、::、sizeof等的原因

    大多数的运算符能够被程序员重载.例外的是:     . (点符号) :: ?: sizeof 并没有什么根本的原因要禁止重载?:.仅仅是因为,我没有发现有哪种特殊的情况需要重载一个三元运算符.注意一个重载了 表达式1?表达式2:表达式3 的函数,不能够保证表达式2:表达式3 中只有一个会被执行. Sizeof 不能够被重载是因为内建的操作(built-in operations),诸如对一个指向数组的指针进行增量操作,必须依靠它.考虑一下: X a[10]; X* p = &a[3]; X* q

  • C++ sizeof 实例解析

    在C++中使用sizeof要比C复杂很多,因为C++类中有static静态变量,virtual虚函数,还有继承.派生等.sizeof是C语言的一种单目操作符,如C语言的其他操作符++.--等.它并不是函数.sizeof操作符以字节形式给出了其操作数的存储大小.sizeof使用形式有三种:sizeof(var_name)或sizeof var_name或sizeof(var_type). [例1]:(列子中忽略构造及析构函数) 复制代码 代码如下: class A  {      public:

  • 浅析C/C++中被人误解的SIZEOF

    1:sizeof是一个函数吗?2:sizeof与strlen的区别?3:sizeof(int)(*p)的值是多少? 复制代码 代码如下: int a[10];   sizeof(a);//是多少?   sizeof(a[10]);//是多少?   void f(int a[10])   {     cout<<sizeof(a)<<endl;//值是多少?   }View Code 解答:1:对于第一个问题,sizeof 不是一个函数,而是一个语言内置的关键字,不信你试试sizeo

  • 详解C++ sizeof(下)

    sizeof作用于基本数据类型,在特定的平台和特定的编译器中,结果是确定的,如果使用sizeof计算构造类型:结构体.联合体和类的大小时,情况稍微复杂一些. 1.sizeof计算结构体 考察如下代码: struct S1 { char c; int i; }; cout<<"sizeof(S1)="<<sizeof(S1)<<endl; sizeof(S1)结果是8,并不是想象中的sizeof(char)+sizeof(int)=5.这是因为结构体或

  • 详解SSM框架下结合log4j、slf4j打印日志

    本文主要介绍了详解SSM框架下结合log4j.slf4j打印日志,分享给大家,具体如下: 首先加入log4j和slf4j的jar包 <!-- 日志处理 <!-- slf4j日志包--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dep

  • 详解Python多线程下的list

    list 是 Python 常用的几个基本数据类型之一.正常情况下我们会对 list 有增删改查的操作,显然易见不会有任何问题.那么如果我们试着在多线程下操作list 会有问题吗? 多线程下的 list 安全 or 不安全? 不安全! 通常我们说的线程安全是指针对某个数据结构的所有操作都是线程安全,在这种定义下,Python 常用的数据结构 list,dict,str 等都是线程不安全的 尽管多线程下的 list 是线程不安全的,但是在 append 的操作下是它又是线程安全的. 如何判断线程安

  • 详解ubuntu20.04下CLion2020.1.3安装配置ROS过程说明

    一 下载安装激活CLion 按照网上给的教程就可以 二 配置ROS 1.配置CLion的启动方式 在主目录打开隐藏文件.bashrc,命令是:sudo gedit ~/.bashrc 将CLion的启动文件clion.sh的路径设置为环境变量 PATH 这样在终端里,无论在哪个工作目录下都可以输入clion.sh即可启动CLion. 设置的代码是: export PATH=/home/zyw/CLionPack/clion-2020.1.3/bin:$PATH 这个路径是安装CLion的路径,不

  • 详解多云架构下的JAVA微服务技术解析

    微服务生态 微服务生态本质上是一种微服务架构模式的实现,包括微服务开发SDK,以及微服务基础设施. 目前比较成熟的 JAVA 微服务生态包括 servicecomb(华为), spring-cloud (Pivotal), dubbo(阿里), tsf(腾讯)等.gRPC.Thrift 等也用于内部服务之间的通信,但是微服务基础设施比较欠缺. 核心的微服务基础设施包括:注册中心.配置中心.应用网关.此外,分布式事物管理.计划任务.调用链跟踪系统等也是微服务基础设施的组成部分.完整的微服务基础实施

  • Java Fluent Mybatis 项目工程化与常规操作详解流程篇 下

    目录 前言 查询 查询写法1 查询写法2 代码说明 新问题 删 总结 前言 接着上一篇:Java Fluent Mybatis 项目工程化与常规操作详解流程篇 上 仓库地址:GitHub仓库 查询 定义查询请求体 package com.hy.fmp.dto.req; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** @

  • C语言详解关键字sizeof与unsigned及signed的用法

    目录 最冤枉的关键字sizeof理解 被误解为函数 sizeof(int)*p 表示什么意思 signed与unsigned 关键字 有符号整数vs无符号整数 整形在内存的存储 原码 反码 补码 存储的本质 十进制二进制快速转化 为什么存储的是补码 大小端 最冤枉的关键字sizeof理解 sizeof:确定一种类型在开辟空间的时候的大小. 被误解为函数 sizeof是关键字而不是函数,可以借助编译器来确定它的身份. #include<stdio.h> int main() { int a =

  • 详解CentOS5.5 下搭建 PHP 环境(最佳的LAMP环境)

    本文详细阐述在 Linux 系统中搭建 PHP 环境,由于 PHP 就是由 C 语言编写的,最初也是运行在 Linux 系统中,所以Linux 是 PHP 的最佳环境. 关于本文中使用到的软件,请点击此链接下载. CentOS5.5现在官方已经不再提供,推荐大家使用centos6以上版本: centos6.8下载地址:http://www.jb51.net/softs/499124.html centos7.2下载地址:http://www.jb51.net/softs/499109.html

  • 详解Ubuntu16.04下Hadoop 2.7.3的安装与配置

    一.Java环境搭建 (1)下载JDK并解压(当前操作系统为Ubuntu16.04,jdk版本为jdk-8u111-Linux-x64.tar.gz) 新建/usr/java目录,切换到jdk-8u111-linux-x64.tar.gz所在目录,将这个文件解压缩到/usr/java目录下. tar -zxvf jdk-8u101-linux-x64.tar.gz -C /usr/java/ (2)设置环境变量 修改.bashrc,在最后一行写入下列内容. sudo vim ~/.bashrc

随机推荐