这个结构体对齐输出有意思

共 4119字,需浏览 9分钟

 ·

2020-08-03 08:04

这个题目是我在群里看到大家讨论的,既然是讨论的了,那我就拿出来说说,因为笔试面试的时候,可能就会遇到这样的题目。

实例代码

#include "stdio.h"
#include "stdint.h"

struct Obj {
    char a; //1
    uint32_t b;//4
    uint8_t c;//1
    uint64_t d[0];//8
};

int main()
{
 struct Obj Op;

 printf("%d %d\n",sizeof(Op),sizeof(Op.d));
 return (0);

程序输出

16 0

--------------------------------
Process exited after 0.03048 seconds with return value 0
请按任意键继续. . .

这里比较令我们疑惑的是,d 的大小明明是 0,为甚结构体的大小会是 16呢?

调戏一下

看看下面代码的输出

#include "stdio.h"
#include "stdint.h"

#define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member)  ((char *)&((struct *)0)->member - (char *)0)

#pragma pack(1)

struct Obj {
    char a; 
    uint32_t b;
    uint8_t c;
    uint64_t d[0];
};

int main()
{
 struct Obj Op;
 PRINT_D(OFFSET(struct Obj,a)); 
 PRINT_D(OFFSET(struct Obj,b)); 
 PRINT_D(OFFSET(struct Obj,c)); 
 PRINT_D(OFFSET(struct Obj,d)); 
 printf("%d %d\n",sizeof(Op),sizeof(Op.d));
 return (0);

程序输出

OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 1
OFFSET(struct Obj,c) is 5
OFFSET(struct Obj,d) is 6
6 0

--------------------------------
Process exited after 0.0108 seconds with return value 0
请按任意键继续. . .

这里的输出刚好是我们的结构体里内容的大小

解释下这段调试代码的作用

#define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member)  ((char *)&((struct *)0)->member - (char *)0)

前面哪个比较简单了,就是使用 「#」这个符号把字符串带过来打印。下面的OFFSET 比较有意思,先是把 0 这个地址强制转换成我们需要的strunct ,然后呢,再读取地址减去0地址,这样就可以得出它的偏移大小了。

调戏一下2

我们改一下代码

#include "stdio.h"
#include "stdint.h"

#define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member)  ((char *)&((struct *)0)->member - (char *)0)

#pragma pack(4)

struct Obj {
    char a; 
    uint32_t b;
    uint8_t c;
    uint64_t d[0];
};

int main()
{
 struct Obj Op;
 PRINT_D(OFFSET(struct Obj,a)); 
 PRINT_D(OFFSET(struct Obj,b)); 
 PRINT_D(OFFSET(struct Obj,c)); 
 PRINT_D(OFFSET(struct Obj,d)); 
 printf("%d %d\n",sizeof(Op),sizeof(Op.d));
 return (0);

程序输出

OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 4
OFFSET(struct Obj,c) is 8
OFFSET(struct Obj,d) is 12
12 0

--------------------------------
Process exited after 0.01165 seconds with return value 0
请按任意键继续. . .

调戏代码3

#include "stdio.h"
#include "stdint.h"

#define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member)  ((char *)&((struct *)0)->member - (char *)0)

#pragma pack(8)

struct Obj {
    char a; 
    uint32_t b;
    uint8_t c;
    uint64_t d[0];
};

int main()
{
 struct Obj Op;
 PRINT_D(OFFSET(struct Obj,a)); 
 PRINT_D(OFFSET(struct Obj,b)); 
 PRINT_D(OFFSET(struct Obj,c)); 
 PRINT_D(OFFSET(struct Obj,d)); 
 printf("%d %d\n",sizeof(Op),sizeof(Op.d));
 return (0);

程序输出

OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 4
OFFSET(struct Obj,c) is 8
OFFSET(struct Obj,d) is 16
16 0

--------------------------------
Process exited after 0.01219 seconds with return value 0
请按任意键继续. . .

结构体对齐大小的方式,这个背下,不背下就存下

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

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

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

这几个原则是在 没有 #pragma pack 的时候才起作用的,有了 #pragma pack,就按照 #pragma pack 的方式去对齐。

分析一下

基于上面的实验和理论,我们可以知道,这个笔试题的输出结果是因为 uint64_t d[] 这个搞鬼了,就是因为这个搞鬼了,我们结构体的最终大小才是 16。因为 uint64_t d 是 8个字节,这样结构体就是以 8 字节的方式对齐了。

虽然d 的不占用内存的,但是这个家伙的存在让结构体的对齐方式产生了改变,就是这么神奇。

为了验证我们的想法,我们改下程序

实例代码

#include "stdio.h"
#include "stdint.h"

#define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member)  ((char *)&((struct *)0)->member - (char *)0)

struct Obj {
    char a; 
    uint32_t b;
    uint8_t c;
    uint32_t d[0];
};

int main()
{
 struct Obj Op;
 PRINT_D(OFFSET(struct Obj,a)); 
 PRINT_D(OFFSET(struct Obj,b)); 
 PRINT_D(OFFSET(struct Obj,c)); 
 PRINT_D(OFFSET(struct Obj,d)); 
 printf("%d %d\n",sizeof(Op),sizeof(Op.d));
 return (0);

程序输出

OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 4
OFFSET(struct Obj,c) is 8
OFFSET(struct Obj,d) is 12
12 0

--------------------------------
Process exited after 0.01002 seconds with return value 0
请按任意键继续. . .

看到没?

看到没?

看到没?

这样之后,程序的输出就是 12 了,也就是说,现在的程序是按照 4字节对齐方式来对齐了。


  推荐阅读:
  专辑|Linux文章汇总
  专辑|程序人生
  专辑|C语言


嵌入式Linux
微信扫描二维码,关注我的公众号 
浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报