Archive for 十一月, 2007

《C陷阱与缺陷》读书笔记(第4章)

4.1 什么是连接器

4.2 声明与定义

在 gcc 中,如果对外部变量重定义,如果两次定义都赋了初值则会报错,否则会编译通过。

4.3 命名冲突与 static 修饰符

通过合理利用 static 来声明全局变量和函数可以有利避免名字冲突。

4.4 形参、实参与返回值

如果函数没有声明或定义就被调用,默认返回类型为整型。比如下面这段程序:

int
main(void)
{
	printf("%f\n", square(2.0));

	return 0;
}

double
square(double x)
{
	return x * x;
}

在 gcc 中,上面这段程序无法通过编译,说是类型冲突,可见在调用 square 时没找到 square 的声明或定义,gcc 默认将其声明为 int square(double),但后面的定义类型不符合。如果将下面的定义返回类型改为 int,则编译器不会报错。

4.5 检查外部类型

char filename[] = "/etc/passwd";
extern char* filename; 

理论上是不行的,但好像 gcc 并不会报错,不知道是哪里有问题?

4.6 头文件

外部变量尽量在头文件中声明,并且定义这个外部变量的c文件也应该include这个文件,这样可以做到有效的类型检查。

Comments

《C陷阱与缺陷》读书笔记(第3章)

3.1 指针与数组

C 语言中,数组名和指向该数组的指针是不完全等同的,数组名还可以用在 sizeof 中取得数组的大小,而如果将 sizeof 用在指针上则只会得到指针本身的大小了。

a[2] 和 2[a] 是等效的。连这样也可以 2[a+1],不过不建议使用。

int calendar[12][31];
sizeof(calendar[12]);

的结果是数组的大小,而不是指针。

二维数组指针的定义:

int (*monthp)[31];
monthp = calendar;

如果维数不同,gcc会发出警告。

3.2 非数组的指针

这一节谈到了 strcpy 和 strcat 这两个函数的问题,当然现在有 strlcpy 和 strlcat 这两个解决方案。作者也提供了利用 malloc 和 strlen 的方案:

char *r;
r = malloc(strlen(s) + strlen(t) + 1);
if (!r) {
	complain();
	exit(1);
}
strcpy(r, s);
strcat(s, t);
...
free(s);

3.3 作为参数的数组声明

参数声明中,数组与指针完全等效,但在定义变量时,两者时不同的。而且在声明外部变量时:

extern char *hello;
extern char hello[];

这两个声明的含义是不同的。(见 4.5)

3.4 避免“举隅法”

复制指针并不同时复制所指向的数据。

3.5 空指针并非空字符串

gcc 中,如果试图对 NULL 进行读操作,会有警告信息。

3.6 边界计算与不对称边界

和书中解释的一样,gcc中给数组分配空间,是按地址递减得顺序来的,所以如下定义:int i, a[10]; a[10] 其实就是 i。

将数组上界视作某序列中第一个被占用的元素,而把下界视作序列中第一个被释放的元素。比如一个储存区的初始地址为 buffer,bufptr 为一指针,则初始化只要 bufptr = buffer 就可以了。计算已用空间,则是:bufptr - buffer, *(bufptr++) = c; 这句话也就是向序列添加一个元素,比较自然。

书上讲到 –n >= 0 比 n– > 0 要高效,但试了一下,好像不同机器结果不同,所以还是用后者吧,毕竟比较容易理解。

如果定义 int a[10],虽然 a[10] 是非法空间,但是 &a[10] 还是可以使用的,这用来判断指针是否到达边界比较有用。

被节最后的按列打印数据挺不错的。

3.7 求值顺序

C 语言对 (&&, ||, ?:, ,) 四个操作符定义了求值顺序,象 &&, !!, ?: 都是有可能有一部分不求值的。

而对于赋值运算符的求值顺序则是没有规定的,所以下面的程序是不正确的:

i = 0;
while (i < n)
	y[i] = x[i++];

3.8 运算符 &&, || 和 !

不要与 & | 相混淆。

3.9 整数溢出

如何判断两个有符号整数相加是否溢出:

if ((unsigned)a + (unsigned)b > INT_MAX)
	complain();

if (a > INT_MAX - b)
	complain();

3.10 为函数 main 提供返回值

如果不给 main 提供返回值,可能默认会返回一个垃圾整数,但通常以返回 0 来表示正常返回,所以这样可能会被认为程序执行失败了。

Comments

NetBSD零碎笔记

[2008/11/14更新]

sudo设置

以前一直没有去尝试过sudo,现在发现这确实是个好东西。如果只是需要简单的功能,设置也并不复杂。运行visudo之后:

danran ALL=(ALL) ALL
Defaults:danran runaspw

ksh提示符

NetBSD默认安装了三个shell,sh, csh和ksh,我用的是ksh,下面是一个简单的提示符,有颜色,同时当命令出错时会提示返回值或者接收到的信号值。

