<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; 内存布局</title>
	<atom:link href="http://tonybai.com/tag/%e5%86%85%e5%ad%98%e5%b8%83%e5%b1%80/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Wed, 13 May 2026 23:44:40 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Go, Rust 还是 Zig？一场关于“简单”与“控制”的灵魂拷问</title>
		<link>https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control/</link>
		<comments>https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control/#comments</comments>
		<pubDate>Fri, 16 Jan 2026 23:38:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[BuildToolchain]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[comptime]]></category>
		<category><![CDATA[Control]]></category>
		<category><![CDATA[enum]]></category>
		<category><![CDATA[Explicit]]></category>
		<category><![CDATA[GarbageCollection]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[Implicit]]></category>
		<category><![CDATA[lifetimes]]></category>
		<category><![CDATA[ManualMemoryManagement]]></category>
		<category><![CDATA[MemoryManagement]]></category>
		<category><![CDATA[MemorySafety]]></category>
		<category><![CDATA[option]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Productivity]]></category>
		<category><![CDATA[Result]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[simplicity]]></category>
		<category><![CDATA[SystemProgramming]]></category>
		<category><![CDATA[TechnicalSelection]]></category>
		<category><![CDATA[ZerocostAbstraction]]></category>
		<category><![CDATA[Zig]]></category>
		<category><![CDATA[借用检查器]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[内存布局]]></category>
		<category><![CDATA[内存管理]]></category>
		<category><![CDATA[异步模型]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[手动内存管理]]></category>
		<category><![CDATA[技术选型]]></category>
		<category><![CDATA[控制]]></category>
		<category><![CDATA[显式]]></category>
		<category><![CDATA[构建工具链]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[生产力]]></category>
		<category><![CDATA[生命周期]]></category>
		<category><![CDATA[简单]]></category>
		<category><![CDATA[系统编程]]></category>
		<category><![CDATA[编译期执行]]></category>
		<category><![CDATA[表达力]]></category>
		<category><![CDATA[认知负荷]]></category>
		<category><![CDATA[隐式]]></category>
		<category><![CDATA[零成本抽象]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5733</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control 大家好，我是Tony Bai。 在系统编程的世界里，开发者似乎总是面临着一个残酷的二选一：是选择极致的简单与生产力，还是选择绝对的控制与零成本抽象？ 这种纠结在 Go 与 Rust 的长期对峙中体现得淋漓尽致。然而，近日一位拥有十年 Go 经验的资深开发者在Zig社区的分享，似乎为这场二元对立的战争撕开了一道口子。他从 Go 迁移到 Zig 的经历，既是一个技术选型的故事，也是一场关于“我们到底需要什么样的编程语言”的深度辩论。 Go 的困境：当“简单”成为一种束缚 对于许多 Gopher 来说，Go 的简单是其最大的武器，但也是最深的痛点。 这位楼主坦言，尽管他深爱 Go 的简单，但在编写某些复杂系统时，这种“过度简化”让他感觉语言本身存在缺陷。 表达力的缺失：Go 缺乏像 Rust 那样的 Enum (带数据的枚举)、Option 和 Result 类型。在处理复杂状态和错误流时，Go 的代码往往显得啰嗦且缺乏约束力。 “差不多”的无奈：为了保持简单，Go 在很多地方做了折中（比如 GC，比如泛型的实现方式）。当你需要榨干硬件性能或追求极致的内存布局时，Go 显得力不从心。 Rust 的围城：控制的代价是复杂度 如果嫌 Go 太简单，Rust 似乎是理所当然的替代者。但对于很多习惯了 Go “写完即运行”体验的开发者来说，Rust 的门槛是一堵高墙。 楼主表示，他喜欢 Rust 的核心概念（Structs, Enums, Option），但 Rust [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/go-rust-zig-simplicity-vs-control-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control">本文永久链接</a> &#8211; https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control</p>
<p>大家好，我是Tony Bai。</p>
<p>在系统编程的世界里，开发者似乎总是面临着一个残酷的二选一：是选择<strong>极致的简单与生产力</strong>，还是选择<strong>绝对的控制与零成本抽象</strong>？</p>
<p>这种纠结在 Go 与 Rust 的长期对峙中体现得淋漓尽致。然而，近日一位拥有十年 Go 经验的资深开发者<a href="https://www.reddit.com/r/Zig/comments/1q38e50/im_really_surprised_by_how_simple_it_is_to/">在Zig社区的分享</a>，似乎为这场二元对立的战争撕开了一道口子。他从 Go 迁移到 Zig 的经历，既是一个技术选型的故事，也是一场关于<strong>“我们到底需要什么样的编程语言”</strong>的深度辩论。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="" /></p>
<h2>Go 的困境：当“简单”成为一种束缚</h2>
<p>对于许多 Gopher 来说，Go 的简单是其最大的武器，但也是最深的痛点。</p>
<p>这位楼主坦言，尽管他深爱 Go 的简单，但在编写某些复杂系统时，这种“过度简化”让他感觉语言本身存在缺陷。</p>
<ul>
<li><strong>表达力的缺失</strong>：Go 缺乏像 Rust 那样的 Enum (带数据的枚举)、Option 和 Result 类型。在处理复杂状态和错误流时，Go 的代码往往显得啰嗦且缺乏约束力。</li>
<li><strong>“差不多”的无奈</strong>：为了保持简单，Go 在很多地方做了折中（比如 GC，比如泛型的实现方式）。当你需要榨干硬件性能或追求极致的内存布局时，Go 显得力不从心。</li>
</ul>
<h2>Rust 的围城：控制的代价是复杂度</h2>
<p>如果嫌 Go 太简单，Rust 似乎是理所当然的替代者。但对于很多习惯了 Go “写完即运行”体验的开发者来说，Rust 的门槛是一堵高墙。</p>
<p>楼主表示，他喜欢 Rust 的核心概念（Structs, Enums, Option），但 Rust 为了内存安全而引入的<strong>借用检查器、生命周期</strong>以及<strong>复杂的异步模型</strong>，让他感觉“像是面对另一个 C++”。</p>
<p>这是一场灵魂拷问：<strong>为了获得控制权，我们真的需要背负如此沉重的认知包袱吗？</strong></p>
<h2>Zig 的破局：在“简单”与“控制”之间走钢丝</h2>
<p>Zig 的出现，似乎精准地击中了 Go 与 Rust 之间的那个真空地带。对于这位 Gopher 来说，Zig 让他感到了久违的“刚刚好”：</p>
<ol>
<li><strong>显式的哲学（像 Go）</strong>：Zig 没有隐式内存分配，没有隐藏的控制流，也没有预处理器。这种“所见即所得”的代码风格，与 Go 的可读性哲学高度共鸣。</li>
<li><strong>现代的类型系统（像 Rust）</strong>：Zig 提供了 comptime（编译期执行）和丰富的类型系统，弥补了 Go 在表达力上的短板，却又没有引入 Rust 那样复杂的生命周期概念。</li>
<li><strong>对 C 的降维打击</strong>：Zig 不仅是一门语言，更是一个强大的 C/C++ 构建工具链。它允许你无缝地与 C 交互，逐步迁移遗留代码，这是 Go (CGO) 和 Rust 都难以做到的顺滑体验。</li>
</ol>
<h2>社区的冷思考：没有免费的午餐</h2>
<p>当然，这场灵魂拷问没有标准答案。社区的讨论也极其理性地指出了选择 Zig 的代价：</p>
<ul>
<li><strong>生态的荒原</strong>：与 Go 庞大的“标准库+第三方库”相比，Zig 的生态仍处于拓荒期。你可能需要自己造很多轮子。</li>
<li><strong>内存管理的回归</strong>：Zig 没有 GC，也没有 Rust 的所有权模型。这意味着你回到了手动管理内存的时代（尽管有 defer 和 arena 等工具辅助）。对于习惯了 GC 的 Gopher 来说，这是一个必须跨越的心理门槛。</li>
<li><strong>稳定性的豪赌</strong>：Zig 尚未发布 1.0，语言特性仍在变动。选择 Zig，意味着你愿意陪它一起成长，也愿意承担变动的风险。</li>
</ul>
<h2>小结：你的灵魂属于哪里？</h2>
<p>这场讨论最终指向了开发者内心的自我定位：</p>
<ul>
<li>如果你追求<strong>高效交付、团队协作和工业级的稳定性</strong>，Go 依然是不可撼动的王者。</li>
<li>如果你追求<strong>数学般的严谨、绝对的安全和零成本抽象</strong>，且不介意陡峭的学习曲线，Rust 是你的圣杯。</li>
<li>而如果你渴望<strong>掌控底层、厌倦了复杂的抽象、却又想要现代化的开发体验</strong>，Zig 也许就是你一直在寻找的那个“刚刚好”。</li>
</ul>
<p><strong>简单还是控制？这不仅是语言的选择，更是你作为工程师，想要如何与机器对话的选择。</strong></p>
<p>资料链接：https://www.reddit.com/r/Zig/comments/1q38e50/im_really_surprised_by_how_simple_it_is_to/</p>
<hr />
<p><strong>你的“灵魂选择”</strong></p>
<p>在“简单”与“控制”的天平上，<strong>你的心偏向哪一边？如果让你现在开始一个新项目，你会毫不犹豫地选择 Go，还是想尝尝 Zig 的鲜，亦或是死磕 Rust？</strong></p>
<p><strong>欢迎在评论区投出你的一票，并分享你的理由！</strong> 让我们看看谁才是开发者心中的“白月光”。</p>
<p><strong>如果这篇文章引发了你的选型思考，别忘了点个【赞】和【在看】，并转发给那个还在纠结学什么语言的朋友！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>再谈C语言位域</title>
		<link>https://tonybai.com/2013/05/21/talk-about-bitfield-in-c-again/</link>
		<comments>https://tonybai.com/2013/05/21/talk-about-bitfield-in-c-again/#comments</comments>
		<pubDate>Tue, 21 May 2013 14:26:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[bitfield]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[byteorder]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[endianess]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[位域]]></category>
		<category><![CDATA[内存布局]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[可移植]]></category>
		<category><![CDATA[大端]]></category>
		<category><![CDATA[存储单元]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[小端]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[移位]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1277</guid>
		<description><![CDATA[我在日常工作中使用C语言中的位域(bit field)的场景甚少，原因大致有二： * 一直从事于服务器后端应用的开发，现在的服务器的内存容量已经达到了数十G的水平，我们一般不需要为节省几个字节而使用内存布局更加紧凑的位域。 * 结构体中位域的实现是平台相关或Compiler相关的，移植性较差，我们不会贸然地给自己造&#8220;坑&#8221;的。 不过近期Linux技术内核社区（www.linux-kernel.cn) mail list中的一个问题让我觉得自己对bit field的理解还欠火候，于是乎我又花了些时间就着那个问题重新温习一遍bit field。 零、对bit field的通常认知 在C语言中，我们可以得到某个字节的内存地址，我们具备了操作任意内存字节的能力；在那个内存空间稀缺的年代，仅仅控制到字节级别还不足以满足C 程序员的胃口，为此C语言中又出现了bit级别内存的&#8220;有限操作能力&#8221; &#8211; 位域。这里所谓的&#8220;有限&#8221;指的是机器的最小粒度寻址单位是字节，我们无法像获得某个字节地址那样得到某个bit的地址，因此我们仅能通过字节的运算来设置 和获取某些bit的值。在C语言中，尝试获得一个bit field的地址是非法操作： struct flag_t { &#160;&#160;&#160; int a : 1; }; struct flag_t flg; printf(&#34;%p\n&#34;, &#38;flg.a); error: cannot take address of bit-field &#8216;a&#8217; 以下是C语言中bit field的一般形式： struct foo_t { &#160;&#160;&#160; unsigned int b1 : n1, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; b2 : n2, [...]]]></description>
			<content:encoded><![CDATA[<p>我在日常工作中使用<a href="http://en.wikipedia.org/wiki/C_(programming_language)‎">C语言</a>中的<a href="http://tonybai.com/2006/06/19/understand-bit-fields/">位域</a>(bit field)的场景甚少，原因大致有二：</p>
<p>* 一直从事于服务器后端应用的开发，现在的服务器的内存容量已经达到了数十G的水平，我们一般不需要为节省几个字节而使用内存布局更加紧凑的位域。<br />
	* 结构体中位域的实现是平台相关或Compiler相关的，移植性较差，我们不会贸然地给自己造&ldquo;坑&rdquo;的。</p>
<p>不过近期Linux技术内核社区（www.linux-kernel.cn) mail list中的一个问题让我觉得自己对<a href="http://en.wikipedia.org/wiki/Bit_field">bit field</a>的理解还欠火候，于是乎我又花了些时间就着那个问题重新温习一遍bit field。</p>
<p><b>零、对bit field的</b><b>通常认知</b></p>
<p>在C语言中，我们可以得到某个字节的内存地址，我们具备了操作任意内存字节的能力；在那个内存空间稀缺的年代，仅仅控制到字节级别还不足以满足C 程序员的胃口，为此C语言中又出现了bit级别内存的&ldquo;有限操作能力&rdquo; &#8211; 位域。这里所谓的&ldquo;有限&rdquo;指的是机器的最小粒度寻址单位是字节，我们无法像获得某个字节地址那样得到某个bit的地址，因此我们仅能通过字节的运算来设置 和获取某些bit的值。<b>在C语言中，尝试获得一个bit field的地址是非法操作</b>：</p>
<p><font face="Courier New">struct flag_t {<br />
	&nbsp;&nbsp;&nbsp; int a : 1;<br />
	};</font></p>
<p><font face="Courier New">struct flag_t flg;<br />
	printf(&quot;%p\n&quot;, &amp;flg.a);</font></p>
<p><font face="Courier New">error: cannot take address of bit-field &lsquo;a&rsquo;</font></p>
<p>以下是C语言中bit field的一般形式：</p>
<p><font face="Courier New">struct foo_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned int b1 : n1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b2 : n2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bn : nk;<br />
	};</font></p>
<p>其中n1，n2，nk为对应位域所占据的bit数。</p>
<p>位域(bit field)的出现让我们可以<b>用变量名代表某些bit，并通过变量名直接获得和设置一些内存中bit的值</b>，而不是通 过晦涩难以理解的位操作来进行，例如：</p>
<p><font face="Courier New">struct foo_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned int a : 3,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c : 4;<br />
	};</font></p>
<p><font face="Courier New">struct foo_t f;<br />
	f.a = 3;<br />
	f.b = 1;<br />
	f.c = 12;</font></p>
<p>另外使用位域我们可以在展现和存储相同信息的同时，自定义<b>更加紧凑的内存布局</b>，节约内存的使用量。这使得bit field在嵌入式领域，在驱动程序领域得到广泛的应用，比如可以仅用两个字节就可以将tcpheader从dataoffset到fin的信息全部表示 和存储起来：</p>
<p><font face="Courier New">struct tcphdr {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; __u16&nbsp;&nbsp; doff:4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; res1:4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cwr:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ece:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; urg:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ack:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; psh:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rst:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; syn:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fin:1;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	};</font></p>
<p><b>一、存储单元(storage unit)</b></p>
<p><a href="http://tonybai.com/2005/07/28/introduction-on-c-standard-overview-series/">C标准</a>允许unsigned int/signed int/int类型的位域声明，<a href="http://tonybai.com/2011/08/31/simplify-coding-in-c99/">C99</a>中加入了_Bool类型的位域。但像<a href="http://tonybai.com/2006/03/14/explain-gcc-warning-options-by-examples/">Gcc</a>这样的编译器自行加入了一些扩展，比如支持short、char等整型类 型的位域字段，使用其他类型声明位域将得到错误的结果，比如：</p>
<p><font face="Courier New">struct flag_t {<br />
	&nbsp;&nbsp;&nbsp; char* a : 1;<br />
	};<br />
	&nbsp;error: bit-field &lsquo;a&rsquo; has invalid type</font></p>
<p>C编译器究竟是如何为bit field分配存储空间的呢？我们以Gcc编译器(<a href="http://tonybai.com/2012/12/04/upgrade-ubuntu-to-1204-lts/">Ubuntu 12.04</a>.2 x86_64 Gcc 4.7.2 )为例一起来探究一下。</p>
<p>我们先来看几个基本的bit field类型的例子：</p>
<p><font face="Courier New">struct bool_flag_t {<br />
	&nbsp;&nbsp;&nbsp; _Bool a : 1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 1;<br />
	};</font></p>
<p><font face="Courier New">struct char_flag_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned char a : 2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 3;<br />
	};</font></p>
<p><font face="Courier New">struct short_flag_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned short a : 2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 3;<br />
	};</font></p>
<p><font face="Courier New">struct int_flag_t {<br />
	&nbsp;&nbsp;&nbsp; int a : 2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 3;<br />
	};</font></p>
<p><font face="Courier New">int<br />
	main()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%ld\n&quot;, sizeof(struct bool_flag_t));<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%ld\n&quot;, sizeof(struct char_flag_t));<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%ld\n&quot;, sizeof(struct short_flag_t));<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%ld\n&quot;, sizeof(struct int_flag_t));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return 0;<br />
	}</font></p>
