PHP foreach 中使用引用的注意事项

问题

先看一个例子:

<?php
$ar = array(1, 2, 3);
var_dump($ar);
foreach ($ar as &$v) {}
foreach ($ar as $v) {}
var_dump($ar);
?>

输出为:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  &int(2)
}

???为什么没有进行赋值操作,数组最后一个元素的值却发生了改变呢?

我早就发现了这个问题,一开始以为是 PHP 的 bug,就扔着没管它, foreach 中不使用引用就没事, 用 foreach $k => $v 然后 $ar[$k] 来改变原始数组, 略微损失点效率。

分析

今天花了点时间,看了 参考 中的文章, 算是稍微明白一点了,原来是这个样子的:

在执行第一个使用引用的 foreach 时, 一开始, $v 指向 $ar[0] 的存储空间,空间内存储着 1 , foreach 结束时, $v 指向 $ar[2] 的存储空间,空间内存储着 3 。 下面要开始执行第二个 foreach 了,注意和第一个 foreach 不同, 第二个 foreach 没有使用引用,那么就是赋值方式, 即将 $ar 的值依次 赋值$v 。 进行到第一个元素时,要将 $ar[0] 赋值给 $v 。 问题就在这里,由于刚刚执行完第一个 foreach, $v 不是一个新变量,而是已经存在的、指向 $ar[2] 的那个 引用 , 如此一来,对 $v 进行赋值的时候,就将 $ar[0] = 1 写入了 $ar[2] 的实际存储空间, 相当于对 $ar[2] 进行赋值。 依此类推,第二个 foreach 执行的结果, 就是数组的最后一个元素变成了倒数第二个元素的值。 参考文章 2 中有详细的示意图。

如果说这是一个错误,那么错误的原因就在于对引用变量的使用。 当引用变量指向和其他变量时,改变引用变量的值当然会影响到他指向的其他变量。 单独说谁都明白,但在这个 foreach 例子中,凑巧了, 同一个变量两次被使用,前一次是引用的身份,后一次是普通变量身份, 就产生了意料之外的效果。 PHP 的开发者也认为,这种情况属于语言特性造成的,不是 bug。 的确,如果要修复这个问题,一种方法是对 foreach 进行特殊处理之外, 另外一种就是限制 foreach 中 $v 的作用域, 这两种方式都与目前 PHP 的语言特性不符,开发人员不愿改, 但还是在 官方文档 中用 Warning 进行了说明。

解决方法

简单,但谈不上完美,就是在使用了引用的 foreach 之后, unset 掉 $v , 开始的例子改为:

<?php
$ar = array(1, 2, 3);
var_dump($ar);
foreach ($ar as &$v) {}
unset($v);
foreach ($ar as $v) {}
var_dump($ar);
?>

运行结果:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}

Mysql升级到5.1后库升级失败的问题

一台 mysql 5.0 服务器,升级到 5.1 后,发现原来有个 database 名字变成了 #mysql50#t-2008-zbb ,刚开始没在意想直接 RENAME DATABASE ,结果这个语法由于过渡危险已经取消了,改用ALTER DATABASE db_name UPGRADE DATA DIRECTORY NAME,结果执行错误:

mysql> ALTER DATABASE `#mysql50#db_name` UPGRADE DATA DIRECTORY NAME;
ERROR 1450 (HY000): Changing schema from '#mysql50#db_name' to 'db_name' is not allowed.

原来这里面还有个 BUG ,刚刚修正过来,发行版中肯定还没有呢。幸好,从中得到了提示,因为 View 的存在导致库无法升级的,删掉所有视图后 UPGRADE 成功:

mysql> ALTER DATABASE `#mysql50#db_name` UPGRADE DATA DIRECTORY NAME;
Query OK, 0 rows affected (0.08 sec)

这台服务器还作了双向同步,我还得手工重置同步状态,又是麻烦一连串儿的事情,幸亏这次操作的是测试服务器,下次升级正式服务器之前,记得先把所有 View 删掉,升级完成后再重新创建。

另外 RENAME DATABASE 实在是危险,我执行过程中出错终止了,结果一部分表在新库里、一部分表在旧库中,不小心把未转完的目标库删掉了(不然后面的正常 ALTER DATABASE 无法继续),结果就丢失了这些表的数据。

