<?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>Rainux's Journal &#187; Programming 编程 Archives  &laquo; Rainux&#039;s Journal</title>
	<atom:link href="http://rainux.org/category/programming/feed/" rel="self" type="application/rss+xml" />
	<link>http://rainux.org</link>
	<description>Rubyist of Vimmer</description>
	<lastBuildDate>Sat, 23 Jul 2011 12:09:18 +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>使用 Function.apply.apply() 为 IE 里的原生函数添加 .apply() 方法</title>
		<link>http://rainux.org/%e4%bd%bf%e7%94%a8-function-apply-apply-%e4%b8%ba-ie-%e9%87%8c%e7%9a%84%e5%8e%9f%e7%94%9f%e5%87%bd%e6%95%b0%e6%b7%bb%e5%8a%a0-apply-%e6%96%b9%e6%b3%95</link>
		<comments>http://rainux.org/%e4%bd%bf%e7%94%a8-function-apply-apply-%e4%b8%ba-ie-%e9%87%8c%e7%9a%84%e5%8e%9f%e7%94%9f%e5%87%bd%e6%95%b0%e6%b7%bb%e5%8a%a0-apply-%e6%96%b9%e6%b3%95#comments</comments>
		<pubDate>Sun, 13 Dec 2009 09:14:20 +0000</pubDate>
		<dc:creator>Rainux</dc:creator>
				<category><![CDATA[Programming 编程]]></category>
		<category><![CDATA[Web Development 织网]]></category>
		<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://rainux.org/?p=246</guid>
		<description><![CDATA[IE 里有很多原生的 Javascript 函数实际上都不是一个标准的 Function 对象，例如 window.alert，window.setTimeout 以及 IE8 的 window.console.log 等。在需要对这样的函数进行包装的时候，会因为它们都没有 Function 对象应该有的 .apply() 及 .call() 方法而难以做到。 这段代码为了兼容旧版本没有 console 的浏览器，尝试将 console.log 包装为 $.log，但是基于上述原因它在 IE 里无法执行。 $ = {}; if (console &#38;&#38; console.log) { $.log = function() { console.log.apply(console, arguments); }; } else { $.log = function() {}; } 由于 window 和 console 这样的原生对象都是只读的，所以无法使用类似这样的代码简单地为其添加 .apply() [...]]]></description>
			<content:encoded><![CDATA[<p>IE 里有很多原生的 Javascript 函数实际上都不是一个标准的 Function 对象，例如 window.alert，window.setTimeout 以及 IE8 的 window.console.log 等。在需要对这样的函数进行包装的时候，会因为它们都没有 Function 对象应该有的 .apply() 及 .call() 方法而难以做到。</p>

<p>这段代码为了兼容旧版本没有 console 的浏览器，尝试将 console.log 包装为 $.log，但是基于上述原因它在 IE 里无法执行。</p>

<pre name="code" class="javascript">$ = {};

if (console &amp;&amp; console.log) {
    $.log = function() {
        console.log.apply(console, arguments);
    };
} else {
    $.log = function() {};
}
</pre>

<p>由于 window 和 console 这样的原生对象都是只读的，所以无法使用类似这样的代码简单地为其添加 .apply() 方法。</p>

<pre name="code" class="javascript">console.log.apply = Function.apply;
</pre>

<p>解决办法是使用 Function.apply.apply() 将 Function.apply 函数对象 apply 到 console.log 上。</p>

<pre name="code" class="javascript">$ = {};

if (console &amp;&amp; console.log) {
    $.log = function() {
        Function.apply.apply(console.log, [console, arguments]);
    };
} else {
    $.log = function() {};
}
</pre>

<p>这段代码和第一段代码完全等价。</p>

<p>如果觉得不容易理解，可以看看这个 .apply() 函数比较清晰的例子。这两行代码也完全等价。</p>

<pre name="code" class="javascript">[1, 2, 3, 4].slice(0, 2);
Array.prototype.slice.apply([1, 2, 3, 4], [0, 2]);
</pre>

	标签：<a href="http://rainux.org/tag/javascript/" title="Javascript" rel="tag">Javascript</a><br />
]]></content:encoded>
			<wfw:commentRss>http://rainux.org/%e4%bd%bf%e7%94%a8-function-apply-apply-%e4%b8%ba-ie-%e9%87%8c%e7%9a%84%e5%8e%9f%e7%94%9f%e5%87%bd%e6%95%b0%e6%b7%bb%e5%8a%a0-apply-%e6%96%b9%e6%b3%95/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Exerb——将 Ruby 脚本转换成 Windows 下的可执行(.exe)文件！</title>
		<link>http://rainux.org/exerb%e2%80%94%e2%80%94%e5%b0%86-ruby-%e8%84%9a%e6%9c%ac%e8%bd%ac%e6%8d%a2%e6%88%90-windows-%e4%b8%8b%e7%9a%84%e5%8f%af%e6%89%a7%e8%a1%8cexe%e6%96%87%e4%bb%b6%ef%bc%81</link>
		<comments>http://rainux.org/exerb%e2%80%94%e2%80%94%e5%b0%86-ruby-%e8%84%9a%e6%9c%ac%e8%bd%ac%e6%8d%a2%e6%88%90-windows-%e4%b8%8b%e7%9a%84%e5%8f%af%e6%89%a7%e8%a1%8cexe%e6%96%87%e4%bb%b6%ef%bc%81#comments</comments>
		<pubDate>Thu, 04 Oct 2007 10:32:07 +0000</pubDate>
		<dc:creator>Rainux</dc:creator>
				<category><![CDATA[Programming 编程]]></category>
		<category><![CDATA[Programming_编程]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby GUI]]></category>

		<guid isPermaLink="false">http://www.rainux.org/2007/10/04/180</guid>
		<description><![CDATA[自从关注并使用 Ruby on Rails 以来，也对 Ruby 语言本身很感兴趣，并经常尝试用它写一些日常使用的小工具脚本。虽然自己使用很方便，但要分享给不用 Ruby 的朋友就比较麻烦了。 但是在 JavaEye 的某个帖子里知道 Exerb 后，事情就变得简单了。Exerb 是一个可以把 Ruby 脚本以及其依赖的 Ruby 扩展库转换成 Windows 下的单个的可独立运行的可执行文件的工具（这么长的一句话读起来是不是很有想扁我的冲动？:p），到 Exerb Project 的主页 下载安装包解开并执行里面的 setup.rb 即可完成安装。 用法也很简单，先执行 mkexy your_script.rb，让 mkexy 启动你的脚本并运行，这期间它会自动探测出 your_script.rb 所依赖的类库文件以及扩展库文件，并在 your_script.rb 结束运行后创建一份清单 your_script.exy，然后执行命令 exerb your_script.exy 即可生成 your_script.exe。OK，现在可以轻松地把你的 Ruby 脚本分享给朋友们了。(注1) 看到这里，也许你会跟我一样想，哇哈哈哈，有了这个工具，用 Ruby 来做 GUI 的小工具就变得有意义了啊。没错，对依赖 fxruby、wxruby 的 Ruby GUI 脚本也能正常的使用 Exerb 完成转换 [...]]]></description>
			<content:encoded><![CDATA[<p>自从关注并使用 Ruby on Rails 以来，也对 Ruby 语言本身很感兴趣，并经常尝试用它写一些日常使用的小工具脚本。虽然自己使用很方便，但要分享给不用 Ruby 的朋友就比较麻烦了。</p>

<p>但是在 <a href="http://www.javaeye.com" rel="nofollow" >JavaEye</a> 的某个帖子里知道 Exerb 后，事情就变得简单了。Exerb 是一个可以把 Ruby 脚本以及其依赖的 Ruby 扩展库转换成 Windows 下的单个的可独立运行的可执行文件的工具（这么长的一句话读起来是不是很有想扁我的冲动？:p），到 <a href="http://exerb.sourceforge.jp/index.en.html" rel="nofollow" >Exerb Project 的主页</a> 下载安装包解开并执行里面的 setup.rb 即可完成安装。</p>

<p>用法也很简单，先执行 <code>mkexy your_script.rb</code>，让 mkexy 启动你的脚本并运行，这期间它会自动探测出 <code>your_script.rb</code> 所依赖的类库文件以及扩展库文件，并在 <code>your_script.rb</code> 结束运行后创建一份清单 <code>your_script.exy</code>，然后执行命令 <code>exerb your_script.exy</code> 即可生成 <code>your_script.exe</code>。OK，现在可以轻松地把你的 Ruby 脚本分享给朋友们了。(注1)</p>

<p>看到这里，也许你会跟我一样想，哇哈哈哈，有了这个工具，用 Ruby 来做 GUI 的小工具就变得有意义了啊。没错，对依赖 fxruby、wxruby 的 Ruby GUI 脚本也能正常的使用 Exerb 完成转换 (注2) (注3)。但是，由于 Exerb 会把 fxruby/wxruby 的运行库嵌入到 .exe 文件里，生成的 .exe 文件都非常大，fxruby 的在 9MB 左右，wxruby 的 11MB 左右。对于一个功能简单的小工具来说，这个程度的体积显然是很多人不能接受的。</p>

<p>事实上，已经有了一个 GUI toolkit 运行库很小，<a href="http://www.osk.3web.ne.jp/~nyasu/vruby/vrproject-e.html" rel="nofollow" >VisualuRuby</a>，虽然它是 Windows Only 的，虽然它现在在 Vista 上还有点小问题。不过这又是另外一个话题了，我会在下一篇 blog 里继续聊。</p>

<p>注1: 在这之前也许你需要用 upx 压缩一下生成的 .exe 文件，因为它确实比较大，一个最简单的 Hello world 也有 1.4MB 左右。</p>

<p>注2: Tk/Gtk/FLTK 我没有试过，对 Tk 不感兴趣，Gtk 在 Windows 下的运行库太过庞大，FLTK 则是因为找到的 ruby-fltk 在 Windows 下的二进制发行版依赖的 Ruby 版本太老，源代码发行版还没来得及尝试自己编译。</p>

<p>注3: 对于 GUI 脚本，使用 <code>exerb -c gui your_script.exy</code> 可以避免生成的可执行文件运行时带有 Windows 命令行窗口。</p>

	标签：<a href="http://rainux.org/tag/programming_%e7%bc%96%e7%a8%8b/" title="Programming_编程" rel="tag">Programming_编程</a>, <a href="http://rainux.org/tag/ruby/" title="Ruby" rel="tag">Ruby</a>, <a href="http://rainux.org/tag/ruby-gui/" title="Ruby GUI" rel="tag">Ruby GUI</a><br />
]]></content:encoded>
			<wfw:commentRss>http://rainux.org/exerb%e2%80%94%e2%80%94%e5%b0%86-ruby-%e8%84%9a%e6%9c%ac%e8%bd%ac%e6%8d%a2%e6%88%90-windows-%e4%b8%8b%e7%9a%84%e5%8f%af%e6%89%a7%e8%a1%8cexe%e6%96%87%e4%bb%b6%ef%bc%81/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用 Linux shell I/O 重定向简化小程序的日志记录</title>
		<link>http://rainux.org/%e4%bd%bf%e7%94%a8-linux-shell-io-%e9%87%8d%e5%ae%9a%e5%90%91%e7%ae%80%e5%8c%96%e5%b0%8f%e7%a8%8b%e5%ba%8f%e7%9a%84%e6%97%a5%e5%bf%97%e8%ae%b0%e5%bd%95</link>
		<comments>http://rainux.org/%e4%bd%bf%e7%94%a8-linux-shell-io-%e9%87%8d%e5%ae%9a%e5%90%91%e7%ae%80%e5%8c%96%e5%b0%8f%e7%a8%8b%e5%ba%8f%e7%9a%84%e6%97%a5%e5%bf%97%e8%ae%b0%e5%bd%95#comments</comments>
		<pubDate>Sun, 11 Mar 2007 19:04:46 +0000</pubDate>
		<dc:creator>Rainux</dc:creator>
				<category><![CDATA[GNU/Linux]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming 编程]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[Programming_编程]]></category>

		<guid isPermaLink="false">http://www.rainux.org/2007/03/12/146</guid>
		<description><![CDATA[一些自动执行特定任务的小脚本程序，如果希望能使程序的 stdout/stderr 输出到屏幕的同时也把他们记录到一个日志文件，可以使用 tee 命令结合 shell I/O 重定向来轻松完成: ./a_tiny_script 2&#62;&#38;1 &#124; tee the_log_contain_both_stdout_and_stderr.log 这个命令的精华在于 2>&#38;1，意为让 stderr 使用 stdout 的文件描述符，效果也就是将 stdout 和 stderr 内容合并，并且输出到 stdout 被定向的位置。在这里也就是管道中的 tee 命令的 stdin 上，然后 tee 将得到的输入同时显示在屏幕上和记录到日志文件里。爽吧？ 以前为了在 PHP 脚本里实现这样的功能竟然使用了 ob 系列函数 + 自定义的 output callback 函数，并且这样也只能做到同时显示和记录 stdout，真是晕到死。 标签：bash, GNU/Linux, PHP, Programming_编程]]></description>
			<content:encoded><![CDATA[<p>一些自动执行特定任务的小脚本程序，如果希望能使程序的 stdout/stderr 输出到屏幕的同时也把他们记录到一个日志文件，可以使用 tee 命令结合 shell I/O 重定向来轻松完成:</p>

<pre name="code" class="bash">./a_tiny_script 2&gt;&amp;1 | tee the_log_contain_both_stdout_and_stderr.log
</pre>

<p>这个命令的精华在于 2>&amp;1，意为让 stderr 使用 stdout 的文件描述符，效果也就是将 stdout 和 stderr 内容合并，并且输出到 stdout 被定向的位置。在这里也就是管道中的 tee 命令的 stdin 上，然后 tee 将得到的输入同时显示在屏幕上和记录到日志文件里。爽吧？</p>

<p>以前为了在 PHP 脚本里实现这样的功能竟然使用了 ob 系列函数 + 自定义的 output callback 函数，并且这样也只能做到同时显示和记录 stdout，真是晕到死。</p>

	标签：<a href="http://rainux.org/tag/bash/" title="bash" rel="tag">bash</a>, <a href="http://rainux.org/tag/gnu-linux/" title="GNU/Linux" rel="tag">GNU/Linux</a>, <a href="http://rainux.org/tag/php/" title="PHP" rel="tag">PHP</a>, <a href="http://rainux.org/tag/programming_%e7%bc%96%e7%a8%8b/" title="Programming_编程" rel="tag">Programming_编程</a><br />
]]></content:encoded>
			<wfw:commentRss>http://rainux.org/%e4%bd%bf%e7%94%a8-linux-shell-io-%e9%87%8d%e5%ae%9a%e5%90%91%e7%ae%80%e5%8c%96%e5%b0%8f%e7%a8%8b%e5%ba%8f%e7%9a%84%e6%97%a5%e5%bf%97%e8%ae%b0%e5%bd%95/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>发现一个 ACDSee 7.x 可能是因为硬编码造成的问题</title>
		<link>http://rainux.org/acdsee-7x</link>
		<comments>http://rainux.org/acdsee-7x#comments</comments>
		<pubDate>Sun, 16 Jan 2005 20:23:16 +0000</pubDate>
		<dc:creator>Rainux</dc:creator>
				<category><![CDATA[Programming 编程]]></category>
		<category><![CDATA[Software 软件]]></category>
		<category><![CDATA[Programming_编程]]></category>
		<category><![CDATA[Software_软件]]></category>

		<guid isPermaLink="false">http://rainux.sefans.com/blog/?p=65</guid>
		<description><![CDATA[大家都知道 ACDSee 有 Viewer 和 Browser 两种模式。一般来说我们在“我的电脑”或者其他文件管理器里双击一个图片文件时 ACDSee 会以 Viewer 模式启动，以便让我们查看双击的这张图片。此时如果我们双击 ACDSee 的显示图片的区域，会使其切换到 Browser 模式，以便让我们浏览刚才打开的图片所在文件夹下的所有图片。 问题就出在这个 Browser 模式上。 Windows 2000 以上的版本默认把某个用户的注册表文件，各种软件的配置和数据文件等等保存到以该用户的名字命名的个人文件夹里，这个文件夹通常通常位于安装 Windows 的分区的 Documents and Settings 文件夹下。Windows 以环境变量 %USERPROFILE% 来表示它。而我为了让 Windows 彻底崩溃后不用手动去挽救数据，通常会把很多本来位于 %USERPROFILE% 文件夹里的系统特殊文件夹重定义到其他地方。包括 Application Data、桌面等。 结果，在随手删除掉 %USERPROFILE% 里已经无意义的“桌面”文件夹 (因为我已经把它重定义到了其他位置) 后，ACDSee 的 Browser 模式无法正常工作了。表现为由 Viewer 模式切换到 Browser 模式后不会显示出所在文件夹的任何图片文件，或者直接启动 Browser 也不会显示大多数文件夹下的图片文件。 发现造成这个问题的原因浪费了我几天前的一个夜晚几个小时的时间，最后在网友急云的提示下才搞清楚。 或许这只是个很小的，微不足道的，大多数人不会遇到的问题，但至少它对我们软件开发者来说有特别的意义。因为这很可能是 ACDSee 的作者在一定程度上使用了硬编码 (hard [...]]]></description>
			<content:encoded><![CDATA[<p>大家都知道 ACDSee 有 Viewer 和 Browser 两种模式。一般来说我们在“我的电脑”或者其他文件管理器里双击一个图片文件时 ACDSee 会以 Viewer 模式启动，以便让我们查看双击的这张图片。此时如果我们双击 ACDSee 的显示图片的区域，会使其切换到 Browser 模式，以便让我们浏览刚才打开的图片所在文件夹下的所有图片。</p>

<p>问题就出在这个 Browser 模式上。
<span id="more-65"></span>
Windows 2000 以上的版本默认把某个用户的注册表文件，各种软件的配置和数据文件等等保存到以该用户的名字命名的个人文件夹里，这个文件夹通常通常位于安装 Windows 的分区的 Documents and Settings 文件夹下。Windows 以环境变量 %USERPROFILE% 来表示它。而我为了让 Windows 彻底崩溃后不用手动去挽救数据，通常会把很多本来位于 %USERPROFILE% 文件夹里的系统特殊文件夹重定义到其他地方。包括 Application Data、桌面等。</p>

<p>结果，在随手删除掉 %USERPROFILE% 里已经无意义的“桌面”文件夹 (因为我已经把它重定义到了其他位置) 后，ACDSee 的 Browser 模式无法正常工作了。表现为由 Viewer 模式切换到 Browser 模式后不会显示出所在文件夹的任何图片文件，或者直接启动 Browser 也不会显示大多数文件夹下的图片文件。</p>

<p>发现造成这个问题的原因浪费了我几天前的一个夜晚几个小时的时间，最后在网友急云的提示下才搞清楚。</p>

<p>或许这只是个很小的，微不足道的，大多数人不会遇到的问题，但至少它对我们软件开发者来说有特别的意义。因为这很可能是 ACDSee 的作者在一定程度上使用了硬编码 (hard code) 造成的。</p>

<p>很多时候，我们为了方便而使用硬编码。要说是在用于教学或者演示的 demo 里为了方便或者突出重点使用硬编码，那是无可厚非的。但如果由此让自己或者别人养成了使用硬编码的习惯，那就罪大恶极了。</p>

<p>其实，著名的由两位数表示年份造成的“千年虫”问题就是个典型的硬编码带来的灾难。</p>

	标签：<a href="http://rainux.org/tag/programming_%e7%bc%96%e7%a8%8b/" title="Programming_编程" rel="tag">Programming_编程</a>, <a href="http://rainux.org/tag/software_%e8%bd%af%e4%bb%b6/" title="Software_软件" rel="tag">Software_软件</a><br />
]]></content:encoded>
			<wfw:commentRss>http://rainux.org/acdsee-7x/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>收到 Borland China 寄来的 Delphi 2005 试用版光盘</title>
		<link>http://rainux.org/borland-china-delphi-2005</link>
		<comments>http://rainux.org/borland-china-delphi-2005#comments</comments>
		<pubDate>Mon, 27 Dec 2004 08:12:03 +0000</pubDate>
		<dc:creator>Rainux</dc:creator>
				<category><![CDATA[Delphi]]></category>
		<category><![CDATA[Programming 编程]]></category>
		<category><![CDATA[Programming_编程]]></category>

		<guid isPermaLink="false">http://rainux.sefans.com/blog/?p=37</guid>
		<description><![CDATA[虽然早用上了 Delphi 2005 Update 1 正式版，这张光盘已经没有实际意义，但还是挺兴奋的，毕竟是 Borland 寄给我的东西，呵呵。 平心而论 Delphi 2005 是个不错的产品，虽然我们可以找出一堆数落她的理由。比如启动太慢，占用内存太多，IDE 太像 Visual Studio 而失去了 Delphi 传统的风格，编辑器反应比较慢等等。不过在同一个 IDE 里同时支持 Delphi for Win32、Delphi for .NET 以及 C# 确实是个很诱人的卖点。以及新增的 Refactoring、UML Modeling 等等都是很实用很重要的功能。 Delphi 2005 是个不错的产品，但 Borland 对她的宣传确实有些过了。 标签：Delphi, Programming_编程]]></description>
			<content:encoded><![CDATA[<p>虽然早用上了 Delphi 2005 Update 1 正式版，这张光盘已经没有实际意义，但还是挺兴奋的，毕竟是 Borland 寄给我的东西，呵呵。</p>

<p>平心而论 Delphi 2005 是个不错的产品，虽然我们可以找出一堆数落她的理由。比如启动太慢，占用内存太多，IDE 太像 Visual Studio 而失去了 Delphi 传统的风格，编辑器反应比较慢等等。不过在同一个 IDE 里同时支持 Delphi for Win32、Delphi for .NET 以及 C# 确实是个很诱人的卖点。以及新增的 Refactoring、UML Modeling 等等都是很实用很重要的功能。</p>

<p>Delphi 2005 是个不错的产品，但 Borland 对她的宣传确实有些过了。</p>

	标签：<a href="http://rainux.org/tag/delphi/" title="Delphi" rel="tag">Delphi</a>, <a href="http://rainux.org/tag/programming_%e7%bc%96%e7%a8%8b/" title="Programming_编程" rel="tag">Programming_编程</a><br />
]]></content:encoded>
			<wfw:commentRss>http://rainux.org/borland-china-delphi-2005/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>关于 Delphi 参数传递方式的一点研究</title>
		<link>http://rainux.org/delphi</link>
		<comments>http://rainux.org/delphi#comments</comments>
		<pubDate>Thu, 29 Jul 2004 07:42:30 +0000</pubDate>
		<dc:creator>Rainux</dc:creator>
				<category><![CDATA[Delphi]]></category>
		<category><![CDATA[Programming 编程]]></category>
		<category><![CDATA[Programming_编程]]></category>

		<guid isPermaLink="false">http://rainux.sefans.com/blog/?p=7</guid>
		<description><![CDATA[某次看 D6DG 说默认的参数传递方式因为会为变量产生本地副本所以会消耗额外的内存，而 const 方式会优化字符串和记录类型的参数传递时的内存占用。从而猜测 const 方式的参数传递实际也是按地址传递，只是编译器强制不允许函数内的代码修改 const 方式传递进去的变量而已。 为了证实我的猜想，特设计以下实验，本实验中使用到了字符串(本文提到的字符串都是指 Delphi 默认的字符串 AnsiString)内部结构中的引用计数。 关于字符串的引用计数，结合 D6DG 中的说明和偶的实际研究，得出的结论是，字符串地址实际上是其内容的第一个字符的地址，在此地址之前的 12 字节内存中的内容才是字符串内部结构中的头部，分别是 32 位字符串占用的内容空间大小，32 位引用计数，32 位字符串长度(仅在 Delphi 7 中验证，推测 Delphi 7 以前的 32 位 Delphi 中的字符串结构都应该是这样，未验证)。 以下代码中 sGlobal 为全局字符串变量。 procedure Method1(S: string); var P: PInteger; begin // S := S + 'k'; // 字符串实际上是指针，这里只是将其强制转换为 PInteger 备用 P := PInteger(S); [...]]]></description>
			<content:encoded><![CDATA[<p>某次看 D6DG 说默认的参数传递方式因为会为变量产生本地副本所以会消耗额外的内存，而 const 方式会优化字符串和记录类型的参数传递时的内存占用。从而猜测 const 方式的参数传递实际也是按地址传递，只是编译器强制不允许函数内的代码修改 const 方式传递进去的变量而已。</p>

<p>为了证实我的猜想，特设计以下实验，本实验中使用到了字符串(本文提到的字符串都是指 Delphi 默认的字符串 AnsiString)内部结构中的引用计数。</p>

<p>关于字符串的引用计数，结合 D6DG 中的说明和偶的实际研究，得出的结论是，字符串地址实际上是其内容的第一个字符的地址，在此地址之前的 12 字节内存中的内容才是字符串内部结构中的头部，分别是 32 位字符串占用的内容空间大小，32 位引用计数，32 位字符串长度(仅在 Delphi 7 中验证，推测 Delphi 7 以前的 32 位 Delphi 中的字符串结构都应该是这样，未验证)。</p>

<p>以下代码中 sGlobal 为全局字符串变量。</p>

<pre name="code" class="delphi">procedure Method1(S: string);
var
  P: PInteger;
begin
  // S := S + 'k';
  // 字符串实际上是指针，这里只是将其强制转换为 PInteger 备用
  P := PInteger(S);
  // 得到字符串地址左偏移 8 字节的地址，也就是字符串的 32 位引用计数存储的位置
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('S 引用计数: ' + IntToStr(P^));
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('S 地址: ' + IntToStr(Integer(Pointer(S))));
  Form1.Memo1.Lines.Add('');
end;

procedure Method2(var S: string);
var
  P: PInteger;
begin
  P := PInteger(S);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('S 引用计数: ' + IntToStr(P^));
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('S 地址: ' + IntToStr(Integer(Pointer(S))));
  Form1.Memo1.Lines.Add('');
end;

procedure Method3(const S: string);
var
  P: PInteger;
begin
  P := PInteger(S);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('S 引用计数: ' + IntToStr(P^));
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('S 地址: ' + IntToStr(Integer(Pointer(S))));
  Form1.Memo1.Lines.Add('');
end;

procedure sGlobalInfo;
var
  P: PInteger;
begin
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('sGlobal 地址: ' + IntToStr(Integer(sGlobal)));
  Form1.Memo1.Lines.Add('');
end;
</pre>

<p>在主程序里分别以初始化后的 sGlobal 为实参调用这几个过程后，我们会看到三种方式 S 的地址都跟 sGlobal 的一样，这初步说明 var 方式和 const 方式确实都是按地址传递参数。</p>

<p>但不可思议的是，默认参数传递方式不会是为变量产生本地副本吗？为什么 S 的地址还会跟 sGlobal 一样呢？因为一开始我设计这个实验时并没有添加显示引用计数的代码，所以很是迷茫。之后想起了 Borland 使用了引用计数的方式和 copy-on-write 技术(对于字符串等使用这两个技术的数据类型的变量 A、B，将 A 赋值给 B 时实际上只是赋值 A 的地址给 B，并增加 A 的引用计数，直到两者其中一个被修改时才为 B 申请新的内存空间并且复制 A 的内容，同时减少 A 的引用计数)来优化字符串的操作。于是查阅 D6DG 并且观察 Delphi 的汇编代码和内存(在 CPU 窗口中)得出了本文开头关于字符串引用计数的结论。</p>

<p>这样，添加了显示引用计数的代码之后我们就可以观察到，使用默认方式时 S 和 sGlobal 的引用计数，都是 2，而其他方式时两者的引用计数都是 1，这更有力的说明了 const 方式确实是按地址传递，而不是像默认方式那样只是增加引用计数。这样，当我们把第一个过程中第一行代码的注释去掉后再次运行程序，可以看到默认方式时 S 的地址已经跟 sGlobal 不同了，同时两者的引用计数都是 1 了，说明确实是在 S 被修改后才产生本地副本(copy-on-write)。</p>

<p>那么，现在我们知道参数类型为字符串时， const 方式的参数传递实际是传递参数地址，这可以优化内存的使用，而参数为其他数据类型时是不是这样呢？</p>

<p>为此修改刚才的程序如下，其中 iGlobal 为全局整形变量。</p>

<pre name="code" class="delphi">procedure Method1(I: Integer);
begin
  Form1.Memo1.Lines.Add('I 地址: ' + IntToStr(Integer(@I)));
  Form1.Memo1.Lines.Add('');
end;

procedure Method2(var I: Integer);
begin
  Form1.Memo1.Lines.Add('I 地址: ' + IntToStr(Integer(@I)));
  Form1.Memo1.Lines.Add('');
end;

procedure Method3(const I: Integer);
begin
  Form1.Memo1.Lines.Add('I 地址: ' + IntToStr(Integer(@I)));
  Form1.Memo1.Lines.Add('');
end;

procedure iGlobalInfo;
begin
  Form1.Memo1.Lines.Add('iGlobal 地址: ' + IntToStr(Integer(@iGlobal)));
  Form1.Memo1.Lines.Add('');
end;
</pre>

<p>在主程序里分别以初始化后的 iGlobal 为实参调用这个过程后，可以看到只有 var 方式中 I 的地址跟 iGlobal 一样，而默认方式和 const 都会为 iGlobal 产生本地副本，可见确实如 D6DG 所说 const 会(也只会)优化字符串和记录类型的参数传递时的内存占用。</p>

<p>至此实验目的达到，还附带了解了 string 的内部格式。</p>

<p>附上<a href="/stuff/20040729.Delphi.01.rar" rel="nofollow" >实验程序源代码</a>。</p>

	标签：<a href="http://rainux.org/tag/delphi/" title="Delphi" rel="tag">Delphi</a>, <a href="http://rainux.org/tag/programming_%e7%bc%96%e7%a8%8b/" title="Programming_编程" rel="tag">Programming_编程</a><br />
]]></content:encoded>
			<wfw:commentRss>http://rainux.org/delphi/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