<p>编译执行后的输出结果为：<br />
	<font face="Courier New">1<br />
	1<br />
	2<br />
	4</font></p>
<p>可以看出Gcc为不同类型的bit field分配了不同大小的基本内存空间。_Bool和char类型的基本存储空间为1个字节；short类型的基本存储空间为2个字节，int型的为4 个字节。这些空间的分配是基于结构体内部的bit field的size没有超出基本空间的界限为前提的。以short_flag_t为例：</p>
<p><font face="Courier New">struct short_flag_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned short a : 2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 3;<br />
	};</font></p>
<p>a、b两个bit field总共才使用了5个bit的空间，所以Compiler只为short_flag_t分配一个基本存储空间就可以存储下这两个bit field。如果bit field的size变大，size总和超出基本存储空间的size时，编译器会如何做呢？我们还是看例子：</p>
<p><font face="Courier New">struct short_flag_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned short a : 7,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 10;<br />
	};</font></p>
<p>将short_flag_t中的两个bit字段的size增大后，我们得到的sizeof(struct short_flag_t)变成了4，显然Compiler发现一个基础存储空间已经无法存储下这两个bit field了，就又为short_flag_t多分配了一个基本存储空间。这里我们所说的基本存储空间就称为<b>&ldquo;存储单元(storage unit)&rdquo;</b><b>。</b>它是Compiler在给bit field分配内存空间时的基本单位，并且这些分配给bit field的内存是以存储单元大小的整数倍递增的。但从上面来看，<b>不同类型bit field的存储单元大小是不同的</b>。</p>
<p>sizeof(struct short_flag_t)变成了4，那a和b有便会有至少两种内存布局方式：<br />
	* a、b紧邻<br />
	* b在下一个可存储下它的存储单元中分配内存</p>
