<?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%ad%a6%e4%b9%a0/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Fri, 08 May 2026 23:03:53 +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/11/07/go-simple-illusion-easy-to-learn-hard-to-master/</link>
		<comments>https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master/#comments</comments>
		<pubDate>Fri, 07 Nov 2025 06:28:23 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[append]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Channelmisuse]]></category>
		<category><![CDATA[cloudnativeapplications]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[coreconcepts]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[do-while]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[fanin]]></category>
		<category><![CDATA[fanout]]></category>
		<category><![CDATA[for]]></category>
		<category><![CDATA[foreach]]></category>
		<category><![CDATA[GeekTime]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.24]]></category>
		<category><![CDATA[Goexpert]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoLanguageAdvancedCourse]]></category>
		<category><![CDATA[GoLanguageFirstCourse]]></category>
		<category><![CDATA[Gophilosophy]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[GoroutineLeak]]></category>
		<category><![CDATA[GoSkilledWorker]]></category>
		<category><![CDATA[httpserver]]></category>
		<category><![CDATA[iferrnil]]></category>
		<category><![CDATA[implicitdependency]]></category>
		<category><![CDATA[Interfaces]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[keywords]]></category>
		<category><![CDATA[LLMassistedcoding]]></category>
		<category><![CDATA[memorymodel]]></category>
		<category><![CDATA[monkeypatching]]></category>
		<category><![CDATA[net/http]]></category>
		<category><![CDATA[newbook]]></category>
		<category><![CDATA[nilinterface]]></category>
		<category><![CDATA[operationalcomplexity]]></category>
		<category><![CDATA[Orchestrator]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[pathtomastery]]></category>
		<category><![CDATA[productionsystem]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[race]]></category>
		<category><![CDATA[RaceConditions]]></category>
		<category><![CDATA[racedetector]]></category>
		<category><![CDATA[redditgolangforum]]></category>
		<category><![CDATA[rollyourown]]></category>
		<category><![CDATA[slices]]></category>
		<category><![CDATA[SoftwareEngineering]]></category>
		<category><![CDATA[standardlibrary]]></category>
		<category><![CDATA[standardlibrarysourcecode]]></category>
		<category><![CDATA[sync.Mutex]]></category>
		<category><![CDATA[synchronizationprimitives]]></category>
		<category><![CDATA[TonyBai]]></category>
		<category><![CDATA[unbufferedchannel]]></category>
		<category><![CDATA[underlyingbehavior]]></category>
		<category><![CDATA[while]]></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>
		<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=5362</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master 大家好，我是Tony Bai。 “Go 语言看起来如此简单，我的这种假设是错的吗？” 近日，一位刚接触 Go 几个月的新手在reddit golang论坛发出了这样一个真诚的提问。他感觉 Go “超级简单”，并好奇自己是否因为初学者的身份，而忽略了语言中那些“疯狂的复杂性”。 这个问题，立刻引发了社区关注。数百条评论从四面八方涌来，汇成了一场关于 Go 语言简单性本质的深度辩论。最终，社区的集体智慧凝聚成一个经典而又充满辩证性的共识：Go 的简单，是刻意为之的设计；而通往精通之路，则隐藏在简约表象之下的深邃之处。 本文将带你深入探索这座“简单”的冰山，从其光彩照人的水上部分，一直潜入其复杂深邃的水下世界。 “蜜月期”——为什么 Go 语言感觉如此简单？ 对于初学者而言，Go 带来的“简单”感受是真实且强烈的。这并非巧合，而是源于 Go 设计者们一系列深思熟虑的“减法”哲学。 极简的语法与关键字 “25 个关键字，宝贝！” 一位评论者这样感叹道。Go 有意地限制了语言的表面积，仅保留了构建大型系统所必需的核心元素。它只有一个循环结构 for，没有 while、do-while 或 foreach 的变体。这种极简主义，让学习者可以快速掌握语言的全貌，而不必记忆大量特殊语法。 “所见即所得”的代码 一位来自 Java/Python 背景的开发者分享道：“Go 给你的玩具可能更少，但至少你可以相信，它们不会在调试时反咬你一口。” Go 缺乏猴子补丁 (monkey patching)、复杂的继承体系和隐式的魔法，这意味着代码的行为更加可预测。“代码读起来就像它实际运行的样子，即便这意味着多写几行。” “电池自带”的强大标准库 “标准库太棒了，” 社区普遍赞同，“你需要花些时间才能理解，在不引入单个依赖的情况下，你能做多少事情。” 从 HTTP 服务器到密码学工具，Go 的标准库提供了构建现代网络服务所需 90% 的功能，让初学者可以立即开始构建有价值的应用，而无需在茫茫的第三方库中选择和配置。 幻象的破灭——“简单”背后的隐藏复杂性 当“蜜月期”结束，开发者开始构建更复杂的真实世界系统时，Go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/go-simple-illusion-easy-to-learn-hard-to-master-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master">本文永久链接</a> &#8211; https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master</p>
