sizeof仅得总大小,offsetof才能获成员偏移;offsetof是编译期常量,定义于,返回成员相对于结构体起始的字节偏移。

用 sizeof 和 offsetof 查看结构体实际布局
直接看 sizeof 只能知道总大小,但无法得知成员偏移和填充位置。真正做布局分析,必须配合 offsetof(定义在 <cstddef></cstddef> 中)。
它返回某个成员相对于结构体起始地址的字节偏移,是编译期常量,安全可靠:
#include <cstddef>
#include <iostream>
struct S {
char a;
int b;
short c;
};
int main() {
std::cout
<p>输出能清晰暴露填充:成员 <code>b</code> 没有紧挨着 <code>a</code>,中间被补了 3 字节;<code>c</code> 前面又空出 2 字节——这是为了满足 <code>int</code>(4字节对齐)和 <code>short</code>(2字节对齐)各自的起始地址约束。</p>
<h3>为什么改变成员声明顺序会影响 <code>sizeof</code> 结果</h3>
<p>结构体成员按声明顺序依次排布,但编译器只在必要时插入填充,不重排顺序。所以把大类型往前放,能显著减少总填充量。</p>
<ul>
<li>
<code>struct Bad { char a; int b; char c; };</code> → <code>sizeof</code> 通常是 12(a+3pad+b+c+3pad)</li>
<li>
<code>struct Good { int b; char a; char c; };</code> → <code>sizeof</code> 通常是 8(b+a+c+2pad,末尾补齐到 4 的倍数)</li>
</ul>
<p>两者逻辑等价,但后者节省 4 字节。这对高频分配的小结构(如网络包头、粒子系统数据)影响真实内存带宽和缓存命中率。</p>
<p>注意:<code>Good</code> 中 <code>a</code> 和 <code>c</code> 虽然都是 <code>char</code>,但不会自动“打包”成一个字节字段——C++ 不做隐式位域合并,除非你显式写 <code>struct { char a:4, c:4; };</code>。</p>
<h3>判断是否为标准布局以确保内存可预测</h3>
<p>如果你打算用 <code>memcpy</code> 序列化、或把结构体传给 C 函数、或 reinterpret_cast 成字节数组,必须确认它是标准布局(standard layout)。否则填充位置、甚至成员顺序都可能因编译器/优化级别而异。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="overflowclass">C++14</a>
<p class="overflowclass">C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p>验证方式很简单:</p>
<pre class="brush:php;toolbar:false;">#include <type_traits>
static_assert(std::is_standard_layout_v<s>, "S must be standard layout");
</s></type_traits>
关键约束包括:
- 不能有虚函数、虚基类
- 所有非静态成员访问控制一致(全
public或全private) - 不能有引用成员
- 若继承,只能有一个基类且它本身也得是标准布局
哪怕只加一个 private: 或一个 virtual void f() = 0;,std::is_standard_layout_v 就会变成 false,此时 offsetof 仍可用,但跨平台二进制兼容性已不可靠。
用 #pragma pack 强制紧凑布局的风险
某些场景(如硬件寄存器映射、网络协议固定格式)要求结构体完全无填充。这时可用 #pragma pack(1):
#pragma pack(push, 1)
struct Packed {
char a;
int b;
short c;
};
#pragma pack(pop)
// sizeof(Packed) == 7
但它带来两个硬伤:
- 性能损失:
int b现在可能落在非 4 字节对齐地址,x86 通常容忍但慢,ARMv7+ 可能直接触发unaligned access异常 - ABI 破坏:该结构体不能再作为函数参数值传入,也不能与标准布局类型混用——
std::is_standard_layout_v<packed></packed>仍为true,但对齐保证失效,memcpy安全性需额外校验
更稳妥的做法是用 alignas 显式控制单个成员对齐,或改用 std::array<:byte n></:byte> + 手动序列化,避免让编译器替你冒险对齐。
真正容易被忽略的是:即使你没写 #pragma pack,某些嵌入式工具链或交叉编译环境默认启用 tight packing,导致桌面端测试通过、目标板上崩溃——务必在目标平台上实测 offsetof 和 sizeof。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