<p>具体采用哪种方式，是Compiler相关的，这会影响到bit field的可移植性。我们来测试一下Gcc到底采用哪种方式：</p>
<p><font face="Courier New">void<br />
	dump_native_bits_storage_layout(unsigned char *p, int bytes_num)<br />
	{</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; union flag_t {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned char c;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct base_flag_t {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int p7:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p6:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p5:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p4:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p3:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p2:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p1:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p0:1;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } base;<br />
	&nbsp;&nbsp;&nbsp; } f;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; bytes_num; i++) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.c = *(p + i);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%d%d%d%d %d%d%d%d &quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p7,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p6,&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p5,&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p4,&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p3,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p2,&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p1,&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.base.p0);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;\n&quot;);<br />
	}</font></p>
<p><font face="Courier New">struct short_flag_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned short a : 7,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 10;<br />
	};</font></p>
<p><font face="Courier New">&nbsp;struct short_flag_t s;<br />
	&nbsp;memset(&amp;s, 0, sizeof(s));<br />
	&nbsp;s.a = 113; /* 0111 0001 */<br />
	&nbsp;s.b = 997; /* 0011 1110 0101 */</font></p>
<p><font face="Courier New">&nbsp;dump_native_bits_storage_layout((unsigned char*)&amp;s, sizeof(s));</font><br />
	&nbsp;<br />
	编译执行后的输出结果为：<font face="Courier New"> 1000 1110 0000 0000 1010 0111 1100 0000</font>。可以看出Gcc采用了第二种方式，即在为a分配内存后，发现该存储单元剩余的空间(9 bits)已经无法存储下字段b了，于是乎Gcc又分配了一个存储单元(2个字节)用来为b分配空间，而a与b之间也因此存在了空隙。</p>
