gcc参数顺序

gcc参数顺序
Ordering of gcc parameters matters

2013年最后一个月最后一个星期谈一个奇怪的bug,我在尝试pinfo的时候,configure脚本会提示ncurses有问题:

checking location of curses.h file... /usr/include/ncurses.h
checking if curses is usable... no
configure: error: Curses not found. You need curses to compile pinfo

打开config.log文件,发现这个提示:

configure:12563: checking if curses is usable
configure:12587: gcc -o conftest -g -O2 -I/usr/include    -L/usr/lib -lncurses conftest.c  >&5
/tmp/ccBLkSqd.o: In function `main':
/net/fantasia/home/zhanxw/software/pinfo-0.6.10/conftest.c:38: undefined reference to `initscr'
/net/fantasia/home/zhanxw/software/pinfo-0.6.10/conftest.c:39: undefined reference to `printw'
/net/fantasia/home/zhanxw/software/pinfo-0.6.10/conftest.c:40: undefined reference to `stdscr'
/net/fantasia/home/zhanxw/software/pinfo-0.6.10/conftest.c:40: undefined reference to `wrefresh'
/net/fantasia/home/zhanxw/software/pinfo-0.6.10/conftest.c:41: undefined reference to `stdscr'
/net/fantasia/home/zhanxw/software/pinfo-0.6.10/conftest.c:41: undefined reference to `wgetch'
/net/fantasia/home/zhanxw/software/pinfo-0.6.10/conftest.c:42: undefined reference to `endwin'
collect2: ld returned 1 exit status

从这个命令行来看,curses库的头文件目录和库文件目录都对,但编译的时候(链接/tmp/ccBLkSqd.o)却找不到几个函数的定义(比如:initscr,printw等等)。为了重现这个看起来不该出现的问题,我把conftest.c创建好,然后用相同的gcc命令行编译代码,的确是可以重现错误。

更令我不解的是如果把conftest.c放到命令行的末尾,这个编译就能通过了:

# This does not work
gcc -o conftest -g -O2 -I/usr/include    -L/usr/lib -lncurses conftest.c 
# This works
gcc -o conftest conftest.c -g -O2 -I/usr/include    -L/usr/lib -lncurses  

为什么把conftest.c移到“-L/usr/lib -lncurses”前面就能解决问题?
看到StackOverFlow上的一个帖子

这个帖子讲到gcc编译和链接一个源程序时,会保留参数的顺序,也就是说在链接时,把conftest.c放到前面和后面,效果不同。具体来讲:

conftest.c 放到后面

gcc -o conftest -g -O2 -I/usr/include    -L/usr/lib -lncurses conftest.c 
<=> 等价于
gcc -o conftest -g -O2 -L/usr/lib -lncurses tmporary_object_file.o

conftest.c 放到前面

gcc -o conftest -g -O2 -I/usr/include conftest.c -L/usr/lib -lncurses 
<=> 等价于
gcc -o conftest -g -O2 tmporary_object_file.o -L/usr/lib -lncurses 

也就是说,gcc在链接目标文件时,会依照命令行的顺序,对于每个在前面目标文件中没有定义的函数,都试着在其后面的目标文件找找,如果最后一个目标文件也找不到,就报错。因此在我们的例子里,tmporary_object_file.o一定要放到-L/usr/lib -lncurses前面才能正确编译。

回到本文最开始,我们如何让configure把conftest.c放到“-L”前面?答案不是改动configure,而是使用LIBS环境变量:

LIBS="-L/usr/lib -lncurses" ./configure

与LIBS不同,LDFLAGS会放到conftest.c前面,这样configure还是没法正确的使用configure。

在本文最后,回想我们本来的问题,就是说gcc的参数有一定的顺序要求。也许gcc的手册里写了顺序的要求,但是很少有人能够看完gcc的手册。如果见到类似问题,应该怎么办呢?我觉得可以有两个思路,第一个就是想想写gcc软件的人是怎么想的。比如现在gcc的选择就是一个很方便的方法,如果当前没某个函数没有定义,就在他后面制定的目标文件里找找。如果这个要求反过来,或者任何顺序都支持,那么链接的函数就会复杂(因为链接程序需要存储所有定义过的函数,参见链接);第二个就是想想传统的(或者标准的)写法是什么,把已知的经验和这个错误联系起来。比如configure的错误看起来很奇怪,但是先按照传统的写法先编译后链接,找到一个能凑合工作的方案(把conftest.c放到前面),再逐步改变到出错时的命令行,这个逐步的过程可以帮助查错也能巩固一下已有的知识。

iTerm2和Emacs的Meta Key

How to set up Meta appropriately for iTerm2

学校的电脑是Mac,Mac下最好的terminal是iTerm2;我用的编辑器是Emacs,但Terminal下的Emacs不能正常工作,比如M+/是我设置的hippie-expand,我不能用Alt/Command + /按出来,一定要用Esc+/ 。
被困扰了好久之后,我终于下定决心彻底解决这个问题。
NOTE:Alt/Command 指的是一个按键,在windows键盘上是Alt,Mac键盘上叫做Command,上面有一个四瓣的小花(Apple Clove)

