服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - 实例讲解C语言编程中的结构体对齐

实例讲解C语言编程中的结构体对齐

2021-03-30 15:47阿凡卢 C/C++

这篇文章主要介绍了C语言编程中的结构体对齐,值得注意的是一些结构体对齐的例子在不同编译器下结果可能会不同,需要的朋友可以参考下

Q:关于结构体的对齐,到底遵循什么原则?
A:首先先不讨论结构体按多少字节对齐,先看看只以1字节对齐的情况:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>
 
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member) ((char *)&((struct *)0)->member - (char *)0)
 
#pragma pack(1)
 
typedef struct
{
  char  sex;
  short  score;
  int   age;
}student;
 
int main()
{
  PRINT_D(sizeof(student))
  PRINT_D(OFFSET(student,sex))
  PRINT_D(OFFSET(student,score))
  PRINT_D(OFFSET(student,age))
  return 0;
}

输出:

?
1
2
3
4
sizeof(student) is 7
OFFSET(student,sex) is 0
OFFSET(student,score) is 1
OFFSET(student,age) is 3

可以看到,如果按1字节对齐,那么结构体内部的成员紧密排列,sizeof(char) == 1, sizeof(short) == 2, sizeof(int) == 4.

修改上面的代码, 去掉#pragma pack语句,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
 
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member) ((char *)&((struct *)0)->member - (char *)0)
 
typedef struct
{
  char  sex;
  short  score;
  int   age;
}student;
 
int main()
{
  PRINT_D(sizeof(student))
  PRINT_D(OFFSET(student,sex))
  PRINT_D(OFFSET(student,score))
  PRINT_D(OFFSET(student,age))
  return 0;
}

运行结果:

?
1
2
3
4
sizeof(student) is 8
OFFSET(student,sex) is 0
OFFSET(student,score) is 2
OFFSET(student,age) is 4

此时,各个成员之间就不像之前那样紧密排列了,而是有一些缝隙。这里需要介绍下对齐原则:

此原则是在没有#pragma pack语句作用时的原则(不同平台可能会有不同):

原则A:struct或者union的成员,第一个成员在偏移0的位置,之后的每个成员的起始位置必须是当前成员大小的整数倍;

原则B:如果结构体A含有结构体成员B,那么B的起始位置必须是B中最大元素大小整数倍地址;

原则C:结构体的总大小,必须是内部最大成员的整数倍;

依据上面3个原则,我们来具体分析下: sex在偏移0处,占1字节;score是short类型,占2字节,score必须以2的整数倍为起始位置,所以它的起始位置为2; age为int类型,大小为4字节,它必须以4的整数倍为起始位置,因为前面有sex占1字节,填充的1字节和score占2字节,地址4已经是4的整数倍,所以age的位置为4.最后,总大小为4的倍数,不用继续填充。

继续修改上面的代码,增加#pragma pack语句:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>
 
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct, member) ((char *)&((struct *)0)->member - (char *)0)
 
#pragma pack(4)
 
typedef struct
{
  char  sex;
  short  score;
  int   age;
}student;
 
int main()
{
  PRINT_D(sizeof(student))
  PRINT_D(OFFSET(student,sex))
  PRINT_D(OFFSET(student,score))
  PRINT_D(OFFSET(student,age))
  return 0;
}

运行结果:

?
1
2
3
4
sizeof(student) is 8
OFFSET(student,sex) is 0
OFFSET(student,score) is 2
OFFSET(student,age) is 4

具体分析下:

有了#pragma pack(4)语句后,之前说的原则A和C就不适用了。实际对齐原则是自身对齐值(成员sizeof大小)和指定对齐值(#pragma pack指定的对齐大小)的较小者。依次原则,sex依然偏移为0, 自身对齐值为1,指定对齐值为4,所以实际对齐为1; score成员自身对齐值为2,指定对齐值为4,实际对齐为2;所以前面的sex后面将填充一个1字节,然后是score的位置,它的偏移为2;age自身对齐值为4,指定对齐为4,所以实际对齐值为4;前面的sex和score正好占用4字节,所以age接着存放;它的偏移为4.

Q:关于位域的问题,空域到底表示什么?
A:它表示之后的位域从新空间开始。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>
 
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct, member) ((char *)&((struct *)0)->member - (char *)0)
 
typedef struct
{
  int a : 1;
  int b : 3;
  int : 0;
  int d : 2;
}bit_info;
 
int main()
{
  PRINT_D(sizeof(bit_info))
  return 0;
}

运行结果:

?
1
sizeof(bit_info) is 8

bit_info中的a, b占用4个字节的前4位,到int:0; 时表示此时将填充余下所有没有填充的位,即刚刚的4个字节的余下28位;int d:2; 将从第四个字节开始填充,又会占用4个字节,所以总大小为8.

再来看下面几个小例子
例1:

?
1
2
3
4
5
struct A{
        char f1 : 3;
        char f2 : 4;
        char f3 : 5;
    };

                    a      b          c
A的内存布局:111,1111 *,11111 * * *
位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2。
例2:

?
1
2
3
4
5
struct B{
        char f1 : 3;
        short f2 : 4;
        char f3 : 5;
    };

由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。
例3:

?
1
2
3
4
5
struct C{
        char f1 : 3;
        char f2;
        char f3 : 5;
    };

非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。
考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长为w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。有兴趣的可以google一下,人家就可以跟你解释的,一大堆的道理。
最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。

延伸 · 阅读

精彩推荐
  • C/C++关于C语言中E-R图的详解

    关于C语言中E-R图的详解

    今天小编就为大家分享一篇关于关于C语言中E-R图的详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看...

    Struggler095962021-07-12
  • C/C++c/c++实现获取域名的IP地址

    c/c++实现获取域名的IP地址

    本文给大家汇总介绍了使用c/c++实现获取域名的IP地址的几种方法以及这些方法的核心函数gethostbyname的详细用法,非常的实用,有需要的小伙伴可以参考下...

    C++教程网10262021-03-16
  • C/C++深入C++拷贝构造函数的总结详解

    深入C++拷贝构造函数的总结详解

    本篇文章是对C++中拷贝构造函数进行了总结与介绍。需要的朋友参考下...

    C++教程网5182020-11-30
  • C/C++OpenCV实现拼接图像的简单方法

    OpenCV实现拼接图像的简单方法

    这篇文章主要为大家详细介绍了OpenCV实现拼接图像的简单方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    iteye_183805102021-07-29
  • C/C++使用C++制作简单的web服务器(续)

    使用C++制作简单的web服务器(续)

    本文承接上文《使用C++制作简单的web服务器》,把web服务器做的功能稍微强大些,主要增加的功能是从文件中读取网页并返回给客户端,而不是把网页代码...

    C++教程网5492021-02-22
  • C/C++c/c++内存分配大小实例讲解

    c/c++内存分配大小实例讲解

    在本篇文章里小编给大家整理了一篇关于c/c++内存分配大小实例讲解内容,有需要的朋友们可以跟着学习参考下。...

    jihite5172022-02-22
  • C/C++C语言实现双人五子棋游戏

    C语言实现双人五子棋游戏

    这篇文章主要为大家详细介绍了C语言实现双人五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    两片空白7312021-11-12
  • C/C++C语言main函数的三种形式实例详解

    C语言main函数的三种形式实例详解

    这篇文章主要介绍了 C语言main函数的三种形式实例详解的相关资料,需要的朋友可以参考下...

    ieearth6912021-05-16