<p>大家好，我是Tony Bai。</p>
<p>“Go 语言看起来如此简单，我的这种假设是错的吗？”</p>
<p>近日，一位刚接触 Go 几个月的新手在reddit golang论坛发出了这样<a href="https://www.reddit.com/r/golang/comments/1oj9jb6/golang_seems_so_simple_am_i_wrong_to_assume_that/">一个真诚的提问</a>。他感觉 Go “超级简单”，并好奇自己是否因为初学者的身份，而忽略了语言中那些“疯狂的复杂性”。</p>
<p>这个问题，立刻引发了社区关注。数百条评论从四面八方涌来，汇成了一场关于 Go 语言简单性本质的深度辩论。最终，社区的集体智慧凝聚成一个经典而又充满辩证性的共识：<strong>Go 的简单，是刻意为之的设计；而通往精通之路，则隐藏在简约表象之下的深邃之处。</strong></p>
<p>本文将带你深入探索这座“简单”的冰山，从其光彩照人的水上部分，一直潜入其复杂深邃的水下世界。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-micro-column-2025-pr.png" alt="" /></p>
<h2>“蜜月期”——为什么 Go 语言感觉如此简单？</h2>
<p>对于初学者而言，Go 带来的“简单”感受是真实且强烈的。这并非巧合，而是源于 Go 设计者们一系列深思熟虑的“减法”哲学。</p>
<h3>极简的语法与关键字</h3>
<p>“25 个关键字，宝贝！” 一位评论者这样感叹道。Go 有意地限制了语言的表面积，仅保留了构建大型系统所必需的核心元素。它只有一个循环结构 for，没有 while、do-while 或 foreach 的变体。这种极简主义，让学习者可以快速掌握语言的全貌，而不必记忆大量特殊语法。</p>
<h3>“所见即所得”的代码</h3>
<p>一位来自 Java/Python 背景的开发者分享道：“Go 给你的玩具可能更少，但至少你可以相信，它们不会在调试时反咬你一口。” Go 缺乏猴子补丁 (monkey patching)、复杂的继承体系和隐式的魔法，这意味着代码的行为更加可预测。“代码读起来就像它实际运行的样子，即便这意味着多写几行。”</p>
<h3>“电池自带”的强大标准库</h3>
<p>“标准库太棒了，” 社区普遍赞同，“你需要花些时间才能理解，在不引入单个依赖的情况下，你能做多少事情。” 从 HTTP 服务器到密码学工具，Go 的标准库提供了构建现代网络服务所需 90% 的功能，让初学者可以立即开始构建有价值的应用，而无需在茫茫的第三方库中选择和配置。</p>
<h2>幻象的破灭——“简单”背后的隐藏复杂性</h2>
<p>当“蜜月期”结束，开发者开始构建更复杂的真实世界系统时，Go 的另一面便会逐渐显现。这份复杂性，并非来自语言本身，而是源于 Go 为了维持简单性，而将复杂性“转移”到的地方。</p>
<h3>并发：Go 的“光荣与荆棘”</h3>
<p>这是社区中被提及次数最多的“深水区”。Go 通过 goroutine 和 channel，将并发编程的门槛降到了前所未有的低度。然而，这种易用性也隐藏着巨大的风险。</p>
<blockquote>
<p>“理解并发作为一个概念可能会很复杂，但 Go 让实现它变得简单。”</p>
</blockquote>
<p>但“实现简单”不等于“用对简单”。</p>
<ul>
<li><strong>Goroutine 泄露</strong>：新手很容易创建出无人“负责”的 goroutine，导致其在后台永久运行，悄无声息地消耗内存和 CPU。</li>
<li><strong>竞态条件 (Race Conditions)</strong>：尽管 Go 提供了强大的竞态检测器 (-race)，但理解和避免数据竞争，需要对内存模型和同步原语（如 sync.Mutex）有深刻的理解。</li>
<li><strong>Channel 的滥用</strong>：“我数不清有多少次，人们到处使用 goroutine 和 channel，然后好奇为什么他们的项目变得如此之慢。” Channel 是强大的工具，但错误地使用无缓冲 channel、忘记关闭 channel、或用它来解决本该用互斥锁解决的问题，都会导致死锁、性能下降和难以调试的 bug。</li>
</ul>
<p><strong>精通并发，是区分 Go 新手与专家的第一道分水岭。</strong></p>
<h3>运维复杂性</h3>
<p>Go 的设计哲学，在某些方面将应用程序的韧性责任，从语言运行时“推”给了基础设施。这为 Go 程序带来了一种独特的<strong>运维复杂性</strong>。</p>
<p>最典型的例子就是 <strong>panic 的处理</strong>。</p>
<ul>
<li>在某些语言中（如 Java），一个未捕获的异常通常只会导致单个线程死亡，而整个应用程序进程会默认继续运行。</li>
<li>但在 Go 中，一个未被 recover 的 panic 会导致<strong>整个程序（进程）立即崩溃退出</strong>。Go 语言本身不提供自动重启或进程守护的能力，它将这种“灾难恢复”的职责，明确地交给了程序的运行环境。</li>
</ul>
<p>这意味着，构建一个高可用的 Go 服务，你<strong>必须</strong>依赖外部系统。正如一位资深开发者在讨论中指出的那样：</p>
<blockquote>
<p>“像 panic 这样的东西，要求你在一个编排器（如 K8s/ECS 等）下运行你的生产系统。”</p>
</blockquote>
<p>这种设计选择，对于新手来说可能是一个认知上的巨大跳跃。他们必须明白，Go 程序的健壮性，并不仅仅是代码层面的 if err != nil，更是在<strong>基础设施层面</strong>，通过配置进程管理器（如 systemd）或容器编排器（如 Kubernetes）的健康检查和自动重启策略来共同保证的。</p>
<p>Go 将自己定位为一个用于构建云原生应用的“零件”，而非一个大包大揽的“一体机”。这种对运维环境的<strong>隐性依赖</strong>，正是其简单性背后的一种深刻权衡。</p>
<h3>“魔鬼在细节中”：切片、接口与错误处理</h3>
<p>Go 的一些核心特性，虽然表面简单，但其底层机制却充满了需要深入理解的“微妙之处”。</p>
<ul>
<li><strong>切片 (Slices)</strong>：新手常常会对其“共享底层数组”的行为感到困惑，不经意间写出因 append 操作导致意外数据修改的 bug。</li>
<li><strong>接口 (Interfaces)</strong>：nil 接口与“值为 nil 的接口”之间的区别，是无数 Gopher 都曾踩过的经典“坑”。</li>
<li><strong>错误处理的冗长</strong>：if err != nil 虽然明确，但在 LLM 辅助编码时代到来之前，这种冗长曾是许多开发者的抱怨之源。现在，新的挑战变成了如何确保依赖 AI 的新手，能真正理解他们生成的每一行错误处理代码。</li>
</ul>
<h2>精通之路——从“知道”到“理解”</h2>
<p>那么，如何跨越从“简单”到“精通”的鸿沟？社区的智慧为我们指明了方向。</p>
<h3>接受 Go 的哲学</h3>
<p>Go 是一门<strong>“刻意设计的简单语言”</strong>。它的目标，是让大型团队能够编写出风格统一、易于阅读和维护的代码。这意味着，你需要接受它的“冗长”，理解它为何抵制某些“高级”特性，并学会在其提供的“约束”下优雅地解决问题。</p>
<h3>刻意练习核心概念</h3>
<p>不要满足于 API 的表面用法。花时间去：</p>
<ul>
<li><strong>画图理解并发模式</strong>：亲自绘制 goroutine 如何通过 channel 通信，理解扇入 (fan-in)、扇出 (fan-out) 等模式。</li>
<li><strong>实验切片的底层行为</strong>：编写小程序来观察 append 何时会触发底层数组的重新分配。</li>
<li><strong>深入标准库源码</strong>：阅读 net/http 或 context 包的源码，是理解 Go 设计哲学的最佳途径。</li>
</ul>
<h3>拥抱“造轮子”</h3>
<p>“你经常需要‘自己动手造轮子’(roll your own)”，一位开发者评论道。这在 Go 的世界里并非贬义。Go 强大的标准库为你提供了高质量的“零件”，鼓励你根据自己的具体需求，组合出最适合的“轮子”，而不是像其他生态那样，总是先去寻找一个庞大、臃肿的“现成汽车”。</p>
<h2>小结：“简单”是起点，而非终点</h2>
<p>回到最初的问题：Go 语言真的简单吗？</p>
<p><strong>是的，Go 的入口极其简单。</strong> 它拥有平缓的学习曲线，让有经验的程序员可以在一周内上手，让新手也能在短时间内构建出有用的程序。</p>
<p><strong>但精通 Go 绝不简单。</strong> 它的真正深度，不在于复杂的语法，而在于理解其并发模型背后的权衡、标准库设计的精妙、以及在简约哲学约束下构建复杂系统的工程智慧。</p>
<p>正如一位评论者所引用的那句古老格言：“<strong>一分钟学会，一辈子精通。</strong>” 虽说“一辈子”有些夸张，但这或许是对 Go 语言简单性与复杂性辩证关系的最佳诠释。Go 的“简单”，为你打开了一扇通往高效、可靠软件工程的大门，但门后的风景，需要你用持续的学习和深刻的思考，去亲自探索和领悟。</p>
<p>资料链接：https://www.reddit.com/r/golang/comments/1oj9jb6/golang_seems_so_simple_am_i_wrong_to_assume_that/</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><strong>想系统学习Go，构建扎实的知识体系？</strong></p>
<p>我的新书《<a href="https://book.douban.com/subject/37499496/">Go语言第一课</a>》是你的首选。源自2.4万人好评的极客时间专栏，内容全面升级，同步至Go 1.24。首发期有专属五折优惠，不到40元即可入手，扫码即可拥有这本300页的Go语言入门宝典，即刻开启你的Go语言高效学习之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-primer-published-4.png" 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/11/07/go-simple-illusion-easy-to-learn-hard-to-master/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu Server 14.04安装docker</title>
		<link>https://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/</link>
		<comments>https://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/#comments</comments>
		<pubDate>Fri, 26 Sep 2014 07:09:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[SSH]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[virtualbox]]></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=1547</guid>
		<description><![CDATA[近期在研究docker这一轻量级容器引擎，研究docker对日常开发测试工作以及产品部署运维工作能带来哪些便利。前些时候刚刚将工作环境从 Ubuntu搬到了Mac Air上，对Mac OS X的一切均不甚熟悉，给docker研究带来了不便，于是打算在VirtualBox中安装一Ubuntu Server作为docker之承载平台。这里记录一下安装配置过程，主要为了备忘，如果能给其他人带来帮助，我会甚感欣慰。 docker官方对ubuntu的支持是蛮好的。docker对Linux内核版本有要求，要&#62;=3.8，Ubuntu Server目前最新版本14.04.1恰符合这一要求，其kernel version = 3.13.0-32。 一、VirtualBox安装Ubuntu Server 14.04.1 VirtualBox安装Ubuntu OS做过了不止一遍，即便是换成最新的14.04.1 Server版，差别也没有太多，无非是按照安装提示，逐步Next。这里给Ubuntu Server 14.04分配了1G Memory, 32G动态硬盘空间。 【配置源】 &#160; 默认情况下，/etc/apt/sources.list中只有一组源：cn.archive.ubuntu.com/ubuntu。这个国外源的下载速度显然无法满足我的要求，于是我把我常用的sohu源加入sources.list中，并且放在前面： &#160; deb http://mirrors.sohu.com/ubuntu/ trusty main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-security main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted &#160; [...]]]></description>
			<content:encoded><![CDATA[<p>近期在研究<a href="http://docker.com">docker</a>这一轻量级容器引擎，研究docker对日常开发测试工作以及产品部署运维工作能带来哪些便利。前些时候刚刚将工作环境从 <a href="http://tonybai.com/tag/ubuntu">Ubuntu</a>搬到了Mac Air上，对Mac OS X的一切均不甚熟悉，给docker研究带来了不便，于是打算在<a href="http://virtualbox.org">VirtualBox</a>中安装一Ubuntu Server作为docker之承载平台。这里记录一下安装配置过程，主要为了备忘，如果能给其他人带来帮助，我会甚感欣慰。</p>
<p>docker官方对<a href="http://ubuntu.com">ubuntu</a>的支持是蛮好的。docker对Linux内核版本有要求，要&gt;=3.8，Ubuntu Server目前最新版本14.04.1恰符合这一要求，其kernel version = 3.13.0-32。</p>
<p><strong>一、VirtualBox安装Ubuntu Server 14.04.1</strong></p>
<p>VirtualBox安装Ubuntu OS做过了不止一遍，即便是换成最新的14.04.1 Server版，差别也没有太多，无非是按照安装提示，逐步Next。这里给Ubuntu Server 14.04分配了1G Memory, 32G动态硬盘空间。</p>
<p>【配置源】</p>
<p>&nbsp; 默认情况下，/etc/apt/sources.list中只有一组源：cn.archive.ubuntu.com/ubuntu。这个国外源的下载速度显然无法满足我的要求，于是我把我常用的sohu源加入sources.list中，并且放在前面：</p>
<p><font face="Courier New">&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-security main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted</font></p>
<p><font face="Courier New">&nbsp; deb-src http://mirros.sohu.com/ubuntu/ trusty main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-security main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted</font></p>
<p>&nbsp; 公司采用代理访问外网，于是还得在/etc/apt/apt.conf中加上代理的设置，否则无法更新源，也就无法安装第三方软件：</p>
<p><font face="Courier New">&nbsp; Acquire::http::Proxy &quot;http://username:passwd@proxyhost:proxyport&quot;;</font></p>
<p>&nbsp;【乱码处理】</p>
<p>&nbsp; 由于安装时候选择了中国区域（locale zh_CN.UTF-8），因此在VirtualBox的窗口中直接执行命令的提示信息可能是乱码。对于Server，我们一般是不会直接通过其主机显示 器登录使用的，都是通过终端访问，但在未安装和开启ssh服务和未配置端口转发前，我们只能先凑合这个窗口了。可先将/etc/default /locale中的LANGUAGE由&quot;zh_CN:zh&quot;改为&quot;en_US:en&quot;， logout后重新登录就可以看到非乱码的英文提示信息了。</p>
<p>【安装VirtualBox增强组件】</p>
<p>&nbsp; Ubuntu Server默认是不安装图形桌面的，只有一个命令行窗口，连鼠标都无法使用。因此增强组件安装的意义没有桌面系统那么强烈。我能想到的只有&ldquo;共享目录&rdquo;这一个功能有些用处。</p>
<p>&nbsp; 安装方法也不难，按下面步骤逐步操作即可：</p>
<p><font face="Courier New">&nbsp; sudo apt-get install build-essential linux-headers-$(uname -r) dkms gcc g++<br />
	&nbsp; sudo mnt /dev/cdrom /mnt<br />
	&nbsp; cd /mnt<br />
	&nbsp; sudo bash ./VBoxLinuxAdditions.run</font></p>
<p>&nbsp; 如果结果都是&quot;done&quot;，重启后就ok了。</p>
<p>【安装ssh服务】</p>
<p>&nbsp;&nbsp;&nbsp; ssh服务由openssh-server提供：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">sudo apt-get openssh-server</font><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp; 安装成功后，ssh server服务就会自动启动起来。</p>
<p>&nbsp;&nbsp; 不过我们还是需要修改一些配置，比如允许Root登录：打开/etc/ssh/sshd_config，将PermitRootLogin后面的内容改为yes。<br />
	&nbsp;&nbsp;&nbsp;<br />
	【设置端口转发】</p>
<p>&nbsp; 前面说过，对于Server，我们更多是在其他主机上通过ssh或telnet远程访问该Server并执行各种操作。由于这里是VirtualBox安 装的虚拟机，其他主机无法看到这台Server，我们需要设置端口转发将外部访问的数据转发给这个内部虚拟Server。</p>
<p>&nbsp; 我们通过VirtualBox软件提供的图形界面即可完成这个操作：<br />
	&nbsp;&nbsp;&nbsp; 1、&ldquo;设置&rdquo;这个虚拟机<br />
	&nbsp;&nbsp;&nbsp; 2、在&ldquo;网络&rdquo;标签中，点击&ldquo;端口转发&rdquo;按钮，进入端口转发规则添加窗口。<br />
	&nbsp;&nbsp;&nbsp; 3、添加一条规则：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 名称：ssh-rules<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 协议：TCP<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 主机IP、子系统IP可以为空。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 主机端口：2222<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 子系统端口：22<br />
	&nbsp;&nbsp; 4、配置结束</p>
<p>&nbsp;&nbsp;&nbsp; 配置结束后，我们在宿主机上netstat -an|grep 2222，可以看到VirtualBox增加了该端口2222的监听。</p>
<p>&nbsp; 现在我们就可以在其他机器上通过ssh -l tonybai 宿主机ip -p 2222的方式登录到我们新安装的这台虚拟Server了。</p>
<p>&nbsp;&nbsp;<br />
	<b>二、安装docker</b></p>
<p>docker目前的最新版本号是1.2.0，但14.04源中的docker还是正式稳定版1.0之前的版本，显然这是无法满足我的要求的。我们只能另外添加docker源来安装最新版docker。</p>
<p>&nbsp; 【安装docker】</p>
<p>&nbsp;&nbsp;&nbsp; 我们在/etc/apt/sources.list中加入下面这个源：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">deb <a class="moz-txt-link-freetext" href="http://mirror.yandex.ru/mirrors/docker/">http://mirror.yandex.ru/mirrors/docker/</a> docker main</font><br />
	&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 执行apt-get update。</p>
<p>&nbsp;&nbsp;&nbsp; sudo apt-get install lxc-docker</p>
<p><font face="Courier New">正在读取软件包列表&#8230; 完成<br />
	正在分析软件包的依赖关系树&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	正在读取状态信息&#8230; 完成&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	将会安装下列额外的软件包：<br />
	&nbsp; aufs-tools cgroup-lite git git-man liberror-perl lxc-docker-1.2.0<br />
	建议安装的软件包：<br />
	&nbsp; git-daemon-run git-daemon-sysvinit git-doc git-el git-email git-gui gitk<br />
	&nbsp; gitweb git-arch git-bzr git-cvs git-mediawiki git-svn<br />
	下列新软件包将被安装：<br />
	&nbsp; aufs-tools cgroup-lite git git-man liberror-perl lxc-docker lxc-docker-1.2.0<br />
	升级了 0 个软件包，新安装了 7 个软件包，要卸载 0 个软件包，有 59 个软件包未被升级。<br />
	需要下载 7,477 kB 的软件包。<br />
	解压缩后会消耗掉 35.4 MB 的额外空间。<br />
	您希望继续执行吗？ [Y/n] y</font></p>
<p>&nbsp; 这个源里的docker居然是最新版。于是安装之。安装后，我们执行docker version来确认一下安装是否成功。</p>
<p><font face="Courier New">&nbsp; tonybai@ubuntu-Server-14:~$ docker version<br />
	Client version: 1.2.0<br />
	Client API version: 1.14<br />
	Go version (client): go1.3.1<br />
	Git commit (client): fa7b24f<br />
	OS/Arch (client): linux/amd64<br />
	2014/09/26 13:56:53 Get http:///var/run/docker.sock/v1.14/version: dial unix /var/run/docker.sock: permission denied</font></p>
<p>&nbsp; 【为docker设置http代理】</p>
<p>&nbsp;&nbsp;&nbsp; 在公司内使用代理才能访问到外网，于是我们也需要为docker命令设置代理以使其顺利执行命令。</p>
<p>&nbsp;&nbsp;&nbsp; 我们安装的docker实际上分为两部分，docker命令行和docker daemon。两者是C/S结构，docker命令行将用户的请求转发给docker daemon，后者会真正与外部通信完成各种操作。</p>
<p>&nbsp;&nbsp;&nbsp; 于是我们可以这样为docker daemon设置http_proxy:<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; sudo service docker stop<br />
	&nbsp;&nbsp;&nbsp; sudo http_proxy=&#39;<a class="moz-txt-link-freetext" href="http://baim:xxx@proxy.neusoft.com:8080">http://user:passwd@proxyhost:port</a>&#39; docker -d &amp;</font></p>
<p>&nbsp;&nbsp;&nbsp; 这样设置启动后，我们可以通过下面命令测试设置是否ok：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New"> sudo docker search ubuntu</font></p>
<p>&nbsp;&nbsp;&nbsp; 如果你看到下面信息，说明设置成功了：</p>
<p>&nbsp;<font face="Courier New">&nbsp;&nbsp; tonybai@ubuntu-Server-14:~$ sudo docker search ubuntu<br />
	[info] GET /v1.14/images/search?term=ubuntu<br />
	[b36518a9] +job search(ubuntu)<br />
	[b36518a9] -job search(ubuntu) = OK (0)<br />
	NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DESCRIPTION&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STARS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OFFICIAL&nbsp;&nbsp; AUTOMATED<br />
	ubuntu&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Official Ubuntu base image&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 709&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	dockerfile/ubuntu&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Trusted automated Ubuntu (http://www.ubunt&#8230;&nbsp;&nbsp; 24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]<br />
	crashsystems/gitlab-docker&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A trusted, regularly updated build of GitL&#8230;&nbsp;&nbsp; 20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]<br />
	ubuntu-upstart&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Upstart is an event-based replacement for &#8230;&nbsp;&nbsp; 13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font><br />
	&#8230; &#8230;.</p>
<p>&nbsp;</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>关于编程语言学习的一些体会</title>
		<link>https://tonybai.com/2013/10/22/some-experience-about-learning-programming-language/</link>
		<comments>https://tonybai.com/2013/10/22/some-experience-about-learning-programming-language/#comments</comments>
		<pubDate>Tue, 22 Oct 2013 15:47:33 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Common-Lisp]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Erlang]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[monad]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Prolog]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Scala]]></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=1423</guid>
		<description><![CDATA[Learn at least one new language every year. &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8212; Andy Hunt and Dave Thomas 自己一直是&#8220;每年学习一门新语言&#8221;的忠实拥趸，曾先后认真地学习了Haskell、Common Lisp、Python、Go等语言，对Prolog、Scala、Erlang、Lua、PHP也有一定了解。但几年下来，只有Python一门语言算 是真正被留在我的大脑里，用在了工作中。其他那几门语言留下来的只是一些思想了。这似乎符合了Andy Hunt和Dave Thomas在《程序员修炼之道》中对于这一实践目的的阐述：&#8220;学会用多种方式解决问题，扩展我们的视野，避免思路僵化和停滞不前&#8221;^_^。 即便是残存的思想，其实也并不深刻。要真正会运用新思维并非那么简单。一门编程语言从入门到精通，至少要经历学语法、做实践、用idioms（写出地道的代码）三个阶段。这让我深刻的感悟到：不以使用为目的的语言学习，都是在浪费生命！ 有精力多学习些语言自然很好，我迫切期待能拥有一个像&#8220;七龙珠&#8221;中孙悟空那样的&#8220;精神时光屋&#8221;呢。但现实中，人的精力是有限的，而我们要面对的计算机科学领域中的知识、技能以及问题却似乎是无限的。因此在&#8220;每年至少学习一门新语言&#8221;这一实践上，建议不要过于教条。 从编程语言自身来看，范型(Paradigm)是影响语言思维差异的主要因素，而编程语言的范型有限，主流的也就那么几种：命令式（过程式）、函数式、逻 辑式、面向对象等。每种范型的背后都有几种、十几种甚至几十种语言，我们其实没有必要都去学。从拓展视野的角度去说，从每种主流范式中找到一两门典型的语 言去学习就可以了。比如命令式的，我们可以选择C；函数式我们选择Haskell；逻辑式的选择Prolog；面向对象的选择Java等。 即便是从每个范型中挑出一门，你要付出的精力依旧不少，我们还要考虑其实用性：要以使用为目的。如果能将其用在工作中，天天与你相伴，被他人接受，自然最 好；退而求其次，你能找到一两个开源项目，并参与其中也是可以的，至少可以让你保持手热；如果这两点都无法做到，仅仅是凭借个人的热情与坚持，那是不会持 久的，若干时间后，你就会对其生疏，可能连基本的&#34;Hello World&#34;语法都记不得了。不过这个年头，思想也不能不要。在有剩余精力的前提下，挑选些牛人们极力&#8220;鼓吹&#8221;的语言，吸收一下其思想精华，说不定哪天就 能用得上，让自己和大家都感觉你很NB，抬高一下自己的身价^_^。记住：编程语言也是要拼爹的，系出名门的语言(诸如Go、Dart等)自然得到更多的青睐、使用和推广，出位的几率也就高出许多，尤其是在目前新编程语言百花齐放的阶段。因此在选择有思想的新语言时，最好在这些名门之后中做优选。 这个时代喜欢&#8220;专家&#8221;，因此我们在一两门语言上务必要做到&#8220;精专&#8221;，这是会给你带来黄油和面包的语言。要专到什么程度呢？我有一个同事，什么问题都用C解决。他甚至为此写了个不小的基础框架，所有业务问题的Code放在框架中被回调即可，即便是这个问题用Python实现只需几行代码。 计算机科学的研究核心是什么？我想肯定不是编程语言，就好比社会科学研究的核心不是人类语言一样。我比较欣赏这样的观点：作为程序员而言，最重要的是去创造，而不是研究。我们应更多的利用已经掌握的语言解决现实中的问题。做 编程语言研究的人可能要了解各种语言的特点与实现方式，但对于大多数的程序员来说，其实我们只需要关注问题域：做底层平台开发的，关注机器模型、通信原理 以及OS原理和实现细节；做算法的，很荣幸，那才是正统的程序设计的核心；前端攻城师则更多关注用户的体验。而在这些解决实际问题的过程中，我们更多采用 的是&#8220;制式&#8221;的编程语言。即做平台开发的，一般用C，C++等系统编程语言，更多的考虑的是性能；做前端开发的，PHP/JavaScript不可或缺。 我们要考虑的是如何利用这些制式的编程语言去解决问题，而在这些制式语言上，我们要做到精通。 从新兴语言中借鉴新思想，然后在旧语言中实现新语言的特性，其实更多是在旧语言中实现了某 种语法糖，你爱吃，不代表其他人也理解也爱吃，还容易被人误认为是&#8220;炫技&#8221;。如果你是技术负责人，且经过评估，新语言十分适合这个问题域，那莫不入直接引 入这门语言，让大家都能使用到这门语言的新思想、新特性。 辩证的说，任何一种编程语言都有其利与弊，比如Haskell，纯函数式语言，变量不能改变，无状态，对并行处理具有天然的适应性，但在处理基本IO时却要编写难于理解的monad；而在命令式语言中，这种IO处理简直简单的不得了。 关于函数式语言，个人感觉未来若干年内仍难以大行其道，建议还是跟上命令式语言的演化主线吧。 跨越问题域学习语言，通常收获不大。一个做平台服务端，用惯了C的资深程序员，让他去学PHP写前端代码，估计是无法迸发出任何火花的。 以上是自己这些年关于编程语言学习的一些体会，比较零散，但希望能有帮助。 &#169; 2013, bigwhite. 版权所有.]]></description>
			<content:encoded><![CDATA[<p><span class="st"><i>Learn at least one <em>new language every year</em>.<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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8212; Andy Hunt and Dave Thomas</i></span></p>
<p><span style="line-height: 1.6em;">自己一直是&ldquo;每年学习一门新语言&rdquo;的忠实拥趸，曾先后认真地学习了<a href="http://tonybai.com/2010/11/14/the-chinese-translation-project-for-programming-in-haskell/">Haskell</a>、<a href="http://tonybai.com/2011/06/21/hello-common-lisp/">Common Lisp</a>、<a href="http://tonybai.com/2013/07/09/an-implementation-of-python-commandline-variables/">Python</a>、<a href="http://tonybai.com/2012/08/17/hello-go/">Go</a>等语言，对<a href="http://tonybai.com/2012/05/08/translate-seven-languages-in-seven-weeks/">Prolog</a>、<a href="http://www.scala-lang.org">Scala</a>、<a href="http://www.erlang.org">Erlang</a>、<a href="http://www.lua.org">Lua</a>、<a href="http://php.net">PHP</a>也有一定了解。但几年下来，只有<a href="http://www.python.org">Python</a>一门语言算 是真正被留在我的大脑里，用在了工作中。其他那几门语言留下来的只是一些思想了。这似乎符合了Andy Hunt和Dave Thomas在《<a href="http://www.douban.com/subject/1152111">程序员修炼之道</a>》中对于这一实践目的的阐述：&ldquo;学会用多种方式解决问题，扩展我们的视野，避免思路僵化和停滞不前&rdquo;^_^。</span></p>
<p>即便是残存的思想，其实也并不深刻。要真正会运用新思维并非那么简单。一门编程语言从入门到精通，至少要经历学语法、做实践、用idioms（写出地道的代码）三个阶段。这让我深刻的感悟到：<b>不以使用为目的的语言学习，都是在浪费生命</b>！</p>
<p>有精力多学习些语言自然很好，我迫切期待能拥有一个像&ldquo;<a href="http://book.douban.com/subject/2042982/">七龙珠</a>&rdquo;中孙悟空那样的&ldquo;精神时光屋&rdquo;呢。但现实中，人的精力是有限的，而我们要面对的计算机科学领域中的知识、技能以及问题却似乎是无限的。因此在&ldquo;每年至少学习一门新语言&rdquo;这一实践上，建议<b>不要过于教条</b>。 从编程语言自身来看，范型(Paradigm)是影响语言思维差异的主要因素，而编程语言的范型有限，主流的也就那么几种：命令式（过程式）、函数式、逻 辑式、面向对象等。每种范型的背后都有几种、十几种甚至几十种语言，我们其实没有必要都去学。从拓展视野的角度去说，从每种主流范式中找到一两门典型的语 言去学习就可以了。比如命令式的，我们可以选择C；函数式我们选择Haskell；逻辑式的选择Prolog；面向对象的选择Java等。</p>
<p>即便是从每个范型中挑出一门，你要付出的精力依旧不少，我们还要考虑其实用性：要以使用为目的。如果能将其用在工作中，天天与你相伴，被他人接受，自然最 好；退而求其次，你能找到一两个开源项目，并参与其中也是可以的，至少可以让你保持手热；如果这两点都无法做到，仅仅是凭借个人的热情与坚持，那是不会持 久的，若干时间后，你就会对其生疏，可能连基本的&quot;Hello World&quot;语法都记不得了。不过这个年头，思想也不能不要。在有剩余精力的前提下，挑选些牛人们极力&ldquo;鼓吹&rdquo;的语言，吸收一下其思想精华，说不定哪天就 能用得上，让自己和大家都感觉你很NB，抬高一下自己的身价^_^。记住：<a href="http://tonybai.com/2012/10/08/the-new-age-of-programming-language/"><b>编程语言也是要拼爹的</b></a>，<span class="st">系出名门的语言(诸如Go、Dart等)自然得到更多的青睐、使用和推广，出位的几率也就高出许多，尤其是在目前新编程语言百花齐放的阶段。因此在选择有思想的新语言时，最好在这些名门之后中做优选。</span></p>
<p><span class="st">这个时代喜欢&ldquo;专家&rdquo;，因此我们在一两门语言上务必要做到&ldquo;精专&rdquo;，这是会给你带来黄油和面包的语言。</span><span class="st">要专到什么程度呢？我有一个同事，什么问题都用C解决。他甚至为此写了个不小的基础框架，所有业务问题的Code放在框架中被回调即可，即便是这个问题用Python实现只需几行代码。</span></p>
<p><span class="st">计算机科学的研究核心是什么？我想肯定不是编程语言，就好比社会科学研究的核心不是人类语言一样。</span><span class="st">我比较欣赏这样的观点：</span><span class="st"><b>作为程序员而言，最重要的是去创造，而不是研究</b>。我们应更多的利用已经掌握的语言</span>解决现实中的问题。<span class="st">做 编程语言研究的人可能要了解各种语言的特点与实现方式，但对于大多数的程序员来说，其实我们只需要关注问题域：做底层平台开发的，关注机器模型、通信原理 以及OS原理和实现细节；做算法的，很荣幸，那才是正统的程序设计的核心；前端攻城师则更多关注用户的体验。而在这些解决实际问题的过程中，我们更多采用 的是&ldquo;制式&rdquo;的编程语言。即做平台开发的，一般用C，C++等系统编程语言，更多的考虑的是性能；做前端开发的，PHP/JavaScript不可或缺。 我们要考虑的是如何利用这些制式的编程语言去解决问题，而在这些制式语言上，我们要做到精通。</span></p>
<p>从新兴语言中借鉴新思想，<span class="st">然后在旧语言中实现新语言的特性，其实更多是在旧语言中实现了某 种语法糖，你爱吃，不代表其他人也理解也爱吃，还容易被人误认为是&ldquo;炫技&rdquo;。如果你是技术负责人，且经过评估，新语言十分适合这个问题域，那莫不入直接引 入这门语言，让大家都能使用到这门语言的新思想、新特性。</span></p>
<p><span class="st">辩证的说，任何一种编程语言都有其利与弊，比如<a href="http://haskell.org">Haskell</a>，纯函数式语言，变量不能改变，无状态，对并行处理具有天然的适应性，但在处理基本IO时却要编写难于理解的monad；而在命令式语言中，这种IO处理简直简单的不得了。</span></p>
<p><span class="st">关于函数式语言，个人感觉未来若干年内仍难以大行其道，建议还是跟上命令式语言的演化主线吧。</span></p>
<p>跨越问题域学习语言，通常收获不大。一个做平台服务端，用惯了C的资深程序员，让他去学PHP写前端代码，估计是无法迸发出任何火花的。</p>
<p><span class="st">以上是自己这些年关于编程语言学习的一些体会，比较零散，但希望能有帮助。</span></p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/10/22/some-experience-about-learning-programming-language/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>利用ZooKeeper服务实现分布式系统的配置数据同步</title>
		<link>https://tonybai.com/2013/08/28/implement-config-sync-for-distributed-system-with-zookeeper-services/</link>
		<comments>https://tonybai.com/2013/08/28/implement-config-sync-for-distributed-system-with-zookeeper-services/#comments</comments>
		<pubDate>Wed, 28 Aug 2013 11:32:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[leader-election]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[trigger]]></category>
		<category><![CDATA[ZooKeeper]]></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=1383</guid>
		<description><![CDATA[很多时候，一旦习惯了某些事情，也就习惯了它们的恶劣，习惯了它们的丑陋，习惯了它们&#8220;赋予&#8221;你的各种痛苦。 &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#8211; Tony Bai 一、痼疾难解 曾几何时，在那个还没有集群化，没有分布式的时代，它还是一个不错的方案，至少在线上没有暴露出太多问题，它也不在我们关注的重点范围之内。但随 着集群化、分布式的新版本的到来，那一大坨遗留的代码就变得格外让人不顺眼，同时问题也随之在线上暴露开来了。 [...]]]></description>
			<content:encoded><![CDATA[<p><i>很多时候，一旦习惯了某些事情，也就习惯了它们的恶劣，习惯了它们的丑陋，习惯了它们&ldquo;赋予&rdquo;你的各种痛苦。<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; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &#8211; Tony Bai</i></p>
<p><b>一、痼疾难解</b></p>
<p>曾几何时，在那个还没有集群化，没有分布式的时代，它还是一个不错的方案，至少在线上没有暴露出太多问题，它也不在我们关注的重点范围之内。但随 着集群化、分布式的新版本的到来，那一大坨遗留的代码就变得格外让人不顺眼，同时问题也随之在线上暴露开来了。</p>
<p>这里的&ldquo;它&rdquo;指的就是我们目前的业务配置数据同步方案。简单描述这个方案如下：</p>
<p>* 方案涉及两个角色 &#8211; 数据库(DB)与应用节点（app_node)；<br />
	* 所有的业务配置数据均统一存储在DB中；<br />
	* 应用节点在启动后从DB中读取最新业务配置数据；<br />
	* 应用节点运行过程中，如果DB中的业务配置数据发生变更（增/删/改），DB中的触发器(trigger)将会执行。在触发器的脚本中，触发器将会【串 行】地与每个应用节点建立TCP链接，并将业务配置表的变更信息发给各个应用节点。 应用节点会接收并【解析】触发器发过来变更数据包，并同步到自己的本地内存中。这样就达到了运行时更新配置的目的。</p>
<p>上面我用【】标记了两个关键词：&ldquo;串行&rdquo;和&ldquo;解析&rdquo;。这两个词隐含有这个方案的两个主要问题。</p>
<p>&ldquo;串行&rdquo; &#8211; 意味着每一次DB的业务配置数据变更，trigger脚本都要逐个与应用节点建立链接并收发数据。当应用节点逐渐增多时，每一次业务数据同步都会相当地耗 时。尤其是当某个应用节点所在主机出现问题时，到该节点链接建立的过程会阻塞，导致整个业务配置数据同步的时间达到无法忍受的地步。</p>
<p>&ldquo;解析&rdquo; &#8211; 我们自定义了trigger与应用节点之间的协议包。协议包中包含了每次变更的详细信息，比如在某个表添加一条记录，trigger会将这个记录的每个字 段信息排成一行打包发给应用节点。应用节点收到这个包后，会根据已有的表字段信息对该包进行解析。看得出这是一个很强的耦合：表字段一旦修 改，trigger脚本要修改，应用节点的解析函数要修改，还要考虑协议包中表字段的排序。如果应用节点解析时与trigger脚本打包时的字段 顺序不同的话，那就可能出现严重错误，而且这种错误有时难于校验并难于发现。</p>
<p><b>二、曾经的努力</b></p>
<p>针对这个方案的不足，我们曾经也做过改进，但主要针对的是解决&ldquo;串行&rdquo;这个问题上。</p>
<p>第一次改进：同步的发起能否并行做？trigger脚本能否并行发起对各个应用节点的链接建立请求？</p>
<p>Java组同事对trigger脚本做了改进。让trigger脚本调用function，而function中又调用了写好的Java方 法，Java代码由DB加载到环境中。在Java方法中创建多个同步线程，并发与各应用节点建立链接并发送数据。这个方法的确可以变&ldquo;串行&rdquo;为 &ldquo;并行&rdquo;，但不知为何生产环境中实际运行时偶尔会出现异常，该异常发生在DB中，影响很大。有时还会导致DB的一些异常现象。至今原因尚未明确， 我们无奈退回到以前的方案。</p>
<p>第二次改进：从Push模式到Pull模式</p>
<p>在之前部门新规划的一个产品中，开发人员对数据同步的机制做了重新的设计，将原来的Push模式改为了Pull模式。大致方案是：<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; * 业务数据变更时，trigger直接将变更内容（以老方案中那个协议包的打包格式）写到一个&ldquo;变更日志表&rdquo;中，每条记录有一个唯一的序号，序号递增。<br />
	&nbsp;&nbsp;&nbsp; * 应用节点启动后，从DB加载最新配置信息，查询&ldquo;变更日志表&rdquo;，得到该表内最新的一条记录的序号n。<br />
	&nbsp;&nbsp;&nbsp; * 应用节点以&ldquo;轮询&rdquo;的方式定期查询&ldquo;变更日志表&rdquo;，并读取和解析那些序号比序号n更新的记录；更新完后，将继续保存最新的一条记录序号。<br />
	&nbsp;&nbsp;&nbsp; * 数据库中有job定期对&ldquo;变更日志表&rdquo;中的记录进行过期删除处理。</p>
<p>个人感觉第二个方案应该是理想方案的一个雏形，虽然目前它的同步更新可能不是那么及时，与DB交互过多（方案细节中每个应用节点在处理完一条记录 后还要更新记录的状态）。该方案设计者也完全也可以放弃那个导致耦合的协议包设计，但他最终还是选择保留了原有协议包解析函数。目前该方案在产品 环境下运行还算良好，并未暴露出什么问题。这算是一次有效的改进，也为本文中要提到的方案提供了一些思路启示。</p>
<p><b>三、与时俱进</b></p>
<p><a href="http://zookeeper.apache.org">ZooKeeper</a>生来就具备解决分布式系统的配置分发和同步的能力。利用ZooKeeper服务实现分布式系统的统一配置中心已经不是那么新鲜 的话题了。最简单的模型莫过于将配置数据存储在ZooKeeper上的路径节点上，然后应用节点在这些配置节点上添加watch。当配置数据变更 时，每个应用节点都可以及时得到通知，同步到最新数据。这种模型对于一些量少简单的系统配置来说较为合适。对于我们每个表动辄上万条配置的情形似 乎不那么适合，想象一下每个应用节点要添加上万个watch，这对ZooKeeper而言也是压力山大啊。因此用ZooKeeper提供的诸多服 务如何来优化我们上面提到的两个主要问题呢？这里提出一种方案仅供参考。</p>
<p>方案示意图：</p>
<p>DB&nbsp; &#8212;-&gt; <b>Config Center Services</b>(css_agent + ZooKeeper)&nbsp; &#8212;&gt; App Node</p>
<p>在新方案中，我们要：<br />
	&nbsp;&nbsp;&nbsp; 保留 &#8211; 保留trigger脚本，作为业务数据变更的唯一的触发起点；<br />
	&nbsp;&nbsp;&nbsp; 摒弃 &#8211; 摒弃那个复杂的带来耦合的协议格式；<br />
	&nbsp;&nbsp;&nbsp; 借鉴 &#8211; 借鉴&ldquo;Push -&gt; Pull&rdquo;的数据获取方式。</p>
<p>新方案中除了DB、应用节点(app_node)外，新增加了一个角色Config Center Services(缩写为ccs），ccs由ZooKeeper + ccs_agent的集群组成。简单起见，每个ZooKeeper节点上部署一个ccs_agent。这些角色之间的数据流和指令流关系，即该方案的原理 如下：</p>
<p>&nbsp;&nbsp;&nbsp; <b>* 初始化</b><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; ZooKeeper集群启动；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; ccs_agent启动，利用ZooKeeper提供的<a href="http://tonybai.com/2013/08/23/leader-election-using-zookeeper/">leader election服务</a>，选出ccs_agent leader。ccs_agent leader启动后负责在ZooKeeper中建立业务配置表node，比如：表employee_info_tab对应的node路径为&ldquo;/ccs /foo_app/employee_info_tab&rdquo;；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; ccs_agent启动后会监听一个端口，用来接受DB trigger向其发起的数据链接；<br />
	&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &#8211; 应用节点启动，监听ZooKeeper上所有（数量有限的）业务配置表node的child event；<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; <b>* 数据变更</b><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; DB中某业务表比如employee_info_tab增加了一条id为&quot;1234567&quot;的记录；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 触发器启动，向ccs_agent cluster中任意一个可用的节点建立链接，并将数据包&ldquo;^employee_info_tab|ADD|1234567$&quot;发送给 ccs_agent；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; ccs_agent收取并解析trigger发来的数据包，在对应的/ccs/foo_app/employee_info_tab下建立<b>ZOO_SEQUENCE</b>类 型节点&ldquo;item-000000000&rdquo;，该节点的值为&ldquo;ADD 1234567&quot;；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; ZooKeeper将/ccs/foo_app/employee_info_tab节点的child事件发给所有watch该节点事件的应用节点；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 应用节点&ldquo;取出&rdquo;/ccs/foo_app/employee_info_tab节点下的children节点&quot;item-000000000&quot;，并读取 其值，后续到DB的employee_info_tab中将id = 1234567的这条记录select出来，将该条记录更新到本地内存中。应用节点记录下处理过的当下节点id为&quot;item-000000000&quot;；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; DB业务表employee_info_tab又增加了两条记录，id分别为&quot;7777777&quot;和&quot;8888888&quot;，经过上面描述的流程，/ccs /foo_app/employee_info_tab节点下会增加&quot;item-000000001&quot;和&quot;item-000000002&quot;两项； 应用节点最终会收到child事件通知。应用节点&ldquo;取出&rdquo;/ccs/foo_app/employee_info_tab节点下的所有 children节点并排序。之后，处理那些id号大于&quot;item-000000000&quot;的节点，并将当前节点id记录为&ldquo;item- 000000002&quot;。依次类推。</p>
<p>&nbsp;&nbsp;&nbsp; <b>* 过期处理</b><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; ccs_agent leader负责定期扫描ZooKeeper中/ccs下各个表节点下的子项，对于超出过期时间的item进行删除处理。</p>
<p>&nbsp;&nbsp;&nbsp; <b>* 应用节点重启</b><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; -&nbsp; 应用节点重启后，会首先从db读取最新信息，并记录启动时间戳；<br />
	&nbsp; &nbsp; &nbsp; &nbsp; -&nbsp; 应用节点重启后，在收到zookeeper的数据变更事件后，会根据当前时间戳与变更表节点下的item创建时间进行比较，并仅处理比启动时间戳新的 item的数据。<br />
	&nbsp;&nbsp;&nbsp;</p>
<p>这个方案主要利用了ZooKeeper提供的leader election服务以及sequence节点的特性，几点好处在于：</p>
<p>&nbsp;&nbsp;&nbsp; &#8211; 串行通知变为并行通知，且通知到达及时；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 变更数据的Push模式为Pull模式，降低了或去除了诸多耦合，包括：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 1) 去除trigger脚本与表字段及字段顺序的耦合；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 2) 去除应用节点与表字段顺序的耦合；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3) 降低应用节点与表字段构成的耦合。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 应用节点无需复杂的包解析，简化后期维护。</p>
<p>当然为了该方案新增若干网元会给产品部署和维护带来一些复杂性，这算是不足之处吧。</p>
<p><b>四、Demo</b></p>
<p><a href="https://github.com/bigwhite/experiments/tree/master/zookeeper_c_bindings_examples/config_center_services">这里</a>有一个600多行代码的Demo，模拟新方案中几个角色：<br />
	&nbsp;&nbsp;&nbsp; DB &#8211; trigger_sim.py<br />
	&nbsp;&nbsp;&nbsp; 应用节点 &#8211; app.c<br />
	&nbsp;&nbsp;&nbsp; ccs_agent &#8211; ccs_agent.c</p>
<p>模拟的步骤大致如下（单机版）：</p>
<p>a) 启动ZooKeeper<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; $&gt; zkServer.sh start<br />
	&nbsp;&nbsp;&nbsp; JMX enabled by default<br />
	&nbsp;&nbsp;&nbsp; Using config: /home1/tonybai/.bin/zookeeper-3.4.5/bin/../conf/zoo.cfg<br />
	&nbsp;&nbsp;&nbsp; Starting zookeeper &#8230; STARTED</font></p>
<p>b) 启动ccs_agent<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; $&gt; ccs_agent<br />
	&nbsp;&nbsp;&nbsp; This is [ccs-member0000000037], i am a leader<br />
	&nbsp;&nbsp;&nbsp; /ccs node exists<br />
	&nbsp;&nbsp;&nbsp; /ccs/employee_info_tab node exists<br />
	&nbsp;&nbsp;&nbsp; /ccs/boss_info_tab node exists<br />
	&nbsp;&nbsp;&nbsp; trigger listen thread start up!<br />
	&nbsp;&nbsp;&nbsp; item expire thread start up!</font></p>
<p>c) 启动app</p>
<p>d) 使用trigger_sim.py模拟DB触发trigger<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">$&gt; trigger_sim.py employee_info_tab ADD 1234567</font></p>
<p>可以看到ccs_agent输出结果如下：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; table[employee_info_tab], oper_type[ADD], id[1234567]</font></p>
<p>app的输出如下：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; child event happened: type[4]<br />
	&nbsp;&nbsp;&nbsp; item-0000000015<br />
	&nbsp;&nbsp;&nbsp; employee_info_tab: execute [ADD 1234567]</font></p>
<p>大约30s后，ccs_agent会输出如下：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; [expire]: employee_info_tab: expire [item-0000000015]</font></p>
<p>模拟步骤在README里有写。这里仅是Demo代码，存在硬编码以及异常处理考虑不全面的情况，不要拍砖哦。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/08/28/implement-config-sync-for-distributed-system-with-zookeeper-services/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>利用ZooKeeper服务实现分布式系统的Leader选举</title>
		<link>https://tonybai.com/2013/08/23/leader-election-using-zookeeper/</link>
		<comments>https://tonybai.com/2013/08/23/leader-election-using-zookeeper/#comments</comments>
		<pubDate>Fri, 23 Aug 2013 14:31:02 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[BAT]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[ZooKeeper]]></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=1377</guid>
		<description><![CDATA[每次与Java组的同事们坐下来谈技术、谈理想、谈人生时，Java组的同事总会向我们投来羡慕的眼光：卧槽！又是自己开发的工具，太NB了。这时C程序 员们的脸上就会洋溢出自豪的笑容，然后内心骂道：谁让我们没有现成的呢。另一个空间里的某些&#8220;无C不欢&#8221;们或者某些&#8220;C Guru&#8221;们会骂道：靠，有了也不用，自己写！ 有时候，C程序员真的有一种下意识：不情愿使用其他语言开发的工具、框架或服务，且比其他程序员更爱&#8220;重新发明轮子&#8221;（有利有弊）。也许这是某种 骨子里的自负在搞怪；另外一个极端：今天和我聊天的一个经验丰富的C程序员还在忧虑：如果离职是否有公司会要他:(。 其实这个时代的C程序员一直活得挺纠结^_^。 这个世界，软硬件发展日新月异，越来越多的后端程序用Java等其他语言实现。Java高级选手在这个世界上也甚是吃香，这个你看看各大招聘网站 就知道了。再听听坊间&#8220;BAT&#8221;三巨头给出的高高在上的offer价格，也可以看出Java程序员是多么的有&#8220;钱途&#8221;和受欢迎了。当然拿好offer的前提是你的Java底子不薄。 其实无论用什么编程语言，成为牛人后，钱途也都是杠杠的。 没有什么好的开场白，于是有了上面一些&#8220;胡言乱语&#8221;。我们言归正传。 本文是一篇初级技术博文。讲的是如何使用ZooKeeper C API通过ZooKeeper的服务实现分布式系统的Leader选举。当然这一试验是为了尝试解决我们自己的分布式系统在集中配置数据分发这一环节上的 一个&#8220;固疾&#8221;。还好我还不那么纠结，也没有重新实现ZooKeeper的冲动，于是我就用了ZooKeeper这一Java实现的成熟的分布式 系统的服务框架。 * 搭建ZooKeeper服务环境 &#160;&#160;&#160; &#8211; 下载官方stable release版本 &#8211; ZooKeeper3.4.5。解压后，将$ZooKeeper_INSTALL_PATH/bin加入到PATH变量中（其中ZooKeeper_INSTALL_PATH为解压后ZooKeeper-3.4.5目录的绝对路径）。 &#160;&#160;&#160; &#8211; 试验环境下，最简单的ZooKeeper用法就是使用单机版。 &#160;&#160;&#160; &#160; 进入到$ZooKeeper_INSTALL_PATH/conf下，将zoo_sample.cfg改名为zoo.cfg，即可作为单机版ZooKeeper的配置文件。当然你也可以像我一样随意修改修改： &#160;&#160;&#160; &#160; # The number of milliseconds of each tick &#160;&#160; tickTime=2000 &#160;&#160; # The number of ticks that the initial &#160;&#160; # synchronization phase can [...]]]></description>
			<content:encoded><![CDATA[<p>每次与Java组的同事们坐下来谈技术、谈理想、谈人生时，Java组的同事总会向我们投来羡慕的眼光：卧槽！又是自己开发的工具，太NB了。这时C程序 员们的脸上就会洋溢出自豪的笑容，然后内心骂道：谁让我们没有现成的呢。另一个空间里的某些&ldquo;无C不欢&rdquo;们或者某些&ldquo;C Guru&rdquo;们会骂道：靠，有了也不用，自己写！</p>
<p>有时候，<a href="http://tonybai.com/tag/c">C程序员</a>真的有一种下意识：不情愿使用其他语言开发的工具、框架或服务，且比其他程序员更爱&ldquo;<a href="http://tonybai.com/2012/11/02/treat-reinventing-the-wheel-dialectically">重新发明轮子</a>&rdquo;（有利有弊）。也许这是某种 骨子里的自负在搞怪；另外一个极端：今天和我聊天的一个经验丰富的C程序员还在忧虑：如果离职是否有公司会要他:(。</p>
<p>其实这个时代的C程序员一直活得<b>挺纠结</b>^_^。</p>
<p>这个世界，软硬件发展日新月异，越来越多的后端程序用Java等其他语言实现。Java高级选手在这个世界上也甚是吃香，这个你看看各大招聘网站 就知道了。再听听坊间&ldquo;BAT&rdquo;三巨头给出的高高在上的offer价格，也可以看出Java程序员是多么的有&ldquo;钱途&rdquo;和受欢迎了。当然拿好offer的前提是你的Java底子不薄。</p>
<p>其实无论用什么编程语言，成为牛人后，钱途也都是杠杠的。</p>
<p>没有什么好的开场白，于是有了上面一些&ldquo;胡言乱语&rdquo;。我们言归正传。</p>
<p>本文是一篇初级技术博文。讲的是如何使用<a href="http://zookeeper.apache.org">ZooKeeper</a> C API通过ZooKeeper的服务实现分布式系统的Leader选举。当然这一试验是为了尝试解决我们自己的分布式系统在集中配置数据分发这一环节上的 一个&ldquo;固疾&rdquo;。还好我还不那么纠结，也没有重新实现ZooKeeper的冲动，于是我就用了ZooKeeper这一Java实现的成熟的分布式 系统的服务框架。</p>
<p><b>* 搭建ZooKeeper服务环境</b></p>
<p>&nbsp;&nbsp;&nbsp; &#8211; 下载官方stable release版本 &#8211; ZooKeeper3.4.5。解压后，将$<i>ZooKeeper_INSTALL_PATH</i>/bin加入到PATH变量中（其中ZooKeeper_INSTALL_PATH为解压后ZooKeeper-3.4.5目录的绝对路径）。</p>
<p>&nbsp;&nbsp;&nbsp; &#8211; 试验环境下，最简单的ZooKeeper用法就是使用单机版。<br />
	&nbsp;&nbsp;&nbsp; &nbsp; 进入到$ZooKeeper_INSTALL_PATH/conf下，将zoo_sample.cfg改名为zoo.cfg，即可作为单机版ZooKeeper的配置文件。当然你也可以像我一样随意修改修改：</p>
<p>&nbsp;&nbsp;&nbsp; &nbsp; <font face="Courier New"># The number of milliseconds of each tick<br />
	&nbsp;&nbsp; tickTime=2000<br />
	&nbsp;&nbsp; # The number of ticks that the initial<br />
	&nbsp;&nbsp; # synchronization phase can take<br />
	&nbsp;&nbsp; initLimit=5<br />
	&nbsp;&nbsp; # The number of ticks that can pass between<br />
	&nbsp;&nbsp; # sending a request and getting an acknowledgement<br />
	&nbsp;&nbsp; syncLimit=2</font></p>
<p><font face="Courier New">&nbsp;&nbsp; dataDir=/home/tonybai/proj/myZooKeeper<br />
	&nbsp;&nbsp; # the port at which the clients will connect<br />
	&nbsp;&nbsp; clientPort=2181</font><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果你要体验多机版ZooKeeper服务，那你还要继续动动手脚，以双机版为例，假设有两个ZooKeeper节点(10.0.0.13和10.0.0.14)：</p>
<p>&nbsp;&nbsp;&nbsp; &nbsp; 10.0.0.13上的ZooKeeper节点1的配置文件如下：</p>
<p>&nbsp;&nbsp;&nbsp;<font face="Courier New">&nbsp; # The number of milliseconds of each tick<br />
	&nbsp;&nbsp; tickTime=2000<br />
	&nbsp;&nbsp; # The number of ticks that the initial<br />
	&nbsp;&nbsp; # synchronization phase can take<br />
	&nbsp;&nbsp; initLimit=5<br />
	&nbsp;&nbsp; # The number of ticks that can pass between<br />
	&nbsp;&nbsp; # sending a request and getting an acknowledgement<br />
	&nbsp;&nbsp; syncLimit=2</font></p>
<p><font face="Courier New">&nbsp;&nbsp; dataDir=/home/tonybai/proj/myZooKeeper<br />
	&nbsp;&nbsp; # the port at which the clients will connect<br />
	&nbsp;&nbsp; clientPort=2181</font></p>
<p><font face="Courier New">&nbsp;&nbsp; server.1=10.0.0.13:2888:3888&nbsp;<br />
	&nbsp;&nbsp; server.2=10.0.0.14:2888:3888</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 10.0.0.14上的ZooKeeper节点2的配置文件如下：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New"># The number of milliseconds of each tick<br />
	&nbsp;&nbsp; tickTime=2000<br />
	&nbsp;&nbsp; # The number of ticks that the initial<br />
	&nbsp;&nbsp; # synchronization phase can take<br />
	&nbsp;&nbsp; initLimit=5<br />
	&nbsp;&nbsp; # The number of ticks that can pass between<br />
	&nbsp;&nbsp; # sending a request and getting an acknowledgement<br />
	&nbsp;&nbsp; syncLimit=2</font></p>
<p><font face="Courier New">&nbsp;&nbsp; dataDir=/home/tonybai/proj/myZooKeeper<br />
	&nbsp;&nbsp; # the port at which the clients will connect<br />
	&nbsp;&nbsp; clientPort=2181</font></p>
<p><font face="Courier New">&nbsp;&nbsp; server.1=10.0.0.13:2888:3888<br />
	&nbsp;&nbsp; server.2=10.0.0.14:2888:3888</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 别忘了在每个节点的dataDir下分别创建一个myid文件：<br />
	&nbsp;&nbsp;&nbsp; &nbsp; 在10.0.0.13节点1上执行：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New"> $&gt; echo 1 &gt; myid</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在10.0.0.14节点2上执行：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	<font face="Courier New">&nbsp;&nbsp; $&gt; echo 2 &gt; myid</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 启动ZooKeeper执行：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">$&gt; zkServer.sh start</font></p>
<p>&nbsp;&nbsp;&nbsp; &nbsp; 模拟一个客户端连到ZooKeeper服务上：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">$&gt; zkCli.sh</font></p>
<p>&nbsp;&nbsp;&nbsp; &nbsp; 成功链接后，你将进入一个命令行交互界面：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;<font face="Courier New"> [zk: 10.0.0.13:2181(CONNECTED) 1] help<br />
	&nbsp;&nbsp;&nbsp; ZooKeeper -server host:port cmd args<br />
	&nbsp;&nbsp;&nbsp; connect host:port<br />
	&nbsp;&nbsp;&nbsp; get path [watch]<br />
	&nbsp;&nbsp;&nbsp; ls path [watch]<br />
	&nbsp;&nbsp;&nbsp; set path data [version]<br />
	&nbsp;&nbsp;&nbsp; rmr path<br />
	&nbsp;&nbsp;&nbsp; delquota [-n|-b] path&nbsp; </font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</p>
<p><b>* 选主原理</b></p>
<p>&nbsp;&nbsp; ZooKeeper在选主过程中提供的服务就好比一栋名为&quot;/election&quot;小屋，小屋只有一个门，各节点只能通过这个门逐个进入。每个节点进入后， 都会被分配唯一编号(member-n)，编号n自小到大递增，节点编号最小的自封为Leader，其他节点只能做跟班的（follower) &#8211; 这年头还是小的吃香：原配干不过小三儿，小三儿干不过小四儿，不是么^_^！）。<br />
	&nbsp;&nbsp; 每当一个节点离开，ZooKeeper都会通知屋内的所有节点，屋内节点收到通知后再次判断一下自己是否是屋内剩余节点中编号最小的节点，如果是，则自封为Leader，否则为Follower。</p>
<p>&nbsp;&nbsp; 再用稍正式的语言重述一遍：</p>
<p>&nbsp;&nbsp; 各个子节点同时在某个ZooKeeper数据路径/election下建立&quot;ZOO_SEQUENCE|ZOO_EPHEMERAL&quot;节点 &#8211; member，且各个节点监视(Watch) /election路径的子路径的变更事件。ZooKeeper的sequence节点特性保证节点创建时会被从小到大加上编号。同时节点的 ephemeral特性保证一旦子节点宕机或异常停掉，其对应的member节点会被ZooKeeper自动删除，而其他节点会收到该变更通知，重新判定 自己是leader还是follower以及谁才是真正的leader。</p>
<p><b>* 示例代码</b></p>
<p>关于ZooKeeper的C API的使用资料甚少，但这里就偏偏要用C API举例。</p>
<p>C API的安装方法：进入$ZOOKEEPER_INSTALL_PATH/src/c下面，configure-&gt;make-&gt;make install即可。</p>
<p>ZooKeeper的C API分为同步与异步两种模式，这里简单起见用的都是同步机制。代码不多，索性全贴出来。在<a href="https://github.com/bigwhite/experiments">这里</a>能checkout到全部代码。</p>
<p><font face="Courier New">/* election.c */</font><br />
	<font face="Courier New">#include &lt;stdio.h&gt;<br />
	#include &lt;stdlib.h&gt;<br />
	#include &lt;string.h&gt;<br />
	#include &lt;unistd.h&gt;<br />
	#include &quot;zookeeper.h&quot;</font></p>
<p><font face="Courier New">static int<br />
	is_leader(zhandle_t* zkhandle, char *myid);</font></p>
<p><font face="Courier New">static void<br />
	get_node_name(const char *buf, char *node);</font></p>
<p><font face="Courier New">struct watch_func_para_t {<br />
	&nbsp;&nbsp;&nbsp; zhandle_t *zkhandle;<br />
	&nbsp;&nbsp;&nbsp; char node[64];<br />
	};</font></p>
<p><font face="Courier New">void<br />
	election_children_watcher(zhandle_t* zh, int type, int state,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char* path, void* watcherCtx)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; int ret = 0;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; struct watch_func_para_t* para= (struct watch_func_para_t*)watcherCtx;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; struct String_vector strings;<br />
	&nbsp;&nbsp;&nbsp; struct Stat stat;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* 重新监听 */<br />
	&nbsp;&nbsp;&nbsp; ret = zoo_wget_children2(para-&gt;zkhandle, &quot;/election&quot;, election_children_watcher,<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;&nbsp;&nbsp;&nbsp;&nbsp; watcherCtx, &amp;strings, &amp;stat);<br />
	&nbsp;&nbsp;&nbsp; if (ret) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, &quot;child: zoo_wget_children2 error [%d]\n&quot;, ret);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(EXIT_FAILURE);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* 判断主从 */<br />
	&nbsp;&nbsp;&nbsp; if (is_leader(para-&gt;zkhandle, para-&gt;node))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;This is [%s], i am a leader\n&quot;, para-&gt;node);<br />
	&nbsp;&nbsp;&nbsp; else<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;This is [%s], i am a follower\n&quot;, para-&gt;node);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return;<br />
	}</font></p>
<p><font face="Courier New">void def_election_watcher(zhandle_t* zh, int type, int state,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char* path, void* watcherCtx)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;Something happened.\n&quot;);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;type: %d\n&quot;, type);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;state: %d\n&quot;, state);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;path: %s\n&quot;, path);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;watcherCtx: %s\n&quot;, (char *)watcherCtx);<br />
	}</font></p>
