<?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/%e7%a7%bb%e4%bd%8d/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 程序员都应掌握的位操作艺术</title>
		<link>https://tonybai.com/2025/08/13/bit-manipulation-in-go/</link>
		<comments>https://tonybai.com/2025/08/13/bit-manipulation-in-go/#comments</comments>
		<pubDate>Wed, 13 Aug 2025 00:01:15 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[bit]]></category>
		<category><![CDATA[bits]]></category>
		<category><![CDATA[CRC32]]></category>
		<category><![CDATA[FileMode]]></category>
		<category><![CDATA[for]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[if]]></category>
		<category><![CDATA[int64]]></category>
		<category><![CDATA[math]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[sync]]></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=5031</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/08/13/bit-manipulation-in-go 大家好，我是Tony Bai。 在编程这门手艺中，我们时常扮演着“建筑师”的角色，用一行行优雅的高级语言，构建起宏伟的应用大厦。但你是否曾停下脚步，好奇地探寻过这座大厦最深处的基石——那些由 0 和 1 构成的、既简单又神秘的二进制世界？ 当你阅读 Go 标准库（比如 sync.Mutex 或 os.FileMode）的源码时，看到那些 &#38;、&#124;、^、&#60;&#60; 符号以一种眼花缭乱的方式组合在一起，你感受到的是困惑，还是一丝兴奋？ 那感觉，就像是魔法学院的学生，无意中翻开了一本古老的“咒语书”。书上的符号看似简单，却蕴含着驱动整个魔法世界运转的底层力量。 这本“咒语书”，就是位运算（Bit Manipulation）。它不是什么过时的黑科技，而是隐藏在现代软件工程之下的一门永恒的、优雅的艺术。 这不是“屠龙之技”，而是一场思维的“魔术表演” 很多人对位运算的印象，还停留在那些刁钻的面试题上。但真正的位操作艺术，远不止于此。它是一种思维方式，一种用最纯粹、最底层的方式与计算机对话的技艺。 掌握这门艺术，能为你带来什么？ 1. “点石成金”的性能魔法 位运算直接在二进制层面操作数据，其速度快如闪电。在性能敏感的场景，它能将原本笨重的计算(结合for、if等)，优化成几次轻盈的位移和与或，带来数量级的性能提升。这就像魔术师在众目睽睽之下，瞬间完成了看似不可能的任务。 2. “芥子纳须弥”的空间魔法 一个 int64 变量，在位操作大师的手中，是 64 个可以独立控制的微观世界。通过精巧的位掩码，你可以在极小的空间内，存储和管理海量的状态信息。这种对空间的极致利用，本身就是一种令人赞叹的艺术。 注：“芥子纳须弥”是一个源自佛教经典的成语，用来形容一个看似微小的空间，却能容纳极其巨大或广阔的世界。芥子：指的是芥菜的种子，非常微小。须弥：指的是须弥山（Sumeru），在佛教传说中，是世界的中心，一座无比宏伟、巨大的神山。所以，“芥子纳须弥”的字面意思就是“在小小的芥菜种子里，容纳下整座须弥山”。 3. “洞悉本质”的认知魔法 这是我认为最迷人的一点。学习位运算，会为你开启一扇“天眼”，让你能够穿透高级语言的层层封装，直视数据的二进制本质。你将开始理解，为什么一个简单的权限判断，用 &#38; 会比用 == 更具智慧；为什么一个哈希函数，需要用 ^ 和 &#60;&#60; 来制造“混乱”。这种认知的提升，会让你在阅读源码、设计系统时，获得前所未有的快感和深度。 揭秘“二进制魔术”的秘密 如果说位运算是一场精彩的魔术，那我非常乐意为你揭开这场魔术背后的秘密。 在我的全新微专栏 《用Go解锁位运算之美》中，我们将一起，从最基础的“手法”练起，逐步掌握那些令人拍案叫绝的“魔术流程”。我们将以经典的思想为蓝图，用工程化的 Go 语言为舞台，上演一场属于程序员的二进制魔术秀。 你将从这场“表演”中学到什么？ 一套“基本手法”：你将掌握定位、消除、分离二进制位 1 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/bit-manipulation-in-go-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/08/13/bit-manipulation-in-go">本文永久链接</a> &#8211; https://tonybai.com/2025/08/13/bit-manipulation-in-go</p>