首先要弄清楚流程:
1. 我按的键能不能传入iTerm2?
2. iTerm2如何把我的击键传到远处的Linux Terminal?
3. 远端的Terminal里的Emacs能不能正确解析击键?

1. 我按的键能不能传入iTerm2?

在Mac系统,一些组合键还真不一定能传到iTerm2,举个例子:Alt/Command + / ,这个是iTerm2里Find Cursor的Shortcuts,就是我一按这个组合键,就执行Find Cursor的功能,iTerm里的shell是收不到我的按键的。
解决方法:详细见这个邮件列表 http://groups.google.com/group/iterm2-discuss/browse_thread/thread/e960f5098ff3c4a0
就是增加一个系统级别的设置,把Find Cursor的组合键Overwrite掉。

顺便说,我顺便发现一个工具ShiftIt,就是用组合键安排窗口,比如:把窗口最大化,放在左边1/2,右边1/2…… 非常顺手。

2. iTerm2如何把我的击键传到远处的Linux Terminal?

下面是假设我按下键了,iTerm2会把我的击键传到Linux Terminal上。注意这个过程并不简单,因为我的按键会被remap,也会被改动。
举例来说,如果我按下Alt/Command,如果有remap(比如在iTerm2里设置了,或者用Double Command等设置了Remap),实际打出的可能是Option(就是Windows键盘上的Win键)。
还有一种可能是我的击键是Alt/Command,但Linux Terminal不认识Alt/Command,只有强制iTerm2把Alt/Command输出成+Esc 才成。

简单来说,在iTerm2 针对每个Session的设定里,在设定keys时,选择Preset xterm;然后把左右Option Key都选成+Esc。
另外在Linux Terminal中.inputrc文件中加入:
set input-meta on
set output-meta on
set convert-meta off

更多资料,参见在iTerm2里设置Emacs 和 paredit以及 iTerm2官方讲Keybinding的wiki