<p><font face="Courier New">int<br />
	main(int argc, const char *argv[])<br />
	{</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; const char* host = &quot;10.0.0.13:2181&quot;;<br />
	&nbsp;&nbsp;&nbsp; zhandle_t* zkhandle;<br />
	&nbsp;&nbsp;&nbsp; int timeout = 5000;<br />
	&nbsp;&nbsp;&nbsp; char buf[512] = {0};<br />
	&nbsp;&nbsp;&nbsp; char node[512] = {0};</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; zoo_set_debug_level(ZOO_LOG_LEVEL_WARN);<br />
	&nbsp;&nbsp;&nbsp; zkhandle = zookeeper_init(host, def_election_watcher, timeout,<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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0, &quot;Zookeeper examples: election&quot;, 0);<br />
	&nbsp;&nbsp;&nbsp; if (zkhandle == NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, &quot;Connecting to zookeeper servers error&#8230;\n&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(EXIT_FAILURE);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* 在/election下创建member节点 */<br />
	&nbsp;&nbsp;&nbsp; int ret = zoo_create(zkhandle,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/election/member&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; &quot;hello&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; 5,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;ZOO_OPEN_ACL_UNSAFE,&nbsp; /* a completely open ACL */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZOO_SEQUENCE|ZOO_EPHEMERAL,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buf,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(buf)-1);<br />
	&nbsp;&nbsp;&nbsp; if (ret) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, &quot;zoo_create error [%d]\n&quot;, ret);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(EXIT_FAILURE);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; get_node_name(buf, node);<br />
	&nbsp;&nbsp;&nbsp; /* 判断当前是否是Leader节点 */<br />
	&nbsp;&nbsp;&nbsp; if (is_leader(zkhandle, node)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;This is [%s], i am a leader\n&quot;, node);<br />
	&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;This is [%s], i am a follower\n&quot;, node);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; struct Stat stat;<br />
	&nbsp;&nbsp;&nbsp; struct String_vector strings;<br />
	&nbsp;&nbsp;&nbsp; struct watch_func_para_t para;<br />
	&nbsp;&nbsp;&nbsp; memset(&amp;para, 0, sizeof(para));<br />
	&nbsp;&nbsp;&nbsp; para.zkhandle = zkhandle;<br />
	&nbsp;&nbsp;&nbsp; strcpy(para.node, node);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* 监视/election的所有子节点事件 */<br />
	&nbsp;&nbsp;&nbsp; ret = zoo_wget_children2(zkhandle, &quot;/election&quot;, election_children_watcher, &amp;para, &amp;strings, &amp;stat);<br />
	&nbsp;&nbsp;&nbsp; if (ret) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, &quot;zoo_wget_children2 error [%d]\n&quot;, ret);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(EXIT_FAILURE);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* just wait for experiments*/<br />
	&nbsp;&nbsp;&nbsp; sleep(10000);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; zookeeper_close(zkhandle);<br />
	}</font></p>