/bin/sh: root: not found

root用户经常收到这样的错误邮件:

From: Cron Daemon <root>
To: root
Subject: Cron <root@fwolf> root /home/fwolf/bin/cmd_run_in_crontab
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin>
X-Cron-Env: <HOME=/root>
X-Cron-Env: <LOGNAME=root>

/bin/sh: root: not found

这个问题很简单,但也容易让人糊涂,先说原因吧,/etc/crontab中格式是这样的:

17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly

root# crontab -e出来的应该是这样:

17 *    * * *   cd / && run-parts --report /etc/cron.hourly

注意,/etc/crontab是针对整个系统的,所以第6列是用户名,root# crontab -e是root用户的,所以第6列就直接是命令了。

这有什么好糊涂的呢,因为这两个文件很可能会一模一样,我就是这样,后来才发现他们的不同,不信用root# crontab -e修改些内容再和/etc/crontab对比就知道了。

现在,/bin/sh: root: not found这个错误不用我再解释了吧?直接拷贝crontab到root的后遗症。

vncserver找不到字体问题的解决

这个好像也是升级到edgy后出的问题,以前都是好好的,现在启动vncserver后连接不上:

VNC viewer version 3.3.7 – built Jul 4 2006 10:04:48 Copyright (C) 2002-2003 RealVNC Ltd. Copyright (C) 1994-2000 AT&T Laboratories Cambridge. See http://www.realvnc.com for information on VNC. vncviewer: ConnectToTcpAddr: connect: Connection refused Unable to connect to VNC server

到服务器上一看,端口根本就没有监听,查看vncserver log发现有如下错误:

Wed Feb 21 00:25:46 2007 vncext: VNC extension running! vncext: Listening for VNC connections on port 5901 vncext: created VNC server for screen 0 error opening security policy file /etc/X11/xserver/SecurityPolicy Could not init font path element /usr/share/X11/fonts/TTF/, removing from list! Could not init font path element /usr/share/X11/fonts/OTF, removing from list! Could not init font path element /usr/share/X11/fonts/CID/, removing from list! Fatal server error: could not open default font ‘fixed’ xsetroot: unable to open display ‘fwolf:1’ xterm Xt error: Can’t open display: fwolf:1 vncconfig: unable to open display “fwolf:1” twm: unable to open display “fwolf:1”

查询这里这里才知道,是由于vncserver找不到字体所以就退出了。

解决的办法嘛,这里推荐的使用命令“vncserver -fp /usr/share/fonts/X11/misc”启动有些太繁琐了;这里对/usr/bin/vncserver脚本的修改似乎又麻烦了一些,并且经过我的实验还不成功;而这里推荐的ln目录的方法更不可取了,/usr/share/X11/fonts/misc目录下还有别的文件呢。所以,反正我也要修改vncserver监听端口,所以干脆再在/usr/bin/vncserver上动个小手脚得了:

在/usr/bin/vncserver大约157行的地方:

# Add font path and color database stuff here, e.g.: # # $cmd .= ” -fp /usr/lib/X11/fonts/misc/,/usr/lib/X11/fonts/75dpi/”; # $cmd .= ” -co /usr/lib/X11/rgb”;

我们可以安装这里的例子,定制自己所需要的cmd参数,写在这里,和vncserver -fp …命令的性质是一样的,比如我们加上一行:

$cmd .= ” -fp /usr/share/fonts/X11/misc/”;

注意是“.=”而不是“=”,还有最后的分号“;”不要忘记了,现在再启动vncserver就ok啦。

ubuntu edgy下的vncserver

连接上vncviewer后发现,这次vnc升级之后还是有改进的,我没有修改过xstartup,所以一直使用的是默认的X界面,以前只能打开一个窗口,想多任务的话还不行(图形界面),现在好了,虽然窗口丑陋了一些,但是可以缩为一个图标,还能夠调整大小(虽然不太灵光)、位置,比以前还是有进步的。看右边我的截图,左边那个图标就是毒蛙azureus,颜色没转过来,不太像吧,呵呵。

Azureus一个非常讨厌的缺点

Azureus大概是linux下最好用的bt客户端了,虽然不敢和windows下的bitcomet等软件相媲美,但从下载管理到编码兼容性等方面综合来说,还算“堪用”。但是今天遇到了Azureus一个小缺点,不算致命,却极度讨厌和烦人。