<p>我们还可以通过<b>匿名0长度位域字段</b>的语法强制位域在下一个存储单元开始分配，例如：</p>
<p><font face="Courier New">struct short_flag_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned short a : 2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b : 3;<br />
	};</font><br />
	这个结构体本来是完全可以在一个存储单元(2字节)内为a、b两个位域分配空间的。如果我们非要让b放在与a不同的存储单元中，我们可以通过加入 匿名0长度位域的方法来实现：</p>
<p><font face="Courier New">struct short_flag_t {<br />
	&nbsp;&nbsp;&nbsp; unsigned short a : 2;<br />
	&nbsp;&nbsp;&nbsp; unsigned short&nbsp;&nbsp; : 0;<br />
	&nbsp;&nbsp;&nbsp; unsigned short b : 3;<br />
	};</font></p>
<p>这样声明后，sizeof(struct short_flag_t)变成了4。</p>
<p><font face="Courier New">&nbsp;struct short_flag_t s;<br />
	&nbsp;memset(&amp;s, 0, sizeof(s));<br />
	&nbsp;s.a = 2; /* 10 */<br />
	&nbsp;s.b = 4; /* 100 */</font></p>
<p><font face="Courier New">&nbsp;dump_native_bits_storage_layout((unsigned char*)&amp;s, sizeof(s));</font></p>
<p>执行后，输出的结果为：</p>
<p><font face="Courier New">0100 0000 0000 0000 0010 0000 0000 0000</font></p>
<p>可以看到位域b被强制放到了第二个存储单元中。如果没有那个匿名0长度的位域，那结果应该是这样的：</p>
<p><font face="Courier New">0100 1000 0000 0000</font></p>
<p>最后位域的长度是不允许超出其类型的最大长度的，比如：</p>
<p><font face="Courier New">struct short_flag_t {<br />
	&nbsp;&nbsp;&nbsp; short a : 17;<br />
	};</font></p>