<p><font face="Courier New">static int<br />
	is_leader( zhandle_t* zkhandle, char *myid)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; int ret = 0;<br />
	&nbsp;&nbsp;&nbsp; int flag = 1;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; struct String_vector strings;<br />
	&nbsp;&nbsp;&nbsp; ret = zoo_get_children(zkhandle, &quot;/election&quot;, 0, &amp;strings);<br />
	&nbsp;&nbsp;&nbsp; if (ret) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, &quot;Error %d for %s\n&quot;, ret, &quot;get_children&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(EXIT_FAILURE);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* 计数 */<br />
	&nbsp;&nbsp;&nbsp; for (int i = 0;&nbsp; i &lt; strings.count; i++) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (strcmp(myid, strings.data[i]) &gt; 0) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flag = 0;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return flag;<br />
	}</font></p>
<p><font face="Courier New">static void<br />
	get_node_name(const char *buf, char *node)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; const char *p = buf;<br />
	&nbsp;&nbsp;&nbsp; int i;<br />
	&nbsp;&nbsp;&nbsp; for (i = strlen(buf) &#8211; 1; i &gt;= 0; i&#8211;) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (*(p + i) == &#39;/&#39;) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; strcpy(node, p + i + 1);<br />
	&nbsp;&nbsp;&nbsp; return;<br />
	}</font></p>
<p>编译这个代码：<br />
	<font face="Courier New">$&gt; gcc -g -std=gnu99 -o election election.c -DTHREADED -I/usr/local/include/zookeeper -lzookeeper_mt -lpthread</font></p>
<p>验证时，我们在不同窗口启动三次election程序：</p>
<p>窗口1， election启动：</p>
<p><font face="Courier New">$&gt; election<br />
	Something happened.<br />
	type: -1<br />
	state: 3<br />
	path:<br />
	watcherCtx: Zookeeper examples: election<br />
	This is [member0000000001], i am a leader</font></p>
<p>窗口2，election启动：</p>
<p><font face="Courier New">$&gt; election<br />
	Something happened.<br />
	type: -1<br />
	state: 3<br />
	path:<br />
	watcherCtx: Zookeeper examples: election<br />
	This is [member0000000002], i am a follower</font></p>