大概起因是从设置开始的,我的Azureus设置了不备份torrent文件,于是所有的种子文件都保存在了/tmp目录下,直接被Azureus引用。而我们都知道,linux的/tmp只要重启就会全部清空,于是,当我重启之后再次启用Azureus时,发现它停留在了splash画面,一直在加载种子文件,要过很长时间,才会知道实在找不到这个种子文件了,跳过去下一个。可怜我有近20个种子文件啊,出去吃了顿饭回来,发现主界面终于能够显示出来了,但依然无法响应用户操作,等待时间遥遥无期,最后只能把~/.azureus/downloads.config和.bak文件删除了事,所有要下载的种子重新添加,这次我选择备份到某地,免得放在/tmp下重启又没了。

不知道别人有没有遇到这样的问题,我想不是每个人都能像我这样几天不关机吧。。。?

Update @ 2008-04-28

现在换mldonkey了,虽然速度不如azureus快,管理也有不方便的地方,但是,稳定压倒一切啊。

鏖战一天:一个该死的木马

昨天晚睡、今天晚起、上班迟到。。。更倒霉的是,单位还要开全体会,在路上的时候,就已经接到电话,不过并沒有埋怨我迟到,而是非常着急的告诉我,投影仪不工作了,跑步前进!

到地方一看,狂昏啊,投影仪的信号线根本就沒有接到分频器上嘛,问题立刻解决,时候他们告诉我一帮人来回鼓捣了半天都没弄好,怎么都不知道检查一下最简单的线缆连接情况呢?

投影仪好了,用来播放学习资料光盘的电脑却出问题了,cpu使用率100%,加上windows的媒体播放机又是个很吃资源的家伙,没事还把窗口标题什么的都隐藏了,在cpu耗尽的情况下急难控制。看到这种情况估计就是中病毒了,果然,AVG和诺顿一个劲的弹框,AVG报的是一个Torjan,诺顿报的是ie临时文件中有一个病毒。一时半会儿收拾不好,先用超级兔子清理一下系统,然后凑合先放着电影。

在将就完成电影播放任务之后,我仔细的观察了一下这个木马,发现他很有意思。祭出ProcessExplorer查看进程,杀掉日常熟悉的可关闭进程之后,发现两点可疑之处,一是一个svchost进程下面挂着很多iexplore进程,另一个是AVG总是报一个netcfgw.exe有病毒,却始终找不到这个文件,进程中也没有。

先从可以的iexplore进程入手,起先以为是ie本身出问题了,用超级兔子把ie洗了个一干二净,但还是老样子,用cmd把iexplore.exe文件删除,几秒钟种之后他又恢复了,更改文件属性和各种覆盖方式都不行,一会儿他就被改写回去了,遂放弃,编写了一个简单的死循环批处理文件,反复用pk.exe杀死IEXPLORE进程。

再来查找系统中加载的无用程序和dll文件,系统自带的最好用的算是“系统信息”了,不过翻出来的东西太乱,也没有发现什么netcfgw.exe,只是发现有一个netcfgw.dll很可疑,但是却找不到文件,google一下,有人说这是一个盗qq密码的木马,那按说应该很容易杀除啊,可我这个怎么这么顽固?过程中还发现系统进程中,有两个可疑的svchost进程,一个就是刚才说的,作为iexplore父进程出现的那个(强制结束会导致windows在1分钟内重启,和当年冲击波一样),另外一个出现在进程列表的最后,可以强制杀死,但一会儿又复活了,隐约觉得系统中还是有一个东西在隐藏运行着,但用ProcessExplorer再也发现不了可疑进程了。

这个木马还有一个厉害的地方,在使用ad-aware和诺顿nav进行全面扫描的时候,居然会中途停止,状态其实并没有停止,只是停留在扫描一个文件的地方很长时间,寒。。。这个厉害的家伙。