<p><font face="Courier New">error: width of &lsquo;a&rsquo; exceeds its type</font></p>
<p><b>二、位域的位序</b></p>
<p>再回顾一下上一节的最后那个例子（不使用匿名0长度位域时）：</p>
<p>&nbsp;<font face="Courier New">struct short_flag_t s;<br />
	&nbsp;memset(&amp;s, 0, sizeof(s));<br />
	&nbsp;s.a = 2; /* 10 */<br />
	&nbsp;s.b = 4; /* 100 */</font></p>
<p>dump bits的结果为<font face="Courier New">0100 1000 0000 0000</font>。</p>
<p>怎么感觉输出的结果与s.a和s.b的值对不上啊！根据a和b的值，dump bits的输出似乎应该为<font face="Courier&lt;br /&gt;<br />
      New">1010 0000 0000 0000</font>。对比这两个dump结果不同的部分：1010 0000 vs. 0100 1000，a和b的bit顺序恰好相反。之前一直与<a href="http://tonybai.com/2005/09/28/also-talk-about-byte-order/">字节序</a>做斗争，难不成bit也有序之分？事实就是这样的。bit也有order的概念，称为位序。位域字 段的内存位排序就称为该位域的位序。</p>
<p>我们来回顾一下字节序的概念，字节序分大端(big-endian，典型体系Sun Sparc)和小端(little-endian，典型体系Intel x86)：<br />
	大端指的是数值（比如0&#215;12345678）的逻辑最高位(0&#215;12)放在起始地址（低地址）上，简称高位低址，就是<b>高位放在起始地址</b>。<br />
	小端指的是数值（比如0&#215;12345678）的逻辑最低位(0&#215;78)放在起始地址（低地址）上，简称低位低址，就是<b>低位放在起始地址</b>。</p>