<p>此时窗口1中的election也会收到/election的字节点增加事件，并给出响应：</p>
<p><font face="Courier New">This is [member0000000001], i am a leader</font></p>
<p>同理当窗口3中的election启动时，窗口1和2中的election都能收到变动通知，并给予响应。</p>
<p>我们现在停掉窗口1中的election，大约5s后，我们在窗口2中看到：</p>
<p><font face="Courier New">This is [member0000000002], i am a leader</font></p>
<p>在窗口3中看到：</p>
<p><font face="Courier New">This is [member0000000003], i am a follower</font></p>
<p>可以看出窗口2和3中的election程序又做了一次自我选举。结果窗口2中的election由于节点编号最小而被选为Leader。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/08/23/leader-election-using-zookeeper/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>也谈代码行统计</title>
		<link>https://tonybai.com/2013/07/24/thoughts-about-lines-of-code-statistics/</link>
		<comments>https://tonybai.com/2013/07/24/thoughts-about-lines-of-code-statistics/#comments</comments>
		<pubDate>Wed, 24 Jul 2013 10:12:50 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Programmer]]></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=1356</guid>
		<description><![CDATA[一直在纠结要不要就这个话题写点什么，之前梳理过一些思路，但感觉这个题目似乎没什么大意义。不过将东西憋在肚子里的滋味总是不好受的，最终我还是选择写出来一些，即便它真的没有什么意义^_^。 事情缘于近期领导让我负责的一个内部任务：制定组织内的代码行统计标准并实现标准化的工具。就是这个任务促使了我对代码行统计重新做了一番考量。 对代码行统计的理解 代码行统计这个活动不是软件开发过程中的关键路径活动，它对代码质量、开发进度以及软件价格几乎产生不了什么影响，应该算是个可有可无的东西。 就代码行统计这个活动本身而言，我个人的观点是没有代码行统计不表明不能开发出好软件；有了代码行统计，就一定能开发出高质量软件吗？ 不过有一种观点认为：世界的本质是数据。通过数据我们可以发现事物运行的规律。代码行统计则是软件工程中对&#8220;数据&#8221;要求的产物。过程的好坏需要有数据支 撑，因此代码行统计这个活动成为了人们实现&#8220;用数据说话&#8221;的一柄利器。在&#8220;数据为王&#8221;的今天，我们无论如何都不能忽视数据的作用。人们通过数据来反映软件 开发过程中的一些规律性的东西本身也没有什么不妥。另外代码是软件开发过程的最重要成果物，因此围绕着代码的性态，我们用工具做诸多分析，期望从得到的数 据中找寻出一些可以指导和改善我们后续工作的蛛丝马迹。代码行统计提供的多是基础数据，在与其他过程基础数据结合分析后，我们能得到更多的信息。 合理地使用场合 个人觉得下面几个场合对代码行统计的需求是合理的： * 统计代码总规模 &#160;&#160; 某个项目、某个模块或又某个版本的代码总规模。 * 代码&#8220;成分&#8221;统计 &#160;&#160; 统计空行、注释、代码的行数及占比、重复代码行数及占比等。 * 版本间代码变更差异统计 &#160;&#160; 两个有关联版本的数据对比统计，获取版本间的有效变更数据情况并作为基础数据提供给后续分析。 一些过程质量指标，诸如千行代码缺陷率等均是以上面这些代码行统计输出的基础数据为基础的。 &#8220;误用&#8221; 有合理的使用，就有&#8220;不合理&#8221;的使用 &#8211; &#8220;误用&#8221;。之所以加上引号，是因为至今人们对此见仁见智，尚无定论。以下列举两典型的&#8220;误用&#8221;。 * 通过代码行统计评估进度 有些组织在项目开始初期，就对成果的规模做了估计，比如10w行代码。然后在过程中使用代码统计工具对项目当前已实现的规模进行统计，并用统计出的数据与 初值的比值作为项目进度的评估参考。个人认为这是种典型的误用。盖茨说过：&#8220;用代码行数来衡量编程的进度，就如同用航空器零件的重量来衡量航空飞机的制造 进度一样&#8221;。且不提初期的估值有多么的不准确，就代码的行数本身而言，也受到各种因素的影响，比如设计方案、实现者的功力以及编码习惯等。同一个功能，A 实现需要100行代码；换成B就需要10行。 * 通过代码行统计评估程序员绩效 在一些外包公司或外包项目里，尤其是日本人的外包项目里，通过编写代码行的多少来评估程序员绩效的作法是很有市场的。我不能完全否定这种方法的正确性，因 为在日本外包项目中变态的日本人对代码的审核极其严格，并且有着苛刻的编码标准和风格，因此一些胡乱堆砌代码或使用奇技淫巧的代码都会被驳回，因此所有项 目开发者的效率似乎被约束到了一个平均线上。在这个前提下，产出的代码越多，似乎的确表明了这个开发者超出了平均效率，或至少牺牲了不少个人时间来完成项 目中的任务，精神可嘉，绩效被评高似乎也是合情合理的。但除此之外，用代码行多寡来评估程序员绩效显然是不受待见的。 考虑这个&#8220;误用&#8221;时，我也想模仿盖茨的话做个形象且深刻比喻，最初我写下的是这句话：&#8220;用代码行数多少来评估程序员的绩效，就好比用曲子的长短来评估音乐 家的水平，或又好比用画幅的大小来衡量画家的水准，或又好比用电影的时长 来掂量导演的功力！&#8221;。但仔细揣摩后发现这句话看起来挺像那么回事，但实际上却是不恰当的。什么是水准、水平或功力，这是衡量人的水平高低的；而绩效则是 一段时间范畴内工作成果的评估； 一个是长期的肯定，一个是阶段性的成绩。我显然是将水平和绩效(阶段性成绩)混为一谈了。高水平的开发者不一定每个周期都会取得高绩效，低水平的开发者也 不是无法取得高绩效的。因此这句话似乎应该改成：&#8220;用代码行数多少来评估程序员的绩效，就好比用这首曲子的长短来评估音乐家在这个阶段的水平，或又好比用 画幅的大小来衡量画家的这个阶段水准，或又 好比用电影的时长来掂量导演在这部电影上的功力！&#8221;。是不是读起来很别扭啊，反正我是这么觉得的。程序员的成果物是代码，代码好坏优劣对程序员绩效有着直 接影响（虽非充分必要条件），我们不妨替换一下本体来换种说法：&#8220;用代码行数多少来评估代码实现的好坏，就好比用曲子的长短来评估曲子的优劣，或又好比用 画幅的大小来衡量画作的高低，或又好比用电影的时长来掂量影片的良莠&#8221;！ 对用代码行数多少来评估程序员绩效这种事情，我是很反感的，但在国内许多公司里，这种现象却又屡见不鲜。但这种行为背后的动机何在呢？传统工厂中，衡量一 个worker的绩效是相对容易量化，也比较客观的，比如制鞋厂可以用制成鞋子的数量来确定 worker绩效；在汽车组装车间，组装汽车的数量可以作为作为工人们的绩效；在炼钢厂，班组炼出的钢铁的吨数可作为班组成员绩效等等。将代码行数作为程 序员绩效的参考指标也许是一个无奈的方法。之所以想用代码行数，是因为程序员工作中能量化的东西不多，代码行数首当其冲。组织为了尽量减少绩效评定时主观 的成分，增加客观的评价，代码行统计从此被误用了。 [...]]]></description>
			<content:encoded><![CDATA[<p>一直在纠结要不要就这个话题写点什么，之前梳理过一些思路，但感觉这个题目似乎没什么大意义。不过将东西憋在肚子里的滋味总是不好受的，最终我还是选择写出来一些，即便它真的没有什么意义^_^。</p>
<p>事情缘于近期领导让我负责的一个内部任务：制定组织内的代码行统计标准并实现标准化的工具。就是这个任务促使了我对<a href="http://tonybai.com/2010/12/24/an-effectual-method-based-on-svn-diff-for-code-quantity-statistics/">代码行统计</a>重新做了一番考量。</p>
<p><b>对代码行统计的理解</b></p>
<p>代码行统计这个活动不是软件开发过程中的关键路径活动，它对代码质量、开发进度以及软件价格几乎产生不了什么影响，应该算是个可有可无的东西。</p>
<p>就代码行统计这个活动本身而言，我个人的观点是没有代码行统计不表明不能开发出好软件；有了代码行统计，就一定能开发出高质量软件吗？</p>
<p>不过有一种观点认为：世界的本质是数据。通过数据我们可以发现事物运行的规律。代码行统计则是软件工程中对&ldquo;数据&rdquo;要求的产物。过程的好坏需要有数据支 撑，因此代码行统计这个活动成为了人们实现&ldquo;用数据说话&rdquo;的一柄利器。在&ldquo;数据为王&rdquo;的今天，我们无论如何都不能忽视数据的作用。人们通过数据来反映软件 开发过程中的一些规律性的东西本身也没有什么不妥。另外代码是软件开发过程的最重要成果物，因此围绕着代码的性态，我们用工具做诸多分析，期望从得到的数 据中找寻出一些可以指导和改善我们后续工作的蛛丝马迹。代码行统计提供的多是基础数据，在与其他过程基础数据结合分析后，我们能得到更多的信息。</p>
<p><b>合理地使用场合</b></p>
<p>个人觉得下面几个场合对代码行统计的需求是合理的：</p>
<p>* 统计代码总规模<br />
	&nbsp;&nbsp; 某个项目、某个模块或又某个版本的代码总规模。</p>
<p>* 代码&ldquo;成分&rdquo;统计<br />
	&nbsp;&nbsp; 统计空行、注释、代码的行数及占比、重复代码行数及占比等。</p>
<p>* 版本间代码变更差异统计<br />
	&nbsp;&nbsp; 两个有关联版本的数据对比统计，获取版本间的有效变更数据情况并作为基础数据提供给后续分析。</p>
<p>一些过程质量指标，诸如千行代码缺陷率等均是以上面这些代码行统计输出的基础数据为基础的。</p>
<p><b>&ldquo;误用&rdquo;</b></p>
<p>有合理的使用，就有&ldquo;不合理&rdquo;的使用 &#8211; &ldquo;误用&rdquo;。之所以加上引号，是因为至今人们对此见仁见智，尚无定论。以下列举两典型的&ldquo;误用&rdquo;。</p>
<p>* 通过代码行统计评估进度</p>
<p>有些组织在项目开始初期，就对成果的规模做了估计，比如10w行代码。然后在过程中使用代码统计工具对项目当前已实现的规模进行统计，并用统计出的数据与 初值的比值作为项目进度的评估参考。个人认为这是种典型的误用。盖茨说过：&ldquo;用代码行数来衡量编程的进度，就如同用航空器零件的重量来衡量航空飞机的制造 进度一样&rdquo;。且不提初期的估值有多么的不准确，就代码的行数本身而言，也受到各种因素的影响，比如设计方案、实现者的功力以及编码习惯等。同一个功能，A 实现需要100行代码；换成B就需要10行。</p>
<p>* 通过代码行统计评估程序员绩效</p>
<p>在一些外包公司或外包项目里，尤其是日本人的外包项目里，通过编写代码行的多少来评估程序员绩效的作法是很有市场的。我不能完全否定这种方法的正确性，因 为在日本外包项目中变态的日本人对代码的审核极其严格，并且有着苛刻的编码标准和风格，因此一些胡乱堆砌代码或使用奇技淫巧的代码都会被驳回，因此所有项 目开发者的效率似乎被约束到了一个平均线上。在这个前提下，产出的代码越多，似乎的确表明了这个开发者超出了平均效率，或至少牺牲了不少个人时间来完成项 目中的任务，精神可嘉，绩效被评高似乎也是合情合理的。但除此之外，用代码行多寡来评估程序员绩效显然是不受待见的。</p>
<p>考虑这个&ldquo;误用&rdquo;时，我也想模仿盖茨的话做个形象且深刻比喻，最初我写下的是这句话：&ldquo;用代码行数多少来评估程序员的绩效，就好比用曲子的长短来评估音乐 家的水平，或又好比用画幅的大小来衡量画家的水准，或又好比用电影的时长 来掂量导演的功力！&rdquo;。但仔细揣摩后发现这句话看起来挺像那么回事，但实际上却是不恰当的。什么是水准、水平或功力，这是衡量人的水平高低的；而绩效则是 一段时间范畴内工作成果的评估； 一个是长期的肯定，一个是阶段性的成绩。我显然是将水平和绩效(阶段性成绩)混为一谈了。高水平的开发者不一定每个周期都会取得高绩效，低水平的开发者也 不是无法取得高绩效的。因此这句话似乎应该改成：&ldquo;用代码行数多少来评估程序员的绩效，就好比用这首曲子的长短来评估音乐家在这个阶段的水平，或又好比用 画幅的大小来衡量画家的这个阶段水准，或又 好比用电影的时长来掂量导演在这部电影上的功力！&rdquo;。是不是读起来很别扭啊，反正我是这么觉得的。程序员的成果物是代码，代码好坏优劣对程序员绩效有着直 接影响（虽非充分必要条件），我们不妨替换一下本体来换种说法：&ldquo;用代码行数多少来评估代码实现的好坏，就好比用曲子的长短来评估曲子的优劣，或又好比用 画幅的大小来衡量画作的高低，或又好比用电影的时长来掂量影片的良莠&rdquo;！</p>
<p>对用代码行数多少来评估程序员绩效这种事情，我是很反感的，但在国内许多公司里，这种现象却又屡见不鲜。但这种行为背后的动机何在呢？传统工厂中，衡量一 个worker的绩效是相对容易量化，也比较客观的，比如制鞋厂可以用制成鞋子的数量来确定 worker绩效；在汽车组装车间，组装汽车的数量可以作为作为工人们的绩效；在炼钢厂，班组炼出的钢铁的吨数可作为班组成员绩效等等。将代码行数作为程 序员绩效的参考指标也许是一个无奈的方法。之所以想用代码行数，是因为程序员工作中能量化的东西不多，代码行数首当其冲。组织为了尽量减少绩效评定时主观 的成分，增加客观的评价，代码行统计从此被误用了。</p>
<p><b>代码行统计的高效使用</b></p>
<p>* 标准统一，工具一致</p>
<p>代码行统计工具有很多，因此执行这个活动时会出现不同人使用的代码行统计工具不一致的情况；并且不同工具对一些指标的定义也许有不同，这会导致收集到的数据存在含义不一致，精确度差的问题。因此高效使用代码行统计工具的一个前提就是（统计）标准统一，工具一致。</p>
<p>* 零干扰</p>
<p>一些传统的代码行统计方法是配置负责人收到统计任务时，将任务分发给各个模块的负责人，由各个模块负责人各自统计，然后反馈给配置负责人汇总。这种方式显 然不那么高效，而且容易引起一些对统计任务的反感情绪。高效的代码行统计最好能做到对开发人员&ldquo;零干扰&rdquo;。配置负责人可以通过&ldquo;自动化&rdquo;的静默方式收集代 码行数据。当然这需要对一些现成的开源工具做一些包装或二次开发才能做到，个人觉得这种投入是值得的，同时也能避免标准不一，工具不一致的情况。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/07/24/thoughts-about-lines-of-code-statistics/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Python脚本命令行变量的实现</title>
		<link>https://tonybai.com/2013/07/09/an-implementation-of-python-commandline-variables/</link>
		<comments>https://tonybai.com/2013/07/09/an-implementation-of-python-commandline-variables/#comments</comments>
		<pubDate>Tue, 09 Jul 2013 07:33:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[eval]]></category>
		<category><![CDATA[exec]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[命令行变量]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1297</guid>
		<description><![CDATA[我们知道Make工具是支持命令行变量的，这种手段为我们提供了很好的灵活性，我们可以通过敲入不同的命令行参数来决定Makefile脚本的行为。 make [variable1=value1 variable2=value2 ... ... ]。 # # Makefile # CMODE = 64-bit ifeq ($(CMODE), 64-bit) &#160;&#160;&#160; CFLAGS += -m64 endif &#160;&#160; all: &#160;&#160;&#160; gcc $(CFLAGS) -o foo foo.c $&#62; make gcc -m64 -o foo foo.c $&#62; make CMODE=32-bit gcc -o foo foo.c 近期我们的一个Python脚本工具也有类似的需求了，但Python脚本原生并不支持这种命令行变量，我们来看看是否可以利用Python提供的机制实现一种可以满足我们需求的命令行变量。 我们的期望结果如下： $&#62; foo.py fruit=apple # foo.py flag = &#39;&#39; #这个定义可以有，也可以没有，如果有，可以理解为默认值 [...]]]></description>
			<content:encoded><![CDATA[<p>我们知道Make工具是支持<a href="http://tonybai.com/2011/05/19/use-command-line-vars-of-make/">命令行变量</a>的，这种手段为我们提供了很好的灵活性，我们可以通过敲入不同的命令行参数来决定Makefile脚本的行为。</p>
<p><font face="Courier New">make [variable1=value1 variable2=value2 ... ... ]。</font></p>
<p><font face="Courier New">#<br />
	# Makefile<br />
	#</font></p>
<p><font face="Courier New">CMODE = 64-bit</font></p>
<p><font face="Courier New">ifeq ($(CMODE), 64-bit)<br />
	&nbsp;&nbsp;&nbsp; CFLAGS += -m64<br />
	endif<br />
	&nbsp;&nbsp;<br />
	all:<br />
	&nbsp;&nbsp;&nbsp; gcc $(CFLAGS) -o foo foo.c</font></p>
<p><font face="Courier New">$&gt; make<br />
	gcc -m64 -o foo foo.c</font></p>
<p><font face="Courier New">$&gt; make CMODE=32-bit<br />
	gcc -o foo foo.c</font></p>
<p>近期我们的一个Python脚本工具也有类似的需求了，但Python脚本原生并不支持这种命令行变量，我们来看看是否可以利用Python提供的机制实现一种可以满足我们需求的命令行变量。</p>
<p>我们的期望结果如下：</p>
<p><font face="Courier New">$&gt; foo.py fruit=apple</font></p>
<p><font face="Courier New"># foo.py</font></p>
<p><font face="Courier New">flag = &#39;&#39; #这个定义可以有，也可以没有，如果有，可以理解为默认值</font></p>
<p><font face="Courier New">&#8230;.</font></p>
<p><font face="Courier New">if flag == &#39;apple&#39;:<br />
	&nbsp;&nbsp;&nbsp; &#8230;.<br />
	elif flag == &#39;orange&#39;:<br />
	&nbsp;&nbsp;&nbsp; &#8230;.<br />
	elif flag == &#39;banana&#39;:<br />
	&nbsp;&nbsp;&nbsp; &#8230;.<br />
	else:<br />
	&nbsp;&nbsp;&nbsp; &#8230;.</font></p>
<p>Python是动态语言，提供了注入eval、exec等在运行时执行代码的能力。我们要实现命令行变量的机制，离不开这些能力的支持。eval用于求值表达式，而x=y是语句，我们只能用exec。</p>
<p>【#1】</p>
<p><font face="Courier New">import sys</font></p>
<p><font face="Courier New">if __name__ == &#39;__main__&#39;:<br />
	&nbsp;&nbsp;&nbsp; c = len(sys.argv)<br />
	&nbsp;&nbsp;&nbsp; if c &lt;= 1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &quot;found zero command variable&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(0)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; exec(sys.argv[1])</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if fruit == &#39;apple&#39;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;this is apple&#39;<br />
	&nbsp;&nbsp;&nbsp; elif fruit == &#39;oracle&#39;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;this is orange&#39;<br />
	&nbsp;&nbsp;&nbsp; elif fruit == &#39;banana&#39;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;this is banana&#39;<br />
	&nbsp;&nbsp;&nbsp; else:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;other fruit&#39;</font></p>
<p><font face="Courier New">$&gt; foo.py fruit=apple</font></p>
<p><font face="Courier New">Traceback (most recent call last):<br />
	&nbsp; File &quot;./foo.py&quot;, line 18, in &lt;module&gt;<br />
	&nbsp;&nbsp;&nbsp; exec(sys.argv[1])<br />
	&nbsp; File &quot;&lt;string&gt;&quot;, line 1, in &lt;module&gt;<br />
	NameError: name &#39;apple&#39; is not defined</font></p>
<p>上面的例子执行后，提示&#39;apple&#39;没有定义。执行foo.py fruit=&quot;apple&quot;得到的也是同样的错误。从内部输出来看，无论是fruit=apple还是fruit=&quot;apple&quot;，exec的参数始终都 是fruit=apple，导致exec抱怨apple这个符号没有定义。</p>
<p>我们打开一个Python命令行交互窗口，做如下测试：</p>
<p><font face="Courier New">$&gt; python<br />
	Python 2.7.3 (default, Aug&nbsp; 1 2012, 05:14:39)<br />
	[GCC 4.6.3] on linux2<br />
	Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.<br />
	&gt;&gt;&gt; exec(&quot;fruit=apple&quot;)<br />
	Traceback (most recent call last):<br />
	&nbsp; File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;<br />
	&nbsp; File &quot;&lt;string&gt;&quot;, line 1, in &lt;module&gt;<br />
	NameError: name &#39;apple&#39; is not defined<br />
	&gt;&gt;&gt; exec(&quot;fruit=&#39;apple&#39;&quot;)<br />
	&gt;&gt;&gt; fruit<br />
	&#39;apple&#39;<br />
	&gt;&gt;&gt; exec(&quot;num=1&quot;)<br />
	&gt;&gt;&gt; num<br />
	1</font></p>
<p>通过这个小实验可以看出，我们不能将命令行参数直接原封不动的传给exec，我们要对其进行一下加工，加工的效果如下：</p>
<p><font face="Courier New">fruit=apple =&gt; fruit=&#39;apple&#39;<br />
	num=1 =&gt; num=1</font></p>
<p>【2】</p>
<p><font face="Courier New">import sys</font></p>
<p><font face="Courier New">def __convert(source):<br />
	&nbsp;&nbsp;&nbsp; (var, sep, val) = source.partition(&quot;=&quot;)<br />
	&nbsp;&nbsp;&nbsp; if val.isdigit():<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return source<br />
	&nbsp;&nbsp;&nbsp; return&nbsp; var + &quot;=&quot; + &quot;\&#39;&quot; + val + &quot;\&#39;&quot;</font></p>
<p><font face="Courier New">if __name__ == &#39;__main__&#39;:<br />
	&nbsp;&nbsp;&nbsp; c = len(sys.argv)<br />
	&nbsp;&nbsp;&nbsp; if c &lt;= 1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &quot;found zero command variable&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(0)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; exec( __convert(sys.argv[1]))</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if fruit == &#39;apple&#39;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;this is apple&#39;<br />
	&nbsp;&nbsp;&nbsp; elif fruit == &#39;orange&#39;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;this is orange&#39;<br />
	&nbsp;&nbsp;&nbsp; elif fruit == &#39;banana&#39;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;this is banana&#39;<br />
	&nbsp;&nbsp;&nbsp; else:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &#39;other fruit&#39;</font></p>
<p>__convert函数对命令行的参数做了转换，对于数值类的var直接原封不动的返回，否则对于值为字符串的var，将其val用&#39;&#39;包裹起来后返回。我们来测试一下新程序：</p>
<p><font face="Courier New">$&gt; foo.py fruit=apple<br />
	this is apple<br />
	$&gt; foo.py fruit=orange<br />
	this is orange<br />
	$&gt; foo.py fruit=watermelon<br />
	other fruit</font></p>
<p>从输出结果来看，我们的预期是达到了^_^。上面的程序只是示例性质的，Python的exec具有运行时执行动态代码的能力，我们在获得这种强大能力的 同时，也面临着巨大的风险。一旦恶意代码从外部传入被exec执行，将带来严重的后果。因此对于exec要执行的代码务必要预先进行必要的形式校验。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/07/09/an-implementation-of-python-commandline-variables/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>代码评审，由人治过渡到“法治”</title>
		<link>https://tonybai.com/2013/07/08/code-review-from-rule-of-man-to-rule-of-law/</link>
		<comments>https://tonybai.com/2013/07/08/code-review-from-rule-of-man-to-rule-of-law/#comments</comments>
		<pubDate>Mon, 08 Jul 2013 08:23:27 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[APR]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Code-Review]]></category>
		<category><![CDATA[pre-commit-hook]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[ReviewBoard]]></category>
		<category><![CDATA[Subversion]]></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=1294</guid>
		<description><![CDATA[事实证明：有效的代码评审(Code Review，也有叫代码审查的），对保证代码质量具有十分重要的作用。因此这两年来我一直尝试着在这块不断改进和完善，以期望能形成一套合理、规范、有 效且高效的代码评审流程，这包括引入在线代码评审系统、走查和在线评审结合、规范评审Request的规模与有效性、设立评审专员等，用心不可谓不良苦 ^_^。大家也的确形成了及时提交Code Review Request或组织进行代码走查的良好习惯。不过我还是发现了一些问题。 * 有些组（我对其影响力不足的^_^）依旧没有严格执行代码评审环节，代码屡屡出现低级错误； * 走查形式的会议评审缺乏全面性，效果好坏与参与者的&#8220;状态&#8221;直接相关； * 在线评审环节缺乏&#8220;责任制&#8221;，常出现的一种情况是：请求大家评审，结果可能却是大家都没有评审。出现&#34;Request Review Miss&#34;的现象。 这让我陷入思考：长期以来我们在代码评审这块过于依赖人的自觉性，理想地认为每个人都能认识到代码评审的重要性，并认真地执行代码评审的流程或充满激情地 参与到其他人发起的代码评审过程中去，但结果事与愿违。这就像党员如何保持纯洁性一样，如果仅仅依靠个人道德/职业水平约束，这事往往是不成的。事实证明 人治在中国社会是会造成各种社会问题的。我们的代码评审环节也是一样，我们不能再期望所有人都能和我站在一条认知和激情水平线上，于是我打算尝试向&#8220;法 治&#8221;过渡。 &#34;法&#34;，规则制度也，是团队一致认同的可以提升产品质量的规则制度。以此为前提，我要做的就是设立&#8220;检查和预防&#8221;机构，即以很低的Cost，检查大家是否按&#8220;法&#8221;完成了代码评审环节，提醒大家要按&#8220;法&#8221;进行。我采取了几个措施： 【规范Commit Log 】 这是一个前提工作。实现规范的Commit Log便于后续的检查和监督，同时细化规范的Commit Log信息对代码维护是大有裨益的。在Commit Log中还增加了一些关联信息，方便维护者了解该Commit的背景。初期的模板是这么来确定的： 模板结构： TITLE BODY RELATIONSHIPS 展开后如下： [Category] Title content Body content [BUGID] QC#733 &#124; JIRA#766 [REVIEWID] RB#767 [REVIEWED BY] xx, yy, zz [SIGNOFF BY] xx TITLE Category： &#160;&#160; &#8211; BUGFIX [...]]]></description>
			<content:encoded><![CDATA[<p>事实证明：有效的<a href="http://tonybai.com/2011/02/22/code-reviews/">代码评审</a>(Code Review，也有叫代码审查的），对保证代码质量具有十分重要的作用。因此这两年来我一直尝试着在这块不断改进和完善，以期望能形成一套合理、规范、有 效且高效的代码评审流程，这包括引入<a href="http://tonybai.com/2010/12/18/thoughts-on-online-coding-review/">在线代码评审系统</a>、走查和在线评审结合、规范评审Request的规模与有效性、设立评审专员等，用心不可谓不良苦 ^_^。大家也的确形成了及时提交Code Review Request或组织进行代码走查的良好习惯。不过我还是发现了一些问题。</p>
<p>* 有些组（我对其影响力不足的^_^）依旧没有严格执行代码评审环节，代码屡屡出现低级错误；<br />
	* 走查形式的会议评审缺乏全面性，效果好坏与参与者的&ldquo;状态&rdquo;直接相关；<br />
	* 在线评审环节缺乏&ldquo;责任制&rdquo;，常出现的一种情况是：请求大家评审，结果可能却是大家都没有评审。出现&quot;Request Review Miss&quot;的现象。</p>
<p>这让我陷入思考：长期以来我们在代码评审这块过于依赖人的自觉性，理想地认为每个人都能认识到代码评审的重要性，并认真地执行代码评审的流程或充满激情地 参与到其他人发起的代码评审过程中去，但结果事与愿违。这就像党员如何保持纯洁性一样，如果仅仅依靠个人道德/职业水平约束，这事往往是不成的。事实证明 人治在中国社会是会造成各种社会问题的。我们的代码评审环节也是一样，我们不能再期望所有人都能和我站在一条认知和激情水平线上，于是我打算尝试向&ldquo;法 治&rdquo;过渡。</p>
<p>&quot;法&quot;，规则制度也，是团队一致认同的可以提升产品质量的规则制度。以此为前提，我要做的就是设立&ldquo;检查和预防&rdquo;机构，即以很低的Cost，检查大家是否按&ldquo;法&rdquo;完成了代码评审环节，提醒大家要按&ldquo;法&rdquo;进行。我采取了几个措施：</p>
<p><b>【规范Commit Log </b><b>】</b></p>
<p>这是一个前提工作。实现规范的<a href="http://tonybai.com/2013/05/09/also-talk-about-commit-log/">Commit Log</a>便于后续的检查和监督，同时细化规范的Commit Log信息对代码维护是大有裨益的。在Commit Log中还增加了一些关联信息，方便维护者了解该Commit的背景。初期的模板是这么来确定的：</p>
<p>模板结构：</p>
<p><font face="Courier New"><i>TITLE</i><br />
	<i>BODY</i><br />
	<i>RELATIONSHIPS</i></font></p>
<p>展开后如下：</p>
<p><font face="Courier New"><i>[Category] Title content</i></font></p>
<p><font face="Courier New"><i>Body content</i></font></p>
<p><font face="Courier New"><i>[BUGID] QC#733 | JIRA#766</i><br />
	<i>[REVIEWID] RB#767</i><br />
	<i>[REVIEWED BY] xx, yy, zz</i><br />
	<i>[SIGNOFF BY] xx</i></font></p>
<p>TITLE Category：<br />
	&nbsp;&nbsp; &#8211; BUGFIX 代码修复<br />
	&nbsp;&nbsp; &#8211; FEATURE 新功能特性添加<br />
	&nbsp;&nbsp; &#8211; TASK 诸如代码美化、调整版本号等<br />
	&nbsp;&nbsp; &#8211; URGENT 紧急提交，对此类commit，可不做review和拦截</p>
<p>BODY Content：<br />
	&nbsp;&nbsp; 有关此次修改的详细信息说明</p>
<p>RELATIONSHIPS：<br />
	&nbsp;&nbsp; &#8211; [BUGID] 一般用Bug跟踪系统的ID号<br />
	&nbsp;&nbsp; &#8211; [REVIEWID] reviewboard上的ID号<br />
	&nbsp;&nbsp; &#8211; [REVIEWED BY] xx, yy, zz<br />
	&nbsp;&nbsp; &#8211; [SIGNOFF BY] xx</p>
<p><b>【&quot;全覆盖&quot;原则</b>】</p>
<p>所有变更代码都要发起在线&ldquo;Code Review Request&rdquo;，即便是会议走查的代码，会后也要补提&ldquo;Review Request&rdquo;。</p>
<p><b>【&ldquo;低保&rdquo;原则</b>】</p>
<p>每个Review Request至少选择两名评审负责人，填到&quot;Request&quot;中，这两个人必须对此Request给出评审意见，这是一个评审的<b>最低保障</b>了，这总比没有人评审要好。当然了其他人也都可以参与评审。只有这两名评审负责人明确提交&quot;ship it&quot; Comment后，该代码才算是通过评审。</p>
<p><b>【关键路径拦截】</b></p>
<p>&quot;对不起，若不符合规定，你的工作将无法进行下去&quot;。有了统一的Commit Log模板，我们就可以对大家的代码Commit环节做检查和<a href="http://tonybai.com/2010/08/07/use-svn-pre-commit-hook/">拦截</a>了。如果代码没有进行评审，无法填写模板中的字段内容，那代码将无法提交到代码库中。如 果虚构Commit log内容，这将是极大的错误，在抽查中一旦发现，后果将是很严重的^_^。</p>
<p>当然这一过程中还有很多细节需要考虑，比如Reviewer的选择不能集中在一个人身上，否则会造成热点；再比如紧急提交代码应该如何处理等等。&ldquo;法治&rdquo; 是与一定的&ldquo;国情&rdquo;相匹配的，并不是所有的组织都需要进行这么严格且略有死板&ldquo;法治&rdquo;手段，依团队内组员的专业能力和认知水平而定。</p>
<p>有些公司开发了自己的统一开发平台，将一系列流程都在一套系统中规范了起来，这当然是更好的&ldquo;法治&rdquo;了。但在没有这样的平台的前提下，初步使用上述的几个手段，还是会收获一些改进的。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/07/08/code-review-from-rule-of-man-to-rule-of-law/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>跨过BUG查找的”最后一公里”</title>
		<link>https://tonybai.com/2013/06/18/walk-through-the-last-mile-of-bugfix/</link>
		<comments>https://tonybai.com/2013/06/18/walk-through-the-last-mile-of-bugfix/#comments</comments>
		<pubDate>Tue, 18 Jun 2013 10:43:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[GDB]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[Programmer]]></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=1288</guid>
		<description><![CDATA[如果你看到一个C程序员在通宵熬夜神情紧张地对着电脑敲代码或阅读代码，多数只有两种可能：一是为了赶进度；二就是查找内存Bug。 &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;&#160; &#160; &#8212; 个人感悟 &#160; 昨晚搞到凌晨一点多，终于算是把一个棘手的Bug的来龙去脉搞清楚了。截至到今天，这个Bug已经困扰了项目组两个核心开发同事达三周之久了。 这个Bug的确很难查找： &#160;&#160; &#8211; 首先模拟环境下无法复现该Bug； &#160;&#160; &#8211; 生产环境下该Bug是随机出现的，发生频率十分低； &#160;&#160; &#8211; Bug出现时并未有dump core等明显异常现象出现，系统依旧运行良好。 得到Bug报告后，我的两位同事就开始对bug引发的问题现象进行了分析，得出了内存被污染的初步结论。之后又在生产环境做了GDB attach到进程的调试，甚至替换了生产环境的版本，利用传统的print语句在关键路径上输出提示信息，试图找到引发Bug的真正原因。但做过这些 后，所能得到的结论依旧停留在内存被污染，至于怎么被污染的、在哪个业务流程上被污染的却无从得知。无奈之下，两位同事开始根据 subversion的commit history进行代码比对和分析，试图查找到哪些新增或修改的代码引发了Bug。代码修改量小还好，如果修改数量巨大，这种代码比对就好比大海捞针，我 们无法保证注意力自始自终是集中的，结果两位同事也的确没有从代码变更中发现什么蛛丝马迹。这类Bug会让你有一种有力无处施展的感觉，面对这样 的Bug，我的两位开发人员似乎也失去了信心和思路。 下面简要描述一下这个Bug： 有这样一个字段数目众多的结构体foo_t，这里仅列出bug相关的几个字段e、c、flag、pdata： struct foo_t { &#160;&#160;&#160; &#8230; &#8230; &#160;&#160;&#160; char e[XX_SIZE]; &#160;&#160;&#160; char c[XX_SIZE]; &#160;&#160;&#160; char flag; &#160;&#160;&#160; data_t *pdata; &#160;&#160;&#160; &#8230; &#8230; [...]]]></description>
			<content:encoded><![CDATA[<p><i>如果你看到一个C程序员在通宵熬夜神情紧张地对着电脑敲代码或阅读代码，多数只有两种可能：一是为了赶进度；二就是查找内存Bug。</i><br />
	<i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &#8212; 个人感悟</i><br />
	&nbsp;<br />
	昨晚搞到凌晨一点多，终于算是把一个棘手的Bug的来龙去脉搞清楚了。截至到今天，这个Bug已经困扰了项目组两个核心开发同事达三周之久了。</p>
<p>这个Bug的确很难查找：</p>
<p>&nbsp;&nbsp; &#8211; 首先模拟环境下无法复现该Bug；<br />
	&nbsp;&nbsp; &#8211; 生产环境下该Bug是随机出现的，发生频率十分低；<br />
	&nbsp;&nbsp; &#8211; Bug出现时并未有dump core等明显异常现象出现，系统依旧运行良好。</p>
<p>得到Bug报告后，我的两位同事就开始对bug引发的问题现象进行了分析，得出了内存被污染的初步结论。之后又在生产环境做了<a href="http://tonybai.com/2006/01/08/debug-multiple-process-program-using-gdb/">GDB</a> attach到进程的调试，甚至替换了生产环境的版本，利用传统的print语句在关键路径上输出提示信息，试图找到引发Bug的真正原因。但做过这些 后，所能得到的结论依旧停留在内存被污染，至于怎么被污染的、在哪个业务流程上被污染的却无从得知。无奈之下，两位同事开始根据 subversion的commit history进行代码比对和分析，试图查找到哪些新增或修改的代码引发了Bug。代码修改量小还好，如果修改数量巨大，这种代码比对就好比大海捞针，我 们无法保证注意力自始自终是集中的，结果两位同事也的确没有从代码变更中发现什么蛛丝马迹。这类Bug会让你有一种有力无处施展的感觉，面对这样 的Bug，我的两位开发人员似乎也失去了信心和思路。</p>
<p>下面简要描述一下这个Bug：</p>
<p>有这样一个字段数目众多的结构体foo_t，这里仅列出bug相关的几个字段e、c、flag、pdata：</p>
<p><font face="Courier New">struct foo_t {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; char e[XX_SIZE];<br />
	&nbsp;&nbsp;&nbsp; char c[XX_SIZE];<br />
	&nbsp;&nbsp;&nbsp; char flag;<br />
	&nbsp;&nbsp;&nbsp; data_t *pdata;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}；</font></p>
<p>业务逻辑是：</p>
<p><font face="Courier New">if (flag) {<br />
	&nbsp;&nbsp;&nbsp; 处理e、c两个字段；<br />
	}</font><br />
	&nbsp;&nbsp;&nbsp;<br />
	bug现象：值本是1的flag字段被污染，值变成了0，导致e、c两个字段没有被做处理，从而引发业务异常，导致客户投诉。我的同事曾经做过如 下尝试，以确定内存污染的行为特点，她在flag之前又加了一个字段flag1：</p>
<p><font face="Courier New">struct foo_t {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; char e[XX_SIZE];<br />
	&nbsp;&nbsp;&nbsp; char c[XX_SIZE];<br />
	&nbsp;&nbsp;&nbsp; unsigned int flag1;<br />
	&nbsp;&nbsp;&nbsp; char flag;<br />
	&nbsp;&nbsp;&nbsp; data_t *pdata;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}；</font></p>
<p>在生产环境下运行得到的结果是flag1和flag值正常，但字段c的尾部字节遭到了污染。现象已经十分明确，离真相就差那最后一公里了。</p>
<p>对于上面的内存污染问题，我首先会怀疑在处理flag或c之前的字段时出现了缓冲区溢出，导致后面字段的内容被整体或局部覆盖。不过从bug现象 来看，这个思路也有说不通的地方，那就是为何是c的尾部字段被污染，而不是从头部开始呢？不过我们依旧沿着这个思路追查了e以及e的诸多前驱字 段，细致的分析了代码，但没有发现<a href="http://tonybai.com/2006/09/06/be-careful-of-the-trap-of-overflow/">溢出</a>点。</p>
<p>c或flag的后继字段比如pdata要想污染c或flag则必须具备更多条件，至少要有操作&amp;pdata的代码，之前基本认为这不太可 能。但现在仅有这一条路可以继续走下去了，也只能沿着这条路走下去。事实证明我们走的没错。在后续的处理流程中有这样的一个函数：</p>
<p><font face="Courier New">int func(void *p, int size)</font></p>
<p>这个函数本来是用于处理data_t*变量的，但由于编码者的疏忽，将&amp;pdata传给了p，另外size这个参数也传了一个错误的值， 估计是滥用了copy&amp;paste。而func函数体中对p指向的内存地址做了修改，这个修改直接污染了 ((char*)&amp;pdata + size)起始的那片内存块儿，这就是问题的真正原因所在。这样看来pdata并未污染其所在的foo_t实例中的flag或c字段，而是污染了其他foo_t实例中的flag或c字段，因为这些实例都放在一个mem block pool中的，所以这还是一个随机的远距离内存污染^_^。</p>
<p>我走完了BUG查找的最后那一公里，到达了终点。这个BUG的查找确实不易，但并非遥不可及，为何我的两位同事就停在离真相只有一公里的地方而踌 躇不前了呢？对此我也做了一些考量，希望能在日后的BUG查找方面给予帮助。</p>
<p>要跨过BUG查找的那最后一公里，可从如下几个方面着手努力：</p>
<p><b>* 收罗证据，不放过一处可疑之处</b></p>
<p>这是准备工作，就好比警察查看罪案现场，哪怕是一根毛发，一处异物也不能放过。一般来说我们至少要收集到Bug发生时的各方面信息，包括：</p>
<p>&nbsp;- 系统日志<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 这个时间点上各个模块的日志都要搜罗到；</p>
<p>&nbsp;- core文件<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果bug引发core dump，那core文件是bug查找的最佳入口；</p>
<p>&nbsp;- 通信数据包内容<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于很多后端服务程序而言，不合法的通信数据包常常会引发Bug，我经手的类似Bug就不止一起了。必要时通过抓包工具将通信包抓到文件中以备后用。</p>
<p>&nbsp;- CPU/内存/磁盘实时状况<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 千万不要小视这些信息。如果发现CPU过高，则很可能代码存在死循环的可能（后pstack进程号，则可直接找到问题所在）；如果磁盘满，则可以很好解释 数据不完整的异常；如果mem占用过高，则可以解释分配内存异常或性能下降等问题。</p>
<p>&nbsp;- 系统操作日志<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果有管理员的操作行为的话，我们也不要放过，将操作日志（一般系统都有保存，并需要对这些日志进行定期审核）截取并保留，以备后用。</p>
<p>&nbsp;- 操作系统/硬件相关异常信息等。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果是因为OS或硬件异常导致的Bug，那搜集到这些信息就太重要了，否则你将付出惨重的Bug查找代价。</p>
<p>Bug查多了你就会有这种感悟：证据用时方恨少啊！</p>
<p><b>* 沉下心</b><b>，保持清晰思路</b></p>
<p>BUG有难有易，简单的Bug大家都能应付，而困难的Bug，就要比拼能力和经验了。要想解决掉Bug，务必要沉下心，不急不躁，这是保持大脑始 终有清晰思路的前提。</p>
<p>能用工具（比如GDB）调试出来的Bug，都不是最难的问题，因为现场就摆在你的面前，你可以看到一切蛛丝马迹。最难的问题最终都是要通过脑力分 析出来的。</p>
<p>解决问题前，要根据之前搜罗的证据，形成自己的查找思路。没有思路是可怕的。没有思路的时候，也不要急于开始查，那样只会乱套。应根据已有的蛛丝 马迹，行成一些思路，哪怕这个思路你自己都不是很肯定，先按这个思路做做看，也许走出一步后，你又能收获新的信息，形成新的思路。就这样敏捷地向 前进，边向前探索边定期回顾。</p>
<p><b>* 知晓原理，缩小查找范围，形成正确思路</b></p>
<p>要保持清晰正确的思路，开发人员对系统的运行原理要做到十分清楚，这样可以缩小查找范围，重点突破。就好比上面的那个bug例子，我们要知道 c/flag被污染有几种潜在的可能，并形成多种思路，然后沿着这几种可能的思路继续走下去。在这次查找过程中，想必两位同事恰恰是在原理这方面 没有理解透彻吧。</p>
<p><b>* 质疑，从自己的代码开始</b></p>
<p>查Bug就要抛弃&ldquo;不可能&rdquo;，拥抱&ldquo;质疑一切&rdquo;。而质疑要从自己的代码开始。程序员或多或少都有一种&ldquo;自负&rdquo;的心态，骨子里会认为自己的代码肯定 是正确的。如果出现问题，一定是其他人代码的问题，哪怕是OS这样总体来说十分稳定的平台也会成为被首先质疑的对象。不过事实证明，错误多出在我 们自己的代码中，毫无理由的去怀疑操作系统、怀疑你使用的第三方库，多半会南辕北辙，浪费你宝贵的查找时间。</p>
<p><b>* 拥抱调试技巧</b><b>和工具</b></p>
<p>必要的调试技巧是Bug查找的基本功底，这些技巧在涉及内存问题查找过程中相当有用。</p>
<p>&nbsp; &#8212; print语句<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 不用多说，print语句是最简单、最常用的调试手段，在代码任意位置，根据你的需要，输出信息，帮助你分析bug原因。其唯一的缺点就是可能需要你重新 build代码和部署你的应用。</p>
<p>&nbsp; &#8212; gdb切入进程地址空间查看堆栈<br />
	&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; 利用gdb一类的专用调试工具可在代码运行时切入进程地址空间，实时查看数据变化。你也可以在gdb下执行应用，获得同样的效果（适合单进程应用）。<br />
	&nbsp;<br />
	&nbsp; &#8212; 调试版中采用magic number + assert<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; C程序的bug多为内存问题。常见的内存越界访问或污染的调试手段是在代码中为内存块添加magic number，并在特定环节用assert保证该magic number的值是没有被修改的。一旦值改变了，则说明问题发生在执行流的两次assert之间的某个地方，后续可进一步缩小assert间隙，直到定位 到问题。</p>
<p>&nbsp; &#8212; 让bug尽可能的容易复现<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一个可以在模拟环境下复现的Bug总是比较好查的。出于这个考虑，我们可通过放大问题区域来尽可能更容易的复现bug，比如将一个字节的字段改为4个字 节，这样可能占据更多被污染的区域，比较利于Bug的复现（但这不总是ok的）。</p>
<p><b>* 把握节奏，避免陷入惯性思维</b></p>
<p>一些比较难fix的Bug，其查找过程可能会十分漫长，就像这次我们遇到的这个问题。这就需要我们的开发人员把握好Bug查找的节奏，因为长时间 调试和查问题容易让人陷入惯性思维，反倒不利于Bug的查找。一旦意识到自己进入惯性思维后，可考虑换种活动做做，比如出去散散步、洗个热水澡 等。或者给其他人员讲解你的查找思路，这个过程中自己可能会发现思路上的缺陷，或者由他人指出你思路方面的问题。</p>
<p><b>感觉Bug查找是一门手艺活，要学会慢工出细活，这总比不出活儿的要好，尤其是在面对那些十分诡异的内存Bug时。</b></p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/06/18/walk-through-the-last-mile-of-bugfix/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>《Understanding and Using C Pointers》要点先睹为快</title>
		<link>https://tonybai.com/2013/05/28/understanding-and-using-c-pointers-keypoint-preview/</link>
		<comments>https://tonybai.com/2013/05/28/understanding-and-using-c-pointers-keypoint-preview/#comments</comments>
		<pubDate>Mon, 27 May 2013 19:00:05 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Aliasing]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[C11]]></category>
		<category><![CDATA[C99]]></category>
		<category><![CDATA[C标准]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[O‘Reilly]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[struct]]></category>
		<category><![CDATA[union]]></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">http://tonybai.com/?p=1282</guid>
		<description><![CDATA[如果你问十个C程序员：你觉得C语言的核心是什么？这十个程序员都会回答：指针。 指针具备成为C语言核心的两个关键要素：强大与争议。 * 指针的强大源自于其天生与机器内存模型的适配。使用指针让代码紧凑，并可获得仅次于汇编代码的执行效率；使用指针可以让C程 序员毫不费力地尽情操纵着内存中的每个byte甚至是bit；使用指针可以为C程序员提供无与伦比的操作灵活性。总之，在C语言中指针几乎是无所 不能的代名词。得指针者得天下，没有指针，C语言将变得平庸。 * 成也指针，败也指针。指针的争议之处就在于其在赋予C程序员无比强大的Power的同时，也常常带来无穷的烦恼甚至灾祸，比如 内存问题、调试困难或因指针导致的程序崩溃等。就好比人类社会，做核心人物有争议是难免的，比如足球界有马拉多纳，跳水界有菲尔普斯，斯诺克界有 奥沙利文^_^。 好了，言归正传，我们回到C语言图书上来。目前市面上的C语言书籍，无论国内国外，无论经典还是山寨，基本都是百科大全型，将C语言讲的面面俱 到。比如最近的一本大而全的经典应当属《C Programming , A Modern Approach》，中文版书名为《C语言程序设计：现代方法》第2版。以至于发展到今天，C语言似乎也没啥可讲的了，新出的C语言书大多是与前辈们雷同 的作品。近两年来也有O&#39;reilly出版的C语言书籍，比如： *《Head First C》 *《21st Century C &#8211; C Tips from the New School》 前者是典型的Head First风格的C教程，后者则是另辟蹊径，结合C语言外延（构建、调试、打包、版本控制、面向对象与C、知名C语言开源库等)进行讲解。这两本书虽形式 有变化，但终究脱离不开百科大全型，针对C的核心-指针并未有较多的深入探讨。而市场上专门写指针的书也稀少的很（似乎鬼子国那边有一本，叫什么 《征服C指针》），唯一的一本书名与指针扯上关系的书《Pointers on C》（中文名&#8220;C和指针&#8221;）其实依旧是一本C语言大全。于是乎国外著名出版社O&#39;Reilly今年5月出品了一本专门讲解C语言核心 &#8211; 指 针的书《Understanding and Using C Pointers》，以满足C程序员深入理解C语言核心并实现进阶的诉求。O&#39;Reilly就是O&#39;Reilly，总是能抓住C语言书籍方面的深度阅读需 求^_^。 《Understanding and Using C Pointers》是个小册子，拢共才200多页，但内容却全部是围绕C语言指针展开的，从最基本的指针声明与操作、C内存模型、动态内存分配，讲到指针 与数组、结构体、字符串的关系，再到最后指针的高级特性：强制转换、Strict Aliasing、线程共享、多态支持等，由浅入深的进行细致的剖析。其作者认为作为C语言核心的指针值得花200页篇幅去讲解，而且期望所有读者在读完 此书后能对C指针有个扎实的理解。总之，这本书对系统C程序员理解C语言的核心-指针是大有裨益的。在其中文版（已经由图灵出版社引进版权了）尚 未出版之前，这里带你先了解以下本书的要点： 第一章 [...]]]></description>
			<content:encoded><![CDATA[<p><i>如果你问十个C程序员：你觉得C语言的核心是什么？这十个程序员都会回答：指针。</i></p>
<p>指针具备成为C语言核心的两个关键要素：<b>强大</b>与<b>争议</b>。</p>
<p>* <b>指针的强大</b>源自于其天生与机器内存模型的适配。使用指针让代码紧凑，并可获得仅次于汇编代码的执行效率；使用指针可以让C程 序员毫不费力地尽情操纵着内存中的每个byte甚至是bit；使用指针可以为C程序员提供无与伦比的操作灵活性。总之，在C语言中指针几乎是无所 不能的代名词。得指针者得天下，没有指针，C语言将变得平庸。</p>
<p>* 成也指针，败也指针。<b>指针的争议</b>之处就在于其在赋予C程序员无比强大的Power的同时，也常常带来无穷的烦恼甚至灾祸，比如 内存问题、调试困难或因指针导致的程序崩溃等。就好比人类社会，做核心人物有争议是难免的，比如足球界有马拉多纳，跳水界有菲尔普斯，斯诺克界有 奥沙利文^_^。</p>
<p>好了，言归正传，我们回到C语言图书上来。目前市面上的C语言书籍，无论国内国外，无论经典还是山寨，基本都是百科大全型，将C语言讲的面面俱 到。比如最近的一本大而全的经典应当属《C Programming , A Modern Approach》，中文版书名为《<a href="http://book.douban.com/subject/4279678/">C语言程序设计：现代方法</a>》第2版。以至于发展到今天，C语言似乎也没啥可讲的了，新出的C语言书大多是与前辈们雷同 的作品。近两年来也有O&#39;reilly出版的C语言书籍，比如：</p>
<p>*《<a href="http://book.douban.com/subject/6919383/">Head First C</a>》<br />
	*《<a href="http://book.douban.com/subject/11229717/">21st Century C &#8211; C Tips from the New School</a>》</p>
<p>前者是典型的Head First风格的C教程，后者则是另辟蹊径，结合C语言外延（构建、调试、打包、版本控制、面向对象与C、知名C语言开源库等)进行讲解。这两本书虽形式 有变化，但终究脱离不开百科大全型，针对C的核心-指针并未有较多的深入探讨。而市场上专门写指针的书也稀少的很（似乎鬼子国那边有一本，叫什么 《<a href="http://book.douban.com/subject/21317828/">征服C指针</a>》），唯一的一本书名与指针扯上关系的书《Pointers on C》（中文名&ldquo;<a href="http://book.douban.com/subject/3012360/">C和指针</a>&rdquo;）其实依旧是一本C语言大全。于是乎国外著名出版社O&#39;Reilly今年5月出品了一本专门讲解C语言核心 &#8211; <b>指 针</b>的书《<a href="http://book.douban.com/subject/20491037/">Understanding and Using C Pointers</a>》，以满足C程序员深入理解C语言核心并实现进阶的诉求。O&#39;Reilly就是O&#39;Reilly，总是能抓住C语言书籍方面的深度阅读需 求^_^。</p>
<p>《Understanding and Using C Pointers》是个小册子，拢共才200多页，但内容却全部是围绕C语言指针展开的，从最基本的指针声明与操作、C内存模型、动态内存分配，讲到指针 与数组、结构体、字符串的关系，再到最后指针的高级特性：强制转换、Strict Aliasing、线程共享、多态支持等，由浅入深的进行细致的剖析。其作者认为作为C语言核心的指针值得花200页篇幅去讲解，而且期望所有读者在读完 此书后能对C指针有个扎实的理解。总之，这本书对系统C程序员理解C语言的核心-指针是大有裨益的。在其中文版（已经由图灵出版社引进版权了）尚 未出版之前，这里带你先了解以下本书的要点：</p>
<p><b>第一章 简介</b></p>
<p><i>1、指针与内存</i></p>
<p>&nbsp;&nbsp; 【指针声明语法】<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">int *pi;</font></p>
<p>&nbsp;&nbsp; 【理解复杂指针声明】<br />
	&nbsp;&nbsp;&nbsp; 方法：从后向前读，例子：</p>
<p><font face="Courier New">&nbsp;&nbsp; const int *pci;</font></p>
<p><font face="Courier New">&nbsp;&nbsp; pci is a variable&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pci<br />
	&nbsp;&nbsp; pci is a pointer variable&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *pci<br />
	&nbsp;&nbsp; pci is a pointer variable to an integer&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *pci<br />
	&nbsp;&nbsp; pci is a pointer variable to a constant integer&nbsp;&nbsp;&nbsp;&nbsp; const int *pci</font></p>
<p>&nbsp;&nbsp;&nbsp; 【地址操作符】<br />
	&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">pi = &amp;num;</font></p>
<p>&nbsp;&nbsp;&nbsp; 【输出指针值】<br />
	&nbsp;&nbsp;&nbsp; 通过%x、%o、%p输出(printf)指针的值，一般使用%p（%p输出结果不一定等同于%x，是与实现有关的）。例子如下：<br />
	<font face="Courier New">&nbsp;&nbsp; &nbsp; int num = 0;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; int *pi = &amp;num;<br />
	&nbsp;&nbsp; &nbsp; printf(&quot;Address of num: %d Value: %d\n&quot;,&amp;num, num);<br />
	&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;Address of pi: %d Value: %d\n&quot;,&amp;pi, pi);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp; Address of num: 4520836 Value: 0<br />
	&nbsp;&nbsp;&nbsp;&nbsp; Address of pi: 4520824 Value: 4520836</font></p>
<p>&nbsp;&nbsp;&nbsp; 【通过间接访问操作符解引用指针】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 间接访问操作符*，使用例子如下：<br />
	<font face="Courier New">&nbsp;&nbsp; &nbsp; int num = 5;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; int *pi = &amp;num;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%d\n&quot;,*pi); // Displays 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp; *pi = 200;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%d\n&quot;,num); // Displays 200</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【指向函数的指针】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New"> void (*foo)();&nbsp; // 这个变量声明中的foo就是一个指向函数的指针</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【Null概念】</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; null concept<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 赋值为NULL的指针变量表示该指针不指向任何内存地址。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; null pointer constant<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; null concept的具体支撑实现，其常量值可能是常量值0，也可能不是。依具体实现而定。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL macro<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在许多标准库实现中，NULL定义如下：<font face="Courier New">#define NULL ((void *)0)</font>，这也是我们对NULL的通常理解。当然这是依Compiler的具体实现而定的。如果编译 器使用非全0位模式实现了NULL，那该编译器就要保证在指针上下文中使用的NULL或0是null pointer。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ASCII NUL<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一个全0的字节。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; null string<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一个不包含任何字符的空字符串。C字符串在最后都放置一个结尾0值。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; null statement<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 只包含一个分号的空语句。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指向void的指针<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 指向void的指针被成为通用指针，可以用于引用任意类型的数据。它有两个属性：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 指向void的指针与指向char类型的指针具有相同的内存表示与内存对齐约束。<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; void指针永远不等于其他类型指针，两个赋值为NULL的void pointer是相等的。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 任何指针都可以被赋给一个void pointer，并且之后还可以被转换回其原来的类型。<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; int num;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *pi = &amp;num;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void* pv = pi;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pi = (int*) pv;</font><br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; void pointer用于数据指针，而不是函数指针。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 全局void pointer或static void pointer在程序启动时被初始化为NULL。</p>
<p><i>2、指针大小与类型</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在多数现代平台上，指针的大小都是相同的，与其类型无关。指向char的指针与指向结构体的指针大小相同。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指向函数的指针可能与指向数据类型的指针大小有差异，这要依具体实现而定。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 【内存模型】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在不同机器和编译器下，C语言原生类型的大小是不同的。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 描述不同数据模型的一般记法：<font face="Courier New">I In L Ln LL LLn P Pn</font>，例如LP64、ILP64、LP32等。<br />
	&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 【预定义的指针相关类型】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">size_t</font> 用于表示对象的大小的一个安全类型。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">ptrdiff_t</font> 用于处理指针运算<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">intptr_t和uintptr_t </font>用于存 储指针地址</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; int num;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; intptr_t *pi = &amp;num;</font></p>
<p><i>3、指针操作符</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【指针运算】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pointer + integer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指针实际移动的字节数 = integer + sizeof(integer_type)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void* pointer的指针运算操作行为是未定义的，依赖Compiler的具体实现。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pointer &#8211; integer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指针实际移动的字节树 = integer &#8211; sizeof(integer_type)。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pointer1 &#8211; pointer2<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 两个指针所指地址间的差值，常用于判断数组中元素的先后次序。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 比较pointers</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【指针比较】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指针可以使用标准的比较操作符（&gt; and &lt;）进行比较，可用来判断数组中元素的先后次序。</p>
<p><i>4、指针的通常用法</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 【多级间接寻址】<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 双指针(double pointer) &#8211; 指向指针的指针。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">char *titles[] = {&quot;A Tale of Two Cities&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; &quot;Wuthering Heights&quot;,&quot;Don Quixote&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; &quot;Odyssey&quot;,&quot;Moby-Dick&quot;,&quot;Hamlet&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; &quot;Gulliver&#39;s Travels&quot;};<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char **bestBooks[3];<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bestBooks[0] = &amp;titles[0];<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bestBooks[1] = &amp;titles[3];<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bestBooks[2] = &amp;titles[5];</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 间接寻址的级数并没有限制，但过多的级数会让人难以理解。<br />
	&nbsp;&nbsp;<br />
	&nbsp; &nbsp; 【常量和指针】</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指向常量的指针<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int limit = 500;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int *pci = &amp;limit;</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">*pci = 600；/* Error， 我们不能解引用一个常量指针并修改其所指的内存值 */</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">const int *pci &lt;=&gt; int const *pci;</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指向非常量的常量指针<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int num;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *const cpi = &amp;num;</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">*cpi = 25; /* 可以解引用常量指针并修改其所指的内存的值 */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int limit;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cpi = &amp;limit; /* Error，我们不能为常量指针重新赋新值 */</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int limit1 = 300;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *const cpi1 = &amp;limit1; /* Warning: 指向非常量的常量指针被用常量 的地址初始化了 */<br />
	&nbsp;<br />
	&nbsp; &nbsp; &nbsp; 指向常量的常量指针 &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int limit = 300;<br />
	&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; const int *const cpci = &amp;limit; </font>/* 声明后，我们不能通过cpci修改limit，也不能为cpci重新赋值 */</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指向&ldquo;指向常量的常量指针&rdquo;的指针<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int limit = 300;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int *const cpci = &amp;limit;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int *const *pcpci = &amp;cpci;</font></p>
<p><b>第二章 C语言动态内存管理</b></p>
<p>在运行时通过函数手工从heap分配和释放内存的过程称为动态内存管理。</p>
<p><i>1、动态内存分配</i><br />
	&nbsp;&nbsp;&nbsp; 【使用malloc函数】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp; int *pi = (int*) malloc(sizeof(int));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *pi = 5;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(pi);</font></p>
<p>&nbsp;&nbsp;&nbsp; 【内存泄漏】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 丢失了内存地址<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 没有调用free函数释放内存</p>
<p><i>&nbsp;2、动态分配内存函数</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; malloc、realloc、calloc、free<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 是否对malloc出的内存起始地址进行强制转型<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">int *p = (int*)malloc(4);</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void *pointer可以转换为任意类型指针，没有强制转型也可以。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但显式的强制转型可以通过代码看出意图，并且与C++编译器(包括早期C编译器)兼容<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 你不能用内存分配函数分配的内存去初始化全局或Static变量。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; alloca函数用于在栈上动态分配内存，函数结束时，这块内存自动释放；但alloca不是标准C库函数，移植性差。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C99支持可变长度数组(VLA)，数组声明时的元素个数可以是运行时才能确定值的变量，但数组size一旦在运行时被确定，数组大小就无法再做改变：<br />
	<font face="Courier New">&nbsp; &nbsp; &nbsp;&nbsp; void compute(int size) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char* buffer[size];<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp; &nbsp; &nbsp; }</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p><i>&nbsp;3、悬挂指针</i><br />
	<i>&nbsp;&nbsp;&nbsp;&nbsp; </i>被free后依然引用原先内存地址的指针，称为dangling pointer。<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 悬挂指针可能导致如下问题：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 如果访问其引用的内存，将导致不可预期的结果<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 如果内存不可访问了，将导致段错误<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 存在潜在的安全风险。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 悬挂指针引起的问题调试起来十分困难，以下几种方法用于避免发生悬挂指针问题或快速查找悬挂指针问题：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; free后，设置指针为NULL；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 编写一个替代free的函数；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 用特定值填充free的内存块，便于快速定位dangling pointer问题<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 使用第三方工具检查dangling pointer问题</p>
<p><b>第三章 指针与函数</b></p>
<p>当与函数一起使用时，指针有两个方面发挥重要作用：<br />
	&nbsp;&nbsp; &#8211; 当指针以参数形式传递给函数时，允许函数修改指针所指内存区域的值，并且这种传递方式更加高效；<br />
	&nbsp;&nbsp; &#8211; 声明函数指针时，函数的名字被求值为函数的地址。<br />
	&nbsp;<br />
	<i>1、程序栈和堆</i></p>
<p>&nbsp;&nbsp;&nbsp; 【程序栈】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 栈和堆共享一块内存区域。栈在这块区域的低地址部分，堆在高地址部分。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 程序栈用于存放栈帧(stack frame)，栈帧中存放的是函数的参数与local变量。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 栈增长方向：向上；堆的增长方向：向下。</p>
<p>&nbsp;&nbsp;&nbsp; 【栈帧的组成】<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 一个栈帧包含如下几个元素：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 返回地址<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 本地变量<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 函数参数<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 栈指针(Stack pointer)和栈帧指针(base pointer or frame pointer)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; Stack pointer和frame pointer用于运行时系统对栈的管理。前者总是指向栈的顶端；后者指向栈帧内的某个地址，比如函数的返回地址；frame pointer辅助程序访问栈帧内的元素。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 栈帧的创建，见下面例子：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">float average(int *arr, int size) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int sum;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;arr: %p\n&quot;,&amp;arr);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;size: %p\n&quot;,&amp;size);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;sum: %p\n&quot;,&amp;sum);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0; i&lt;size; i++) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sum += arr[i];<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (sum * 1.0f) / size;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; average的栈帧中沿着栈&ldquo;向上&rdquo;的方向，依次推入的是：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 参数 size、arr （与声明的顺序恰好相反）<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 函数average调用的返回地址<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 本地变量sum（如果有多个本地变量，推入栈的顺序也与变量声明顺序相反）</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 每个线程通常都在自己的栈中创建栈帧。</p>
<p><i>2、指针作为参数和返回值</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C语言的参数是&ldquo;按值传递&rdquo;的，包括指针本身，函数内使用的是参数的copy。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在处理大数据结构时，将指针作为参数传递给函数或作为返回值会使得程序执行起来更加高效（只是copy一个指针大小的数据，而不是指针所指向的数据对象大 小）。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 另外一个以指针作为函数参数的目的是希望在函数内部对数据进行修改。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当传递一个指向常量的指针给函数时，其意图为不希望函数内部对指针所指的数据进行修改。例如void passingAddressOfConstants(const int* num1, int* num2)，不希望num1所指数据被修改。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将指针作为返回值返回时，应避免以下几个常见问题：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 返回未初始化的指针<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 返回指向非法地址的指针<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 返回指向函数本地变量的指针<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 返回指针后，没有释放其所指的内存块<br />
	&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果函数要修改的不是参数中指针所指的数据，而是指针本身所指的内存地址，那么应以double pointer形式作为函数参数：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void allocateArray(int **arr, int size, int value) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *arr = (int*)malloc(size * sizeof(int));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(*arr != NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0; i&lt;size; i++) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *(*arr+i) = value;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *vector = NULL;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; allocateArray(&amp;vector,5,45);</font></p>
<p><i>3、</i><i>函数指针</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数指针就是存放函数地址的指针。&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用函数指针可能导致程序运行变慢（可能感知不到），因为函数指针的使用可能导致CPU无法正确的运用分支预测，导致CPU流水线中断。</p>
<p>&nbsp;&nbsp;&nbsp; 【声明函数指针】</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数指针的声明看起来像函数原型，比如：void (*foo)(int i);<br />
	&nbsp; &nbsp; &nbsp; 程序员应该确保通过函数指针调用函数的正确使用，因为C编译器不会检查是否正确的为函数指针传入正确的参数（类型、顺序以及个数）。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 通常我们用typedef声明一个函数指针类型，比如：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef void (*funcptr)(int i)；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; funcptr fp = foo;</font></p>
<p>&nbsp;&nbsp;&nbsp; 【函数指针强制转型】<br />
	&nbsp;&nbsp;&nbsp; &nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一个类型的函数指针可以被强制转为另外一种类型函数指针。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 转型后的指针 == 转型前的指针<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; typedef int (*fptrToSingleInt)(int);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; typedef int (*fptrToTwoInts)(int,int);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; int add(int, int);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fptrToTwoInts fptrFirst = add;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fptrToSingleInt fptrSecond = (fptrToSingleInt)fptrFirst;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fptrFirst = (fptrToTwoInts)fptrSecond;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf(&quot;%d\n&quot;,fptrFirst(5,6));</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在函数指针间转换，很可能导致函数调用失败。</p>
<p><b>第四章 指针与数组</b></p>
<p><i>1、数组概述</i></p>
<p>数组与指针记法关系紧密，在特定上下文中可以相互替换。<br />
	数组内部表示中并没有数组长度信息。<br />
	&nbsp;<br />
	&nbsp; 【一维数组】<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">int vector[5];</font></p>
<p>&nbsp;&nbsp;&nbsp; 一维数组是一个线性结构。数组下标起始于0，终止于(元素个数-1)。</p>
<p>&nbsp; 【二维数组】<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">int matrix[2][3] = {{1,2,3},{4,5,6}};</font></p>
<p>&nbsp;&nbsp;&nbsp; 二维数组使用行和列标识数组元素。这类数组需要被映射到一个一维地址空间中。<br />
	&nbsp;&nbsp;&nbsp; 在C中，二维数组的第一行放在内存的最开始处，接下来是第二行，&#8230;，直到最后一行，这就是所谓的&ldquo;行主序&rdquo;。</p>
<p>&nbsp; 【多维数组】<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">int arr3d[3][2][4] = {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {{1, 2, 3, 4}, {5, 6, 7, 8}},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {{9, 10, 11, 12}, {13, 14, 15, 16}},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {{17, 18, 19, 20}, {21, 22, 23, 24}}<br />
	&nbsp; };</font></p>
<p>&nbsp;&nbsp;&nbsp; 二维以上的维数的数组称为多维数组，其元素内存分配依旧遵守二维数组那种映射方式。</p>
<p><i>2、指针记法(notation)与数组</i></p>
<p>&nbsp;&nbsp;&nbsp; 指针记法与数组记法在一定场合可以互换，但两者并不完全相同。<br />
	&nbsp;&nbsp;&nbsp; 数组名单独使用时，我们得到的是数组的地址；该地址等同于数组内第一个元素的地址。</p>
<p><font face="Courier New">&nbsp; int vector[5] = {1, 2, 3, 4, 5};<br />
	&nbsp; int *pv = vector;<br />
	&nbsp; int (*pv)[5] = &amp;vector;</font></p>
<p>&nbsp;&nbsp;&nbsp; vector与&amp;vector不同，前者返回指向一个整型变量的指针（int *），后者返回一个指向整个数组的指针(int[5] *)。<br />
	<font face="Courier New">&nbsp; pv[i] &lt;=&gt; *(pv + i)<br />
	&nbsp; *(pv + i) &lt;=&gt; *(vector + i)</font></p>
<p>&nbsp; 【指针与数组间的不同】</p>
<p>&nbsp;&nbsp;&nbsp; <font face="Courier New">int vector[5] = {1, 2, 3, 4, 5};<br />
	&nbsp; int *pv = vector;</font></p>
<p>&nbsp;&nbsp;&nbsp; sizeof(vector) = 20 != sizeof(pv)</p>
<p>&nbsp;&nbsp;&nbsp; pv是lvalue，可以被修改而指向不同的地址；比如pv = pv + 1<br />
	&nbsp;&nbsp;&nbsp; 而vector不能被修改。vector = vector + 1这个表达式是错误的，不过pv = vector + 1是ok的。</p>
<p>&nbsp; 【使用malloc创建一维数组】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; int *pv = (int*) malloc(5 * sizeof(int));<br />
	&nbsp;&nbsp;&nbsp; pv[3] = 10;</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp; 可使用realloc改变malloc创建的数组的大小。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<br />
	3<i>、传递一维数组</i><br />
	&nbsp;&nbsp;&nbsp; 两种记法：数组记法和指针记法，分别如下：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; void displayArray(int arr[], int size);<br />
	&nbsp;&nbsp;&nbsp; void displayArray(int* arr, int size);</font></p>
<p>&nbsp;&nbsp;&nbsp; 无论哪种，displayArray函数体内int arr[]或int *arr都将以int *arr方式使用，即数组名退化为指针，sizeof(arr) = 指针长度，而不是数组总长度。</p>
<p>&nbsp;&nbsp; 【一维指针数组】<br />
	&nbsp;&nbsp;&nbsp;<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; int* arr[5];<br />
	&nbsp;&nbsp;&nbsp; for(int i=0; i&lt;5; i++) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; arr[i] = (int*)malloc(sizeof(int));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *arr[i] = i;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>&nbsp;&nbsp; 【指针与多维数组】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 多维数组可以看成是由子数组组成的，就好比二维数组的每行都可以看成是一个一维数组。<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int matrix[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int (*pmatrix)[5] = matrix;</font></p>
<p>4<i>、传递多维数组</i></p>
<p><font face="Courier New">&nbsp;&nbsp; void display2DArray(int arr[][5], int rows)；&lt;=&gt;<br />
	&nbsp;&nbsp; void display2DArray(int (*arr)[5], int rows)；</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 上面两个版本是等价的。两个版本都指定了列的值，因为编译器需要知道每行的元素个数。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 注意第二个版本不等价于<font face="Courier New">void display2DArray(int *arr[5], int rows)</font>；</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在void display2DArrayUnknownSize(int *arr, int rows, int cols)的 函数体实现中，你不能使用arr[i][j]，因为arr并未被声明为二维数组。</p>
<p><i>5、动态分配二维数组</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【采用不连续的内存分配方式】</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; int rows = 2;<br />
	&nbsp;&nbsp;&nbsp; int columns = 5;<br />
	&nbsp;&nbsp;&nbsp; int **matrix = (int **) malloc(rows * sizeof(int *));<br />
	&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; rows; i++) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; matrix[i] = (int *) malloc(columns * sizeof(int));<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【采用连续内存分配的方式】</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; int rows = 2;<br />
	&nbsp;&nbsp;&nbsp; int columns = 5;<br />
	&nbsp;&nbsp;&nbsp; int **matrix = (int **) malloc(rows * sizeof(int *));<br />
	&nbsp;&nbsp;&nbsp; matrix[0] = (int *) malloc(rows * columns * sizeof(int));<br />
	&nbsp;&nbsp;&nbsp; for (int i = 1; i &lt; rows; i++)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; matrix[i] = matrix[0] + i * columns;</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; or</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; int *matrix = (int *)malloc(rows * columns * sizeof(int));</font></p>
<p><b>第五章 指针与字符串</b></p>
<p><i>1、字符串基础</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 字符串：以ASCII结尾&#39;\0&#39;字符结尾的字符序列。<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 分类：字节字符串(byte string) &#8211; char类型字符序列<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 宽字符串（wide string) &#8211; wchar_t 类型字符序列（每个字符16bit or 32bit，依编译器实现而定）<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 字符串声明：<font face="Courier New">char header[32]</font> or<font face="Courier New"> char *header</font>；</p>
<p>&nbsp;&nbsp;&nbsp; 【字符串字面量池(String literal pool)】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 字符串字面量定义后将被放在字面量池中。这块内存区域存放的是组成字符串的字符序列。当一个字面量多次使用时，通常在字面量池中只存储一份该字符串。这将 降低程序的内存使用量。并且通常情况下，字面量池中的字符串是immutable的。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 大多数编译器都提供了编译开关，用于指示是否关闭字符串字面量池，比如Gcc的-fwritable-strings。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【字符串初始化】、<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">char *header = &quot;Media Player&quot;;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; or<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char header[] = &quot;Media Player&quot;;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; or<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char header[13];<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strcpy(header,&quot;Media Player&quot;);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; or<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *header = (char*) malloc(strlen(&quot;Media Player&quot;)+1);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strcpy(header,&quot;Media Player&quot;);</font></p>
<p><i>2、标准字符串操作</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 比较字符串：strcmp<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 拷贝字符串：strcpy<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 连接字符串：strcat</p>
<p><i>3、传递字符串</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 传递简单字符串：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp; size_t stringLength(char* string) ;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size_t stringLength(char string[]);</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 传递字符串常量：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size_t stringLength(const char* string);</font></p>
<p><i>4、返回字符串</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回一个字面量：<font face="Courier New">return &quot;Boston Processing Center&quot;</font>；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 动态分配的内存：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char* spaces = (char*) malloc(number + 1);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return spaces;</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回local字符串的地址是危险的。</p>
<p><i>5、函数指针与字符串</i></p>
<p><b>第六章 指针与结构体</b></p>
<p><i>1、简介</i></p>
<p>&nbsp;&nbsp;&nbsp; 【如何为结构体分配内存】 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 结构体的大小往往大于该结构体所有字段大小之和，因为有数据对齐的需求，导致编译器在进行结构体内存分配时进行了padding操作。特定数据类型具有一 定的对齐要求，比如short类型的字段要求其地址能被2整除，而integer类型的字段要求其起始地址能被4整除。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 考虑到这些多余分配的内存，你应该谨慎对待如下操作：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 小心使用指针运算<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 结构体数组的元素间有多余内存空间</p>
<p>&nbsp;&nbsp;&nbsp; 【结构体内存释放】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为结构体分配内存时，运行时不会自动为结构体内的指针字段分配内存；同理，释放结构体内存时，运行时也不会自动释放结构体内指针字段所指向的内存。</p>
<p>&nbsp;&nbsp;&nbsp; 【避免malloc和free的额外开销】<br />
	&nbsp;&nbsp;&nbsp;&nbsp; malloc和free多次重复调用时，会给程序带来额外的开销。一个解决方法就是自己维护一份已分配的结构。需要时，从这个池里取出一份，释放时，直接 返回给池中。如果没有可用的结构时，才考虑新创建一个。</p>
<p><i>2、使用指针支持数据结构</i></p>
<p>无论是简单还是复杂的数据结构，指针都提供了更加灵活的支持，包括链表、队列、栈以及树等。</p>
<p><b>第七章&nbsp;安全问题以及不当使用指针</b><br />
	&nbsp;&nbsp;&nbsp;<br />
	深入理解指针以及其正确的使用方法有利于开发出安全可信赖的应用。</p>
<p>OS引入了一些提升安全的技术，比如 Address Space Layout Randomization和Data Execution Prevention。</p>
<p>【Address Space Layout Randomization (ASLR) ，地址空间布局随机化】<br />
	&nbsp; ASLR技术使得程序的数据区域随机布局，数据区域包括：代码、栈、堆。随机的放置这些区域让代码攻击行为很难精确预测特定代码的内存地址并使用它们。</p>
<p>【Data Execution Prevention(DEP)，数据执行保护】<br />
	&nbsp; DEP技术会阻止执行非执行数据区域中的代码。在一些攻击中，一些非执行数据区域中的数据被恶意覆写为代码，执行权也被转移到那里。但有了DEP后，这些 恶意代码将无法执行。</p>
<p><i>1、指针声明与初始化</i></p>
<p>&nbsp;&nbsp; 【不正确的指针声明】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New"> int* ptr1, ptr2;</font><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ptr1是指针，但ptr2只是一个整型变量。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 正确声明方法：<font face="Courier New">int *ptr1, *ptr2; /* 更好的做法是每行仅声明一个变量 */</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 下面做法存在同样的问题：<br />
	<font face="Courier New">&nbsp;&nbsp; #define PINT int*<br />
	&nbsp;&nbsp; PINT ptr1, ptr2;</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用typedef就没有问题了：<br />
	<font face="Courier New">&nbsp;&nbsp; typedef int* PINT;<br />
	&nbsp;&nbsp; PINT ptr1, ptr2;</font></p>
<p>&nbsp;&nbsp; 【使用指针前未初始化】<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 使用前未做初始化的指针，常称作野指针（wild pointer)：</p>
<p><font face="Courier New">&nbsp;&nbsp; int *pi;<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp; printf(&ldquo;%d\n&rdquo;,*pi);</font></p>
<p>&nbsp;&nbsp;&nbsp; 【处理未初始化的指针】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指针脸上没有写自己是否做过初始化^_^。通常有三种方法用于对付未初始化的指针：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 总是将指针初始化为NULL；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 使用assert函数<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 使用第三方工具<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	<i>2</i><i>、指针使用问题</i><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 缓冲区溢出(Buffer overflow)可能由以下原因导致：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 访问数组元素的时候没有检查下标值<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 做数组指针相关运算时不够谨慎<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 用gets之类的函数从标准输入读取字符串<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 使用strcpy和strcat不当</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【测试NULL】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 调用malloc后，总是检查返回值是否为NULL。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【误用解引用操作符】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int num;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *pi;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *pi = &amp;num</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【悬挂指针】</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【访问数组越界】</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char firstName[8] = &quot;1234567&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char middleName[8] = &quot;1234567&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char lastName[8] = &quot;1234567&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; middleName[-2] = &#39;X&#39;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; middleName[0] = &#39;X&#39;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; middleName[10] = &#39;X&#39;;</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【错误计算数组大小】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当将数组作为参数传递给函数时，务必将函数的Size一并传入，这个Size信息将避免数组访问越界。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 【误用sizeof操作符】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int buffer[20];<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *pbuffer = buffer;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0; i&lt;sizeof(buffer); i++) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *(pbuffer++) = 0;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; sizeof(buffer)=&gt;sizeof(buffer)/sizeof(buffer[0]);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【总是匹配指针类型】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【有界指针(bounded pointer)】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【字符串安全问题】<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 对strcpy和strcat使用不当，会导致缓冲区溢出。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在C11标准中加入了strcat_s和strcpy_s函数，如果发生缓冲区溢出，它们会返回错误。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【函数指针问题】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不要将函数赋值给签名不同的函数指针，这很可能将导致未定义行为发生。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	<i>3、内存释放问题</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【两次free】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【清除敏感数据】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一个良好的实践是覆写哪些不再需要的敏感数据。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *name = (char*)malloc(&#8230;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(name,0,sizeof(name));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(name);</font></p>
<p><i>4、使用静态分析工具</i></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 比如Gcc -Wall等。</p>
<p><b>第八章 &nbsp;其他零碎的知识点</b></p>
<p><i>1、指针转型</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指针转型有几个原因：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 访问特定目的的地址<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 分配一个地址代表一个端口<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 决定机器的endianess</p>
<p>&nbsp;&nbsp;&nbsp; 【访问特定的地址】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #define VIDEO_BASE 0xB8000<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *video = (int *) VIDEO_BASE;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *video = &#39;A&#39;;</font></p>
<p>&nbsp;&nbsp;&nbsp; 【访问一个端口】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #define PORT 0xB0000000<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int volatile * const port = (unsigned int *) PORT;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *port = 0x0BF4; // write to the port<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value = *port; // read from the port</font></p>
<p>&nbsp;&nbsp;&nbsp; 【判断机器的endianess】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int num = 0&#215;12345678;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char* pc = (char*) &amp;num;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; 4; i++) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%p: %02x \n&quot;, pc, (unsigned char) *pc++);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><i>2、Aliasing、Strict Aliasing和restrict关键字</i></p>
<p>两个指针同时指向一块相同的内存地址，这两个指针被称为aliasing。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp; int num = 5;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; int* p1 = &amp;num;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; int* p2 = &amp;num;</font></p>
<p>aliasing的使用对编译器生成的代码强加了限制。<br />
	如果两个指针引用相同位置，每个指针都可以修改这块地址。当编译器生成读写这块内存的代码时，不总是可以通过将值存储在寄存器中这种办法来优化代 码。对每次引用，将强制使用机器级别的低效load和store操作。</p>
<p>Strict Aliasing：另外一种形式的aliasing。strict aliasing不允许不同类型的指针指向同一块内存区域。下面代码：一个指向整型的指针alias了一个指向float类型的指针了，这违反了Strict Aliasing的规则。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; float number = 3.25f;<br />
	&nbsp;&nbsp;&nbsp; unsigned int *ptrValue = (unsigned int *)&amp;number;<br />
	&nbsp;&nbsp;&nbsp; unsigned int result = (*ptrValue &amp; 0&#215;80000000) == 0;</font></p>
<p>如果仅仅是符号标志和修饰符不同，是不会影响strict aliasing的，下面的语句是符合Strict aliasing规则的：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; int num;<br />
	&nbsp;&nbsp;&nbsp; const int *ptr1 = &amp;num;<br />
	&nbsp;&nbsp;&nbsp; int *ptr2 = &amp;num;<br />
	&nbsp;&nbsp;&nbsp; int volatile ptr3 = &amp;num;</font></p>
<p>有些场合，相同数据的不同表示是很有用处的，下面一些方法可以避免与Strict aliasing规则冲突：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 使用Union: 多个数据类型的联合体可以规避strict aliasing<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; 关闭strict aliasing ：利用编译器提供的开关将strict aliasing关闭（不建议这么做哦），<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 比如Gcc提供的一些开关：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -fno-strict-aliasing 关闭strict aliasing<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -fstrict-aliasing 打开strict aliasing<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -Wstrict-aliasing 针对strict aliasing相关问题给出警告</font><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 使用char pointer：char pointer可以alias任何对象。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【使用Union实现一个值的多种方式表示】<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; typedef union _conversion {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; float fNum;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &nbsp;&nbsp; unsigned int uiNum;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } Conversion;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; int isPositive1(float number) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &nbsp;&nbsp; Conversion conversion = { .fNum =number};<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &nbsp;&nbsp; return (conversion.uiNum &amp; 0&#215;80000000) == 0;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 由于没有指针，所以不存在违反Strict aliasing的问题。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【Strict Aliasing】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 编译器假设多个不同类型的指针不会引用到同一个数据对象，这样在strict aliasing的规则下，编译器才能够实施一些优化。如果假设不成立，那很可能发生意料之外的结果。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 即使是两个拥有相同字段，但名字不同的结构体，其对应的指针也不能引用同一个对象。但通过typedef结构体类型指针与原类型指针可以引用同一个数据对象。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; typedef struct _person {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; char* firstName;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; char* lastName;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; unsigned int age;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } Person;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; typedef Person Employee;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Person* person;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Employee* employee;</font></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【使用restrict关键字】<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用restrict关键字，意即告诉编译器这个指针没有被alias，这样编译器将可以进行优化，生成更为高效的代码。通常的优化方法是缓存这个指针。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不过即便使用了restrict关键字，对编译器来说也只是一个建议，编译器可自行选择是否进行优化。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 建议新代码中都要使用restrict关键字。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void add(int size, double * restrict arr1, const double * restrict arr2) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; size; i++) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; arr1[i] += arr2[i];<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; double vector1[] = {1.1, 2.2, 3.3, 4.4};<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; double vector2[] = {1.1, 2.2, 3.3, 4.4};<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; add(4,vector1,vector2);</font><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 以上是add函数的正确用法。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; double vector1[] = {1.1, 2.2, 3.3, 4.4};<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; double *vector3 = vector1;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; add(4,vector1,vector3);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; add(4,vector1,vector1);</font></p>
<p>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 这个例子中vector3与vector1指向同一份数据，也许add可以正常工作，但这个函数的调用结果并不那么可靠。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 标准C库中有多个函数使用了restrict关键字，比如void *memcpy(void * restrict s1, const void * restrict s2, size_t n)等。</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/28/understanding-and-using-c-pointers-keypoint-preview/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