没办法,既然是木马,那之后找专门的杀木马来吧,由于不知道木马的名字,也可能是变种变得太厉害了,或者只是某人经过特殊改装的木马,所以没法找专杀工具,只能使用通用的工具了。下载和使用几个对付木马的软件情况如下:

  • 木马克星,不好用,甚至在安装和启动软件的时候,会被系统背后隐藏的黑手强制关闭。
  • 木马杀客,没有名字那么厉害,什么都没有找到。
  • Torjan Remover 6.5.2,什么都没有找到。
  • TorjanHunter,下载的还是个注册版,界面不错,挺好用,简单扫描什么都没有发现,全面扫描后发现c:\windows\system32\netcfgw.dll文件是一个Torjan.Spy….木马。

通过实际演练,也大体知道这些防木马软件功力如何了。其实在漫长的查找木马过程中,我也发现刚才为什么找不到c:\windows\system32\netcfgw.dll文件了,因为他是一个系统+隐藏+只读属性的文件,用dir /a能看到,但是用del netcfgw.dll却会提示找不到文件。

同时,还发现了一个好用的软件:Sreng,比超级兔子功能要少,但使用上更加简单,用他发现一个可疑的启动注册表项:HKLM\SOFTWARE\Microsoft\Windows\Current Version\Explore\ShellExecuteHooks下面有一项内容为“c:\windows\system32\sysldr.dll”。

用TorjanHunter发现netcfgw.dll之后,显示的是能杀掉,但过会儿再去看,文件其实还在那里(可能是像iexplore.exe一样又被复制过来了),但结合Sreng发现的sysldr.dll基本上搞清楚这个木马了。

首先木马的本体是netcfgw.dll文件,运行的时候会变身为netcfgw.exe,也就是AVG发现的那个,而sysldr.dll是用来加载netcfgw的,使用钩子技术挂载在肯定会运行的explorer.exe中,同时还导致很多软件运行的时候也带着他。至于svchost和iexplore本身可能并没有感染,木马制造者只是用他们来实现访问某个网站的目的而已。

现在,杀木马就简单了,打开ProcessExplorer和一个cmd窗口,然后attrib -h -s -r netcfgw.dll & del netcfgw.dll干掉netcfgw,再在ProcessExplorer中使用搜索dll或handle功能,查找包含有sysldr.dll的进程,结果找到explorer.exe和TorjanHunter以及AVG的运行文件(再寒。。。),全部杀掉,然后同样的方法:attrib -h -s -r sysldr.dll & del sysldr.dll将其干掉,两个dll文件都位于c:\windows\system32下。重启之后,没有发现问题,木马清除完毕。

着实忙活了一天,并且也没有顾上截图,表示遗憾先。事后感觉自己已经一段时间没有用windows了,多少有些生疏,但同事、朋友们的电脑难免遭殃,自己去帮忙的时候还是要从知识上和工具上做好储备才行,现在把好用的工具罗列如下:

  • 杀毒防毒:还是信任诺顿,比较放心,升级也方便,还不用注册。注意一定要用企业版,家庭版和SystemWorks是垃圾。
  • ie修复和系统启动项:简单的可以用Sreng,复杂一点可以用超级兔子,想再彻底一点的可以用ad-aware,几个工具各有所长,可以互补。
  • 有了上两条的东西,日常使用小心些,直接拨号上网的一定要开防火墙,推荐Zone-Alarm,基本不会有大问题。
  • 查杀木马:还是用TorjanHunter吧,网上注册版很多,最近还有人推荐ewido,自己也用过一次,查杀效果还可以,但好像注册麻烦些。

上面说的几个都是被动的,主要用于出事之后,其实在日常使用的时候注意一些,这些都可以避免,下面是我经常为同事和朋友们推荐的方法,大家参考一下:

把ie的安全级别调整为最高,必须用ie的工作软件添加到信任网站中使用,一般的网站全部使用firefox浏览,这样既不影响工作和娱乐,安全性也提高了很多。

实在对firefox不感兴趣的,也可以用opera,再退一步,可以使用ie内核的greenbrowsermaxthon,但不管怎样,千万不要用原版的ie,这东西太脆弱了,简直就是万恶之源啊。

以上只针对xp,windows2003和vista几乎没用过,听说在安全性上有所改进,但不知实际效果如何。但不管怎么改,windows也是先把所有权限都给用户,再进行限制,和nix先限制用户的权限,再一点点开放的方式相比,安全性从底层机制上就有更大的漏洞,再加上没有计算机知识、毫无防范意识的用户来使用,简直就像抢劫3岁小孩那么简单。