my_prompt () {
	prev=$?
	short=${PWD#${HOME}}
	[ ${prev} -ne 0 ] && status=" ${prev}"
	[ ${short}" " != ${PWD}" " ] && prefix="~"
	echo -n "[^[[32m${prefix:-}${short}^[[31m${status:-}^[[0m]\n"'$ '
}

PS1='$(my_prompt)'

其中的^[需要通过Ctrl-V + Esc输入。

更改终端字体

/etc/wscons.conf中加入:

font	sfnt	-	-	-	/root/s.fnt

setvar ttyE0 font sfnt

顺便推荐一下这个字体,就是s.fnt,可以在kbd包中找到它。

另外terminus也是可以在console下使用的。首先去一些linux的发行版的二进制文件中抓取编译好的psf字体文件,我是从slackware中找的。然后按照这里提示的方法对psf文件做一些转化,之后就可以在NetBSD下使用了。转化的命令摘录如下:

dd if=font.psf of=font.fnt bs=1 count=4096 skip=4

静态IP设置

/etc/rc.conf中加入:

defaultroute="网关"
ifconfig_rtk0="inet IP地址 netmask 掩码" 

再在/etc/resolv.conf中加入:

nameserver DNS

ctags 没有 -R

NetBSD 中的 ctags 好像没有 -R 选项,去网上查了下,可以配合 find 实现:

find  dirname -name "*.[ch]" | xargs ctags

但这不是长久之计,应该NetBSD自带的ctags不支持tag重复的情况,所以最好还是安装pkgsrc中的exctags。

stardict安装

wip 里的好像和 gnome 的依赖性太大,所以没有从那安装,自己下了 stardict-2.4.8 的包,然后再用下面的 configure 一下:

./configure  -prefix=/usr --sysconfdir=/etc --mandir=/usr/man --disable-gnome-support

编译到最后会出现无法编译 stardict.desktop 的问题,不管它,反正这东西我也不用,所以直接进入 data 目录,将 stardict.desktop.in 改名为 stardict.desktop,这样就可以编译通过了。

编译时除了 gtk2 的包外,可能还需要 expat, freetype 这两个包。

readline 问题

NetBSD 下的 readline 好像有点问题,Python 历史,方向键等都不能使用,挺恼人的。找了下,好像 rlwrap,socat,rlfe 这些都是专门提供这种功能的软件,效果还不错。其实 python 的 readline 问题也可以通过安装 py-readline 这个包来解决。

gimmie 编译时 intltool 版本过低问题

原本也是摸不着头脑,但无意间 ls -l 可以看到 intltool-update.in 等三个文件链接到 /usr/share 上了,应该时 /usr/pkg/share 才对啊。

vim 编译

NetBSD 自带的 vim 不支持 python,但直接改 pkgsrc 好像又有问题,所以还是直接自己下载编译吧。

其实编译VIM并没有想像中的那么难。下载用 aap 就可以了。编译的话,只要在 src/Makefile 上把一下几个开启就可以了。

CONF_OPT_GUI = --disable-gui
CONF_OPT_X = --without-x
CONF_OPT_MULTIBYTE = --enable-multibyte
CONF_OPT_PYTHON = --enable-pythoninterp
CONF_OPT_CSCOPE = --enable-cscope

fcitx输入法

fcitx 我是直接从 www.fctix.org 下载的 fcitx ‘GirlDog’ 3.5-070703 版本。编译安装没什么问题,设置的话在 .xinitrc 中加入:

export LC_CTYPE=zh_CN.eucCN
export XMODIFIERS="@im=fcitx"
export XIM_PROGRAM=fcitx
export GTK_IM_MODULE="xim"
export QT_IM_MODULE="xim"

我的系统的 locale 是 en_US.UTF-8。

Comments

《C陷阱与缺陷》读书笔记(第2章)

2.1 理解函数声明

这一节主要讲到了如何定义函数指针。方法非常简单。比如函数原型为:

void sigfunc(int);

如果 sfp 是指向这个函数的指针,那么 *sfp 可以代替 sigfunc,所以下面这个式子与上式等同:

void (*sfp)(int);

再将 sftp 去掉,就可以得到指针类型:

void (*)(int);

还可以用 typedef 来定义类型,这样再此用到就方便了:

typedef void (*funcptr)();

2.2 运算符的优先级问题

看了这一节真是吓了一跳,很多非常不明显的优先级错误:

if(flags & FLAG != 0)
r = hi << 4 + low;

书上提到的方法是分类记忆:
非真实运算符 > 单目运算符 > 双目运算符
双目中:
算术 > 移位 > 关系 > 逻辑 > 条件 > 赋值

另外有一个挺有意思的例子:

a < b == c < d

看 a,b 和 c,d 的大小顺序是否一样。

2.3 注意作为语句结束标志的分号

有两个容易出错的地方:

  • if while 后面的分号。这可以通过编辑器的格式化来发现。

  • struct 后面的分号,这就不容易发现了。

2.4 switch 语句

这个倒是早就注意到的东西了。

2.5 函数调用

函数调用时必须带括号,如 f(),如果只是 f,则是返回函数地址。这和很多动态语言相似。

2.6 “悬挂” else 引发的问题

也是比较熟悉的一个陷阱。

Comments