其中官方wiki指出了一个技巧,Ctrl+V,然后击键,可以看出到底快捷键具体是什么,比如Ctrl+v,然后按Esc,就会出现:”^[“, 说明Esc对应 ^[

3. 远端的Terminal里的bash/Emacs能不能正确解析击键?

Bash 常用的一个功能是backward-kill-word,就是说用快捷键向前删除单词,一般可以用Ctrl+W,还有一种常用的快捷键是Alt+Backspace。比如在iTerm2里可以Remap Alt+Backspace,但是怎么才能输入Alt+Backspace呢?直接输入时,Backspace会删除上一个字符,这时候通过查ASCII表,可以看到Hex 0x17对应backspace,那么我在iTerm2里设置0x17就可以了。

现在在Terminal 方式的Emacs里面(emacs -nw方式),应该可以用Option来代替Meta键,比如Meta+/可以用Option+/来输入。但如果我还是习惯用Alt/Command+/怎么办?
为了不在Emacs里设置,可以在iTerm2中设置 Alt/Command + / 为发送Escaped sequenced: ^[ /

假设我们按了快捷键,只有要Emacs事先知道快捷键对应什么功能才行。(例如可以用Ctrl+h k来看我们输入的快捷键对应的功能)。

经过以上三步,现在如果我需要在Emacs里输入Alt/Command + /, 可以按Esc+/ , Alt/Command + /, 或Option + / .
当然好处不止如此,比如在Emacs里各个窗口移动,可以很方便的用Shift+ArrowKey; 在一个文档快速移动,可以用Ctrl+ArrowKey。当然最大的好处是可以用Option作为Meta,以后按键就方便多了。

2016-12-09补充
如果在GNU screen里用Emacs,需要在.screenrc里加入这两行:
term xterm
c1 off

这是为了确保GNU screen不会改变键盘上输入的Escape sequence。
换句话说,可以保证“Shift+向右的箭头”可以从键盘送到Emacs程序。

关于Fix Bug (读Coolshell Linux 2.6.39-rc3的一个插曲有感)

云风的buzz中提到了coolshell.cn的一篇文章Linux 2.6.39-rc3的一个插曲有感,看过之后,有两个感受:
(1)Linus 提出的 ‘think and analyze’的解决方式。
对于bug,从我解读Linus和Cloud的buzz回复来看,他们对于bug的方式都是知其然也知其所以然,即要么不去改bug,要么就把bug想清楚了从系统上改好。就像给一个人看病,要是治胃疼,不是说你少吃点,或者是来几片止疼药,而是调查清楚为什么得了胃病。要是长期不规律吃饭得的胃病,恐怕最好的解决方法是养成按时吃饭的习惯。这样做会增加改一个bug的时间。但从长远来看,我们有可能节省时间。比如:如果我们引入magic number之后,一个星期之后,又有类似的bug发现了,如果我们每个bug都是通过仔细思考,而且系统构建有足够的正交性的话,我们会很自然的想到,这个bug肯定发生在其他部位,而不太可能是刚修复bug的变体,这就减少了解决问题时的思考路径,节省了时间。这个时候,我们应该对自己说Good job,给自己多一点鼓励。
而有时,我们可能在别人的代码里发现了bug,这种情况下同样适用Linus的建议,即‘think and analyze’或者是’years and years of testing on thousands of machines’。要不能通过思考和分析解决问题,要么就用历史证明过的已经work的代码。这样即使不能fix 这个bug,也不会造成fix bug的假象,从而避免以后有的版本work,有的版本不work的现象。读通别人的代码并不简单,这个’think and analyze‘的过程也许要很多天,因为这个过程强迫我们用别人的思维结果来思考,而我们很难知道为什么别人这样做。个人认为,如果有足够的单元测试和高质量的文档,这个思考分析的时间并不是浪费,而是学习其他人编程思路和技巧的机会。同时,我们应该让老板知道,我们需要时间来读代码,我们在解决问题,但是的确需要时间。如果一个系统的核心部分很难读懂,而bug并不关键,不妨告诉自己不要太完美主义(想想iphone,没有多任务,电池续航有限,价格高,但仍然一个好产品)。

总而言之,Linus的观点就是每看到一个bug,问一下你确实了解这个bug的原理么?
如果Yes: (think and analyze之后),准备重组的理由,用别人可以理解的方式改(少用magic number)。
如果No: (‘years and years of testing on thousands of machines’) 回到以前测试好的版本,并且注明这个bug没有被fix。
这就是Linus认为的: It really is that simple. It’s _always_ that simple.

(2)对Yinghai Lu的评价
首先Yinghai Lu是一个kernel开发者,我认为他是有足够的相关经验的:很快发现了问题,提出了一种解决方法
陈皓认为“我没有想到Linux 内核组会有像Yinghai这样工作的方式,毕竟这是一个黑客级的开发团队。” 我认为言之过甚。
Yinghai 的 email 中说:” can you try following change ? it will push gart to 0x80000000″ (你能不能试试这个?)
而陈皓说“给出了个fix”,他认为Yinghai提出的是会加入到kernel code中的patch,我想这不一定是Yinghai的本意。

以我的理解来看,Linus一直负责code review,他希望code的质量高,有注释,不能随意fix bug (改一两个数)。因此他非常反感code中的magic number,以及只改code不管背后的逻辑;而Yinghai Lu做为开发者,首先想到的是找到问题的原因。他自己的电脑里没法重复这个bug,因此他需要把patch交给别人来测试。在不同的立场下,Linus生气 和 Yinghai的解释 就容易理解了。

我不清楚kernel开发的具体流程是怎样的。但我认为Yinghai 试图做的是在top down这个框架下做修补,他不想或者不能够对整个top down的机制做出修补,特别是这个机制下已经有垃圾代码的情况下(见HPA的回信)。在Linus看来,他是不希望看到top down这种新尝试(相对于bottom up)缺乏足够的调研支持的。至于未来Linux kernel的采用机制,将要由kernel 开发者们和Code reviewer之间做出协调,也要和以往的低质量代码做出协调。我会拭目以待。

给主机起别名(SSH)

Give SSH hostname alias names.

当使用SSH登录远端的主机时,常常要输入长长的一串主机名,比如:xxx@wonderland.sph.umich.edu。
我发现了一个简便的方法:
在.ssh/config里面加入下面的字段:
Host won
Hotname wonderland.sph.umich.edu
这样只需要输入ssh won,就可以登录wonderland.sph.umich.edu主机了。
上面的方法可以使用ssh,sftp。

还有一个更简便的方法可以使用ssh,但不能兼顾sftp,就是用bash alias功能,比如:
alias won=’ssh xxx@wonderland.sph.umich.edu’
那么只需要输入won就可以登录了。

ubuntu 中VPN 的使用

今天在ubuntu 10.10 Linux环境中顺利使用pptpd 架设的VPN服务器。

这里把必要的步骤记录一下:

1. 建立profile

用pptpsetup命令:
sudo pptpsetup –create <TUNNEL> –server <SERVER> –username <USERNAME> [–password <PASSWORD>]
TUNNEL是自己起的名字,SERVER填入VPN 服务器地址,username和password按照VPN服务器设置来做。

2. 连接VPN
sudo pon <TUNNEL>
这样就可以连接到VPN服务器。
可以用ifconfig命令检查是否VPN成功架设了,成功的话会出现一个ppp0的interface。
也可以用plog命令检查VPN连接的过程是否顺利。

3.更改route table
sudo route add default dev ppp0

4. 更改DNS服务器
sudo vi /etc/resolv.conf
添上OpenDNS的DNS服务器地址:
nameserver 208.67.222.222
nameserver 208.67.220.220

5. 检查
可以访问http://www.whatismyip.com/ 看看自己的ip是不是已经变为VPN服务器的地址了。