返回介绍

18.4 结构体的成员封装

发布于 2025-02-22 14:00:47 字数 4184 浏览 0 评论 0 收藏 0

结构体做的一个重要的事情就是封装了成员,让我们看看简单的例子:

#!bash
#include <stdio.h>
struct s
{
    char a;
    int b;
    char c;
    int d;
};
void f(struct s s)
{
    printf ("a=%d; b=%d; c=%d; d=%d
", s.a, s.b, s.c, s.d);
};

如我们所看到的,我们有 2 个 char 成员(每个 1 字节),和两个 int 类型的数据(每个 4 字节)。

18.4.1 x86

编译后得到:

#!bash
_s$ = 8 ; size = 16
?f@@YAXUs@@@Z PROC ; f
    push ebp
    mov ebp, esp
    mov eax, DWORD PTR _s$[ebp+12]
    push eax
    movsx ecx, BYTE PTR _s$[ebp+8]
    push ecx
    mov edx, DWORD PTR _s$[ebp+4]
    push edx
    movsx eax, BYTE PTR _s$[ebp]
    push eax
    push OFFSET $SG3842
    call _printf
    add esp, 20 ; 00000014H
    pop ebp
    ret 0
?f@@YAXUs@@@Z ENDP ; f
_TEXT ENDS

如我们所见,每个成员的地址都按 4 字节对齐了,这也就是为什么 char 也会像 int 一样占用 4 字节。为什么?因为对齐后对 CPU 来说更容易读取数据。

但是,这么看明显浪费了一些空间。 让我们能用/Zp1(/Zp[n]代表结构体边界值为 n 字节)来编译它:

清单 18.12: MSVC /Zp1

#!bash
_TEXT SEGMENT
_s$ = 8 ; size = 10
?f@@YAXUs@@@Z PROC ; f
    push ebp
    mov ebp, esp
    mov eax, DWORD PTR _s$[ebp+6]
    push eax
    movsx ecx, BYTE PTR _s$[ebp+5]
    push ecx
    mov edx, DWORD PTR _s$[ebp+1]
    push edx
    movsx eax, BYTE PTR _s$[ebp]
    push eax
    push OFFSET $SG3842
    call _printf
    add esp, 20 ; 00000014H
    pop ebp
    ret 0
?f@@YAXUs@@@Z ENDP ; f

现在,结构体只用了 10 字节,而且每个 char 都占用 1 字节。我们得到了最小的空间,但是反过来看,CPU 却无法用最优化的方式存取这些数据。 可以容易猜到的是,如果这个结构体在很多源代码和对象中被使用的话,他们都需要用同一种方式来编译起来。 除了 MSVC /Zp 选项,还有一个是#pragma pack 编译器选项可以在源码中定义边界值。这个语句在 MSVC 和 GCC 中均被支持。 回到 SYSTEMTIME 结构体中的 16 位成员,我们的编译器怎么才能把它们按 1 字节边界来打包? WinNT.h 有这么个代码:

清单 18.13:WINNT.H

#!cpp
#include "pshpack1.h"

和这个:

清单 18.14:WINNT.H

#!cpp
#include "pshpack4.h" // 4 byte packing is the default

文件 PshPack1.h 看起来像

清单 18.15: PSHPACK1.H

#!bash
#if ! (defined(lint) || defined(RC_INVOKED))
#if ( _MSC_VER >= 800 && !defined(_M_I86)) || defined(_PUSHPOP_SUPPORTED)
#pragma warning(disable:4103)
#if !(defined( MIDL_PASS )) || defined( __midl )
#pragma pack(push,1)
#else
#pragma pack(1)
#endif
#else
#pragma pack(1)
#endif
#endif /* ! (defined(lint) || defined(RC_INVOKED)) */

这就是#pragma pack 处理结构体大小的方法。

18.4.2 ARM+优化 Keil+thumb 模式

清单 18.16

#!bash
.text:0000003E exit ; CODE XREF: f+16
.text:0000003E 05 B0 ADD SP, SP, #0x14
.text:00000040 00 BD POP {PC}

.text:00000280 f
.text:00000280
.text:00000280 var_18 = -0x18
.text:00000280 a = -0x14
.text:00000280 b = -0x10
.text:00000280 c = -0xC
.text:00000280 d = -8
.text:00000280
.text:00000280 0F B5 PUSH {R0-R3,LR}
.text:00000282 81 B0 SUB SP, SP, #4
.text:00000284 04 98 LDR R0, [SP,#16] ; d
.text:00000286 02 9A LDR R2, [SP,#8] ; b
.text:00000288 00 90 STR R0, [SP]
.text:0000028A 68 46 MOV R0, SP
.text:0000028C 03 7B LDRB R3, [R0,#12] ; c
.text:0000028E 01 79 LDRB R1, [R0,#4] ; a
.text:00000290 59 A0 ADR R0, aADBDCDDD ; "a=%d; b=%d; c=%d; d=%d
"
.text:00000292 05 F0 AD FF BL __2printf
.text:00000296 D2 E6 B exit

我们可以回忆到的是,这里它直接用了结构体而不是指向结构体的指针,而且因为 ARM 里函数的前 4 个参数是通过寄存器传递的,所以结构体其实是通过 R0-R3 寄存器传递的。

LDRB 指令将内存中的一个字节载入,然后把它扩展到 32 位,同时也考虑它的符号。这和 x86 架构的 MOVSX(参考 13.1.1 节)基本一样。这里它被用来传递结构体的 a、c 两个成员。

还有一个我们可以容易指出来的是,在函数的末尾处,这里它没有使用正常的函数尾该有的指令,而是直接跳转到了另一个函数的末尾! 的确,这是一个相当不同的函数,而且跟我们的函数没有任何关联。但是,他却有着相同的函数结尾(也许是因为他也有 5 个本地变量(5 x 4 = 0x14))。而且他就在我们的函数附近(看看地址就知道了)。事实上,函数结尾并不重要,只要函数好好执行就行了嘛。显然,Keil 决定要重用另一个函数的一部分,原因就是为了优化代码大小。普通函数结尾需要 4 字节,而跳转指令只要 2 个字节。

18.4.3 ARM+优化 XCode(LLVM)+thumb-2 模式

清单 18.17: 优化的 Xcode (LLVM)+thumb-2 模式

#!bash
var_C = -0xC
    PUSH {R7,LR}
    MOV R7, SP
    SUB SP, SP, #4
    MOV R9, R1 ; b
    MOV R1, R0 ; a
    MOVW R0, #0xF10 ; "a=%d; b=%d; c=%d; d=%d
"
    SXTB R1, R1 ; prepare a
    MOVT.W R0, #0
    STR R3, [SP,#0xC+var_C] ; place d to stack for printf()
    ADD R0, PC ; format-string
    SXTB R3, R2 ; prepare c
    MOV R2, R9 ; b
    BLX _printf
    ADD SP, SP, #4
    POP {R7,PC}

SXTB(Singned Extend Byte,有符号扩展字节)和 x86 的 MOVSX(见 13.1.1 节)差不多,但是它不是对内存操作的,而是对一个寄存器操作的,至于剩余的——都一样。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文