<p><font face="Courier New">看下面例子：</font></p>
<p><font face="Courier New">int<br />
	main()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; char c[4];<br />
	&nbsp;&nbsp;&nbsp; unsigned int i = 0&#215;12345678;<br />
	&nbsp;&nbsp;&nbsp; memcpy(c, &amp;i, sizeof(i));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; printf(&quot;%p &#8211; 0x%x\n&quot;, &amp;c[0], c[0]);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%p &#8211; 0x%x\n&quot;, &amp;c[1], c[1]);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%p &#8211; 0x%x\n&quot;, &amp;c[2], c[2]);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%p &#8211; 0x%x\n&quot;, &amp;c[3], c[3]);<br />
	}</font></p>
<p>在x86 (小端机器)上输出结果如下：</p>
<p><font face="Courier New">0x7fff1a6747c0 &#8211; 0&#215;78<br />
	0x7fff1a6747c1 &#8211; 0&#215;56<br />
	0x7fff1a6747c2 &#8211; 0&#215;34<br />
	0x7fff1a6747c3 &#8211; 0&#215;12</font></p>
<p>在sparc(大端机器)上输出结果如下：</p>
<p><font face="Courier New">ffbffbd0 &#8211; 0&#215;12<br />
	ffbffbd1 &#8211; 0&#215;34<br />
	ffbffbd2 &#8211; 0&#215;56<br />
	ffbffbd3 &#8211; 0&#215;78</font></p>