<p>大家好，我是Tony Bai。</p>
<p>在编程这门手艺中，我们时常扮演着“建筑师”的角色，用一行行优雅的高级语言，构建起宏伟的应用大厦。但你是否曾停下脚步，好奇地探寻过这座大厦最深处的基石——那些由 0 和 1 构成的、既简单又神秘的二进制世界？</p>
<p>当你阅读 Go 标准库（比如 sync.Mutex 或 os.FileMode）的源码时，看到那些 &amp;、|、^、&lt;&lt; 符号以一种眼花缭乱的方式组合在一起，你感受到的是困惑，还是一丝兴奋？</p>
<p>那感觉，就像是魔法学院的学生，无意中翻开了一本古老的“咒语书”。书上的符号看似简单，却蕴含着驱动整个魔法世界运转的底层力量。</p>
<p>这本“咒语书”，就是<strong>位运算（Bit Manipulation）</strong>。它不是什么过时的黑科技，而是隐藏在现代软件工程之下的一门永恒的、优雅的<strong>艺术</strong>。</p>
<h2>这不是“屠龙之技”，而是一场思维的“魔术表演”</h2>
<p>很多人对位运算的印象，还停留在那些刁钻的面试题上。但真正的位操作艺术，远不止于此。它是一种思维方式，一种用最纯粹、最底层的方式与计算机对话的技艺。</p>
<p>掌握这门艺术，能为你带来什么？</p>
<p><strong>1. “点石成金”的性能魔法</strong></p>
<p>位运算直接在二进制层面操作数据，其速度快如闪电。在性能敏感的场景，它能将原本笨重的计算(结合for、if等)，优化成几次轻盈的位移和与或，带来数量级的性能提升。这就像魔术师在众目睽睽之下，瞬间完成了看似不可能的任务。</p>
<p><strong>2. “芥子纳须弥”的空间魔法</strong></p>
<p>一个 int64 变量，在位操作大师的手中，是 64 个可以独立控制的微观世界。通过精巧的位掩码，你可以在极小的空间内，存储和管理海量的状态信息。这种对空间的极致利用，本身就是一种令人赞叹的艺术。</p>
<blockquote>
<p>注：“芥子纳须弥”是一个源自佛教经典的成语，用来形容一个看似微小的空间，却能容纳极其巨大或广阔的世界。芥子：指的是芥菜的种子，非常微小。须弥：指的是须弥山（Sumeru），在佛教传说中，是世界的中心，一座无比宏伟、巨大的神山。所以，“芥子纳须弥”的字面意思就是“在小小的芥菜种子里，容纳下整座须弥山”。</p>
</blockquote>
<p><strong>3. “洞悉本质”的认知魔法</strong></p>
<p>这是我认为最迷人的一点。学习位运算，会为你开启一扇“天眼”，让你能够穿透高级语言的层层封装，直视数据的二进制本质。你将开始理解，为什么一个简单的权限判断，用 &amp; 会比用 == 更具智慧；为什么一个哈希函数，需要用 ^ 和 &lt;&lt; 来制造“混乱”。这种认知的提升，会让你在阅读源码、设计系统时，获得前所未有的快感和深度。</p>
<h2>揭秘“二进制魔术”的秘密</h2>
<p>如果说位运算是一场精彩的魔术，那我非常乐意为你揭开这场魔术背后的秘密。</p>
<p>在我的全新微专栏 《<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=4116476795552268292#wechat_redirect">用Go解锁位运算之美</a>》中，我们将一起，从最基础的“手法”练起，逐步掌握那些令人拍案叫绝的“魔术流程”。我们将以经典的思想为蓝图，用工程化的 Go 语言为舞台，上演一场属于程序员的二进制魔术秀。</p>
<p><strong>你将从这场“表演”中学到什么？</strong></p>
<ul>
<li><strong>一套“基本手法”</strong>：你将掌握定位、消除、分离二进制位 1 的核心技巧，并理解其在权限系统、状态判断这些经典“纸牌魔术”中的应用。</li>
<li><strong>两种“进阶戏法”</strong>：我们将深入探索位的“统计学”（高效计算 1 的个数）和“排列组合”（反转所有位），并揭秘 Go math/bits 标准库背后，那借助硬件完成的“大变活人”戏法。</li>
<li><strong>三大“压轴魔术”</strong>：我们将把所有知识融会贯通，去看位运算如何在<strong>紧凑数据结构</strong>、<strong>CRC32 数据校验</strong>、以及<strong>编译器级的除法优化</strong>这些真实工程场景中，上演令人叹为观止的最终表演。</li>
</ul>
<p>最重要的是，你将收获的，不仅是技巧，更是一种<strong>艺术家的眼光</strong>。它会让你在未来的编程生涯中，懂得欣赏和创造代码中的底层之美。</p>
<h2>魔术秀节目单抢先看</h2>
<p>这个专栏共包含 3 幕精心编排的“魔术表演”，层层递进，惊喜不断：</p>
<ul>
<li>
<p><strong>第一幕：入门篇：位运算的“基本功”与 Go 语言实践</strong></p>
<ul>
<li>x &amp; -x 的魔力：定位与分离</li>
<li>x &amp; (x-1) 的妙用：状态推进与高效判断</li>
<li>异或 ^ 的对称之美：从交换到校验</li>
<li>对齐的艺术：内存与性能的基石</li>
</ul>
</li>
<li>
<p><strong>第二幕：进阶篇：玩转位的“统计学”与“排列组合”</strong></p>
<ul>
<li>数 1 的三种境界：从朴素循环到 math/bits.OnesCount</li>
<li>“零”的踪迹：LeadingZeros 与 TrailingZeros 的实战价值</li>
<li>乾坤大挪移：位的反转与 math/bits.Reverse 的实现思路</li>
</ul>
</li>
<li>
<p><strong>第三幕：实战篇：位运算在高性能 Go 程序中的应用</strong></p>
<ul>
<li>场景一：用“位掩码”设计优雅、高效的状态机</li>
<li>场景二：深入 CRC32，理解位运算如何守护数据完整性</li>
<li>场景三：揭秘编译器如何用“魔法数字”干掉昂贵的除法运算</li>
</ul>
</li>
</ul>
<p>每一幕表演，都包含了详实的 Go 代码示例、“魔术”原理的慢动作回放、以及精心设计的互动环节（思考题），确保你不仅能看懂，更能亲手上台，成为一名真正的“二进制魔术师”。</p>
<h2>成为“二进制魔术师”的邀请函</h2>
<p>如果说高级语言让你学会了如何“沟通”，那么位运算可能不会改变你日常交谈的方式，但它会在关键时刻，让你拥有化腐朽为神奇的力量，赋予你的代码以灵魂和极致的效率。</p>
<p>如果你也对技术的底层之美充满好奇，如果你也渴望在平凡的代码中创造出不凡的艺术，那么，这份邀请函就是为你准备的。</p>
<p><strong>现在，我正式邀请你，与我一同，用 Go 这根魔杖，去施展二进制的无尽“魔术”。</strong> 扫码或点击<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=4116476795552268292#wechat_redirect">阅读全文</a>订阅《<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=4116476795552268292#wechat_redirect">用Go解锁位运算之美</a>》，开启你的二进制艺术探索之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/bit-manipulation-in-go-pr.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; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/08/13/bit-manipulation-in-go/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>