<p>通过以上输出结果可以看出，小端机器的数值低位0&#215;78放在了低地址0x7fff1a6747c0上；而大端机器则是将数值高位0&#215;12放在了低 地址0xffbffbd0上。</p>
<p>机器的最小寻址单位是字节，bit无法寻址，也就没有高低地址和起始地址的概念，我们需要定义一下bit的&ldquo;地址&rdquo;。以一个字节为例，我们把从左到右的8个bit的位置(position)命名按顺序命名如下：</p>
<p><font face="Courier New">p7 p6 p5 p4 p3 p2 p1 p0</font></p>
<p>其中最左端的p7为起始地址。这样以<b>一字节大小</b>的数值10110101(b)为例，其在不同平台下的内存位序如下：</p>
<p>大端的含义是数值的最高位1（最左边的1）放在了起始位置p7上，即数值10110101的大端内存布局为10110101。<br />
	小端的含义是数值的最低位1(最右边的1)放在了起始位置p7上，即数值10110101的小端内存布局为10101101。</p>
<p>前面的函数dump_native_bits_storage_layout也是符合这一定义的，即最左为起始位置。</p>
<p>同理，对于一个bit个数为3且存储的数值为110(b)的位域而言，将其3个bit的位置按顺序命名如下：</p>
<p><font face="Courier New">p2 p1 p0</font></p>
<p>其在大端机器上的bit内存布局，即位域位序为： <font face="Courier New">110</font>;<br />
	其在小端机器上的bit内存布局，即位域位序为： <font face="Courier New">011</font>。</p>
<p>在此基础上，理解上面例子中的疑惑就很简单了。</p>
<p>&nbsp;s.a = 2; /* 10(b) ，大端机器上位域位序为 10，小端为01 */<br />
	&nbsp;s.b = 4; /* 100(b)，大端机器上位域位序为100，小端为001 */</p>
<p>于是在x86（小端）上的dump bits结果为：<font face="Courier New">0100 1000 0000 0000</font><br />
	而在sparc(大端）上的dump bits结果为：<font face="Courier New">1010 0000 0000 0000</font></p>
<p>同时我们可以看出这里是根据位域进行单独赋值的，这样<b>位域的位序是也是以位域为单位排列的</b><b>，即每个位域内部独立排序</b>， 而不是按照存储单元（这里的存储单元是16bit）或按字节内bit序排列的。</p>
<p><b>三、tcphdr定义分析</b></p>
<p>前面提到过在linux-kernel.cn mail list中的那个问题大致如下：</p>
<p>tcphdr定义中的大端代码：</p>
<p><font face="Courier New">__u16&nbsp;&nbsp; doff:4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; res1:4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cwr:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ece:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; urg:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ack:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; psh:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rst:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; syn:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fin:1;</font></p>
<p><font face="Courier New">问题是其对应的小端代码该如何做字段排序？似乎有两种方案摆在面前：</font></p>
<p>方案1:<br />
	<font face="Courier New"><font face="Courier New">__u16&nbsp;&nbsp;&nbsp; res1:4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; doff:4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fin:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; syn:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rst:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; psh:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ack:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; urg:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ece:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cwr:1;</font></font></p>
<p><font face="Courier New"><font face="Courier New">or</font></font></p>
<p><font face="Courier New">方案2:<br />
	__u16&nbsp;&nbsp; cwr:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ece:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; urg:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ack:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; psh:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rst:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; syn:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fin:1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; res1:4<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; doff:4;</font></p>
<p><font face="Courier New">个人觉得这两种方案从理论上都是没错的，关键还是看tcphdr是如何进行pack的，是按__u16整体打包，还是按byte打包。原代码中使用的是方 案1，推测出tcphdr采用的是按byte打包的方式，这样我们只需调换byte内的bit顺序即可。res1和doff是一个字节内的两个位域，如果 按自己打包，他们两个的顺序对调即可在不同端的平台上得到相同的结果。用下面实例解释一下：</font></p>
<p><font face="Courier New">假设在大端系统上，doff和res1的值如下：</font></p>
<p><font face="Courier New">doff res1<br />
	1100 1010 大端</font></p>
<p><font face="Courier New">在大端系统上pack后，转化为网络序：</font></p>
<p><font face="Courier New">doff res1<br />
	1100 1010 网络序</font></p>
<p><font face="Courier New">小端系统接收后，转化为本地序：</font></p>
<p><font face="Courier New">0101 0011</font></p>
<p><font face="Courier New">很显然，我们应该按如下方法对应：</font></p>
<p><font face="Courier New">res1 doff<br />
	0101 0011</font></p>
<p><font face="Courier New">也就相当于将doff和res1的顺序对调，这样在小端上依旧可以得到相同的值。</font></p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/05/21/talk-about-bitfield-in-c-again/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
