2008年10月31日星期五

万圣节在世界之窗

公司加班到六点半,我就找理由逃脱了。周总说了:可以,我也在下棋呢!呵呵。

晚上有洪洪和曾胖组织的世界之窗活动,借助优惠券,我们可以在世界之窗享受廉价的万圣
节。感谢洪洪,感谢曾胖,感谢老外,嘿嘿。

万圣节又名鬼节,在西方是非常活跃的节日。最重要的标志,就是孩子们挨家挨户的要糖果,
这点我们可以在《老友记》中可以感受其气氛(因为被孩子哄的团团转,Rachel把糖果全部
给了一个小女孩)。另外你也可以看到Phoebe的超人服。


今天在世界之窗,你完全感受到另类的中国式万圣节。灰长灰长的热闹!中国人就是多,头
顶上带个犄角,更有各式各样的妖魔鬼怪齐聚这里。


我是第一次进世界之窗,这里你可以看到很多很多的世界之窗。由于在夜间,没有像曾胖同
志那样注意技巧。如果你想学摄影,可以找曾胖




这里是精简版本的曼哈顿,你也可以看到已经米稀的双子楼。


恐怖活动是主题,到处是南瓜头。在僵尸峡谷还可以看到专业级别的女鬼。


最后是万圣节party,不太专业的乐队,不太哈皮的派对。到最后出现赶鬼行动,用纸团相
互攻击。其实最后被群鬼完全吓跑。


最后是龙套小鬼,祝福大家异类的万圣节快乐。


_

2008年10月30日星期四

心诚则灵

经过接近三个月的离职期之后,我最近得以 光荣上岗

回顾这三个月,似乎经历了松山湖之行,回乡历程,孤单的七夕节,八月北京奥运,经济的
冰冻期;几经周折,我也完成了助学贷款,习惯了泡图书馆的日子,参与17爬爬吧的活动(1
23) 烦躁的求职生活。

这段时间也正是痛苦和快乐并重,有次扁桃体发炎,疼到吞咽困难也坚持不去看医生;多个
公司下遭遇闭门羹,面试题更是折磨的我苦不堪言;我和莹莹经历了最严重的争吵,甚至有
放弃感情的念头……

不过也终究过来了,我觉得生活还就这样,我也还就这样,2008年也就这样过来了。我总是
对这样苦中作乐的日子念念不忘,我还会回头想起高考的日子,踏上深圳的每一步。我甚至
可以一直这样慢悠悠的闲廷信步,一直等到花光手上所有积蓄,需要到必胜客去打钟点工来
找足乐子。尽管这样随性的性格伤害了一些好朋友。

上个星期回家,老妈告诉我家里的情况很乱。一般家里和游子总是处于“报喜不报忧”的状态
之中,假设他们不告诉家中的情形,那你得总怀揣着担忧的心思;而家人支支吾吾甚至直接
告知情况糟糕的时候,家里一定乱成一团糟了。因此找份工作仍旧是重中之重,回家过年也
会挂上日程。

现在的工作是在一家工控公司作嵌入式开发,公司名称保密。他们会为一些厂家作外包,来
实现一些工控,自动化设施的嵌入式主板,他们也有了PCB设计师,调板工,唯独让人郁闷
的是:除了经理以外,没有开发人员。很显然这个公司还处于起步阶段,办公室位于33层
(最高层),主板扔的到处都是,每个房间都有包装盒堆成的小山,室内环境比清华信息港
差上几个档次。最重要的是——在这个公司, 加班时间将成为家常便饭,而且没有加班费。

于是我进入了这样一个阶段:

  1. 始终拒绝加班的我,将会把加班作为一种享受
  2. 仍旧为成为一个顶级的软件工程师而努力
  3. 我将来的一段时间内,必须把工作放在第一位
  4. 那个小小的梦想还将被搁置

心诚则灵,收起邋遢随性的性格,为将来努力吧!

得知一大票人口在我和莹莹就职后也开始找到工作,恭喜你们,也祝愿全球金融冰冻期早日
结束。

2008年10月25日星期六

微软黑屏和盗版权益

自从微软发布WGA更新以后,中国网民的抗议声音不绝于耳: 微软黑屏引发威胁百倍于熊猫烧香微软黑屏引发国家安全话题,国家版权局将会对此事件 表态。真实情况将会怎样呢?是中国一家之言,还是全球群起反对?是网友过激,还是捍卫 安全呢?

环顾四周,我们才发现,微软黑屏是一个全球性的政策,然而对其他国家来说远远未像中国 那样表现过激,就连香港也对此冷漠。很多普及正版的国家和地区,人民是可以支付正版价 格的;而对于大多数发展中国家来说,他们也有更多的选择。

而就技术方面来说,微软只是针对盗版用户投放信息,现在没有任何组织和个人可以拿出证 据说微软控制电脑和数据。如果你说强制黑屏可以指明微软控制用户的计算机,我觉得这是 捕风捉影。操作系统本身具有利用硬件资源提供服务的功用,而在正常情况下,微软将会根 据EULA,在用户同意后才可以使用,更改任何用户数据,并通知处理用户信息的目的。但是 因为EULA的争议性,中国大陆还在考虑其是否适用。

如果中国官方现在考虑,那我建议微软黑屏和控制电脑之间需要更多的证据。当然我觉得更 值得考虑的事情是:1)盗版用户和软件制作商之间基于什么样的权力关系。2)国家如何扶植 操作系统和办公软件,用以改变对Windows和Office过度依赖的现状。3)如何发展本国自由 软件技术和提高重要机构的信息安全。

2008年10月22日星期三

我把事情搞砸了

事情得回溯到六月底,我一个人在康科办公室里,翻看着APUE的源码,我会很满足的告诉自
己:我在忙碌,我像一个有志向,发奋图强的年轻人一样在学习。但是面对着眼前的烂摊子
我就傻眼了————加拿大随时会发来稀奇古怪的需求,需要你改动一点点小玩意,在晚上下班
之前提交上去。

我讨厌了这样的工作,别人的软件让我在其之上修改一些小的界面,或者使用简单的汇编语
句取得一些硬件信息;完成之后我还要钻到实验室里,花费两天时间来测试效果。想起为了
完成新主板的MAPPING,我要投身两个星期来分析完芯片初期数据,那个经历真是糟糕透了。

我宁愿去看Linux内核,那是一个有创意的,有前途的东西,让我学习了一些硬件知识是如
何应用到操作系统设计之上的。后来我发现手头的工作极其枯燥,而且无法腾出时间来看内
核代码,我义无反顾的提交了辞呈。老板对于我的第二次辞呈没有发表太多意见,在一个月
后(七月底),我在一片静寂之中离开了付出两年汗水的公司。

八月初,我回到家乡,我见到了很多亲人老友,他们对我的经历一无所知,辞职的事情除了
老哥以外,我没有告诉任何人。家里有压在我心头的几块石头,完成这些琐碎之事以后,我
匆忙的回到了深圳,开始了漫长的求职时期。

我得说,我从没想过,求职期如此的漫长和烦躁,这让我生活的周边都显得更加煎熬。

我在八九月发出了超过30条求职信,而回复者寥寥无几。我认为这开始并不是什么稀奇事情,
很多人告诉我八到十月是招聘淡季,我对这些措辞不以为然,我不相信什么淡季,依据我的
能力,找到一个适合的工作没有大问题。现在回想起来,我对自己的自信慢慢感觉到羞愧!

九月份我的回复得到了稍许回应,我接到了几个公司的面试申请。于是我开始一一品尝面试
的滋味,腾讯公司,开源通讯公司,深信服公司,深圳傲天通讯,研祥科技大厦……这其中带
有趣味,然而更多的是苦痛。

在经历过如此多的面试之后,我得说,我根本不了解我工作的这一行是个什么样的状况,甚
至可以说,我根本没有对自己的工作做一个很好的评估————根本不知道自己要作什么。

想起毕业的时候我选择报一本《JAVA编程思想》南下找工作的情景,我觉得从理念上看,现
在和那个时候没有什么本质分别:我依然自信满满的认为我可以作自己要的工作,可对这一
份工作的概念都是模糊的。

我想作Linux程序员,但是来到人才招聘网上,我看到Linux程序员都有什么要求呢:
1) Linux驱动编程
2) Linux嵌入式开发
3) Linux后台开发

在这样的职位需求面前,我傻眼了,如果佯装是一个Linux程序员去面试职位,你几乎必须
面对以下问题之一:
1) Linux内核裁减
2) Linux驱动程序编写和移植
3) 嵌入式CPU架构和编程环境
4) Linux网络编程

如果你没有以上经验,那么你几乎没有和人事部经理谈话的资格,在技术面试官前就会被淘
汰。可是我就没有,全部NO,如我所说,80%的面试官都不会接受我的。

剩下20%是通过面试题来测试的,我得说,我是一个应试型的人才,面对考试不以为然。但
是在答复一些笔试题时候,我仍然深受打击————我的专业功底显得那么的不堪一击!我经历
过的面试题浩如烟海,他们覆盖了计算机知识绝大多数部分:
1. C/C++语言功底,C++庞大的语言特性,Shell高级编程
2. UNIX环境编程,Socket编程,多线程编程
3. UNIX网络编程,tcp/ip,http协议
4. 数据结构和算法设计,一些查找和排序算法
5. CPU架构,x96, Power, MIPS架构知识

要从理论上学习这些,我起码要看完手头的大堆书籍。他们包括:
Thinking in C++
Effective C++
UNIX环境编程 APUE
UNIX网络编程 UNP
编程艺术
算法设计
Linux设备驱动
深入理解Linux内核
... ...

顿时我觉得自己非常渺小,经过的两年工作时间在眼前化为乌有。

如果没有什么奇迹发生的话,我仍将领教上面所说的面试和笔试问题,我将再次面临自己薄
弱的知识和经验积累,我还会在离职的深渊中不断轮回。因此这样一个明显的选择摆在我的
面前:要么尝试其他低阶的工作甚至换行,要么就要费劲心力来补齐自己的不足。更要命的
是,我花费这么长的时间才发觉这个问题,那么离职时我在做什么呢?一年前呢?哦~这让
我实在难受————

天呐!我把事情搞砸了!

[LDD] C3. Character Device driver

代码阅读 <Linux Device Driver> v3
Chapter 3. Character Device Driver

一、scull介绍 Simple Chracter Utility for Loading Localities
scull是一个操作内存区域的字符设备。

scull源码实现的设备有:
scull0 ~ scull3
这四个设备分别由一个全局且持久的内存区域组成。
scullpipe0 ~ scullpipe3
这四个FIFO与管道类似。一个进程读取由另一个进程写入的数据。如果多个进程读
取同一个设备,它们就会为数据发生竞争。scullpipe的内部实现将说明在不借助
于中断的情况下如何实现阻塞式和非阻塞式读写操作。
scullsingle
scullpriv
sculluid
scullwuid
这些设备与scull0相似,但在何时允许open操作方面有一些限制。
scullsingle,一次只允许一个进程使用该驱动程序。
scullpriv,它对每个虚拟控制台是私有的。因为控制台进程会获取不痛的内存区。
sculluid和scullwuid可以被多次打开,但是每次只能由一个用户打开;如果另一
个用户锁定该设备,sculluid则返回“Device Busy”错误,而scullwuid则实现了阻
塞式open。

二、设备编号
对字符设备的访问是通过文件系统内的设备文件进行的,这些设备文件通常位于/dev。而这
些文件包含两个重要数据,主设备号和次设备号。

主设备号表示对应的驱动程序,现代的Linux允许多个驱动程序共享主设备号,但一般仍旧
遵循“一个主设备号对应一个驱动程序”的原则。

次设备号由内核使用,用于正确确定设备文件指定的设备。

MAJOR(dev_t dev); /* 返回主设备号 */
MINOR(dev_t dev); /* 返回次设备号 */
MKDEV(int major, int minor); /* 由主次设备得到dev_t类型的设备编号 */


关于分配和释放设备编号,系统在linux/fs.h中声明了以下函数:

/* 获得一个或者多个设备编号 */
int register_chrdev_region(dev_t first, unsigned int, char *name);

/* 运行过程中,使用以下函数会为我们恰当分配所需要的设备号 */
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
unsigned int count, char *name);

/* 应该用以下函数释放这些设备编号 */
void unregister_chrdev_region(dev_t first, unsigned int count);


三、重要数据结构

#include <linux/fs.h>
struct inode {
struct hlist_node i_hash;
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry;
unsigned long i_ino;
atomic_t i_count;
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev;
u64 i_version;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
unsigned int i_blkbits;
blkcnt_t i_blocks;
unsigned short i_bytes;
umode_t i_mode;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
const struct inode_operations *i_op;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
/* ... */
};

struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;

u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;

/* ... */
};

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};

/* 自定义结构 */
struct scull_qset {
void **data;
struct scull_qset *next;
};

struct scull_dev {
struct scull_qset *data;
int quantum;
int qset;
unsigned long size;
unsigned int access_key;
struct semaphore sem;
struct cdev cdev;
};


struct inode ————
内核使用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。
对单个文件,可能会有许多个表示打开的文件描述符的file结构,但他们都指向单个inode
结构。

inode包含大量节点文件信息。而此驱动只是用到了以下两个字段:
dev_t i_rdev; // 对于表示设备的inode结构,该字段包含真正的设备编号
struct cdev *i_cdev; // 对于表示字符设备的inode结构,此字段包含struct cdev指针

另外,开发者可以通过两个宏来获取主次设备编号:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

struct file ————
file结构代表一个打开的文件,系统中任何一个打开的文件都有一个对应的file结构,他由
内核在open时候创建,并传递给在文件上的所有函数,知道最后的close函数。在文件实例
关闭之后,内核会释放这个结构。用到的字段有:

struct path f_path;
#define f_dentry f_path.dentry // 目录项结构
const struct file_operations *f_op;
unsigned int f_flags; // 标志,是否阻塞
mode_t f_mode; // 权限检查
loff_t f_pos; // 当前读写位置
/* needed for tty driver, and maybe others */
void *private_data; // 跨系统调用时候保存状态信息


struct file_operations ————
file_operations用来建立驱动程序操作和驱动设备编号之间的连接,他包含了一组函数指
针,这些函数和某些系统调用相关联。程序员可以视此结构为面向对象的C编程的一个例证。

file_operations的函数指针必须指向驱动程序实现特定操作的函数,对于不支持的操作,
对应的字段被赋值NULL,这种情况下,内核的处理行为是不尽相同的。
在本例中,我们实现了以下操作:

struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};


四、scull内存使用
两个函数 ————
void *kmalloc(size_t size, gfp_t flags);
void kfree(void *ptr);
这个两个函数定义在linux/stab.h中,是内核中非常重要的两个内存操作函数。用kmalloc
函数分配,然后用kfree释放,同时也注意不应该把非kmalloc返回的指针传递给kfree。
(但是将NULL传递给kfree是合法的)


scull驱动中,每一个scull设备(scull_dev)都是一个链表头,他的指针指向scull_qset
结构的链表。而每一个scull_qset则是典型的链表结构,包含了一个4000bytes * 1000的内
存区域,这里的每一个区域就被称为是一个量子(quantum)。
4000和1000的尺寸,由宏SCULL_QUANTUM和SCULL_QSET定义。当然,你也可以设置为模块参
数,也可以在运行时使用ioctl来更改。

由于scull设备是虚拟的内存区域,所以我们的read/write操作也将是对内存的操作,顶多
的麻烦是跨越内核空间和用户空间的拷贝而已。这让实现变的非常容易。

五、实现
初始化和清理函数

int scull_init_module(void)
{
int result, i;
dev_t dev = 0;

if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}

if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}

/* allocate the devices -- we can't have them static, as the number
* can be specified at load time.
*/
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail;
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

/* initialize each device */
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);
scull_setup_cdev(&scull_devices[i], i);
}

/* At this point call the init function for any friend device */
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
// dev += scull_p_init(dev);
// dev += scull_access_init(dev);

#ifdef SCULL_DEBUG /* only when debugging */
// scull_create_proc();
#endif
printk(KERN_INFO "init scull successful\n");
return 0; /* succeed */

fail:
scull_cleanup_module();
return result;
}

void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major, scull_minor);

/* Get rid of our char dev entries */
if (scull_devices) {
for (i = 0; i < scull_nr_devs; i++) {
scull_trim(scull_devices + i);
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}

#ifdef SCULL_DEBUG /* use proc only if debugging */
scull_remove_proc();
#endif

/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, scull_nr_devs);

/* and call the cleanup functions for friend devices */
// scull_p_cleanup();
// scull_access_cleanup();
printk(KERN_INFO "cleanup scull successful\n");
}

init_module过程包括 ————
1) 注册设备
2) 初始化scull_dev设备
3) 初始化其他设备
为了简化设计,此章节没有引入pipe等设备,所以注销了某些方法。

cleanup_module过程 ————
1)释放scull_dev结构,注销字符设备
2) 注销设备号


然后是在file_operations指明的函数,这里重点讲解read和write的实现 ————

ssize_t scull_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = 0;

if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if(*f_pos >= dev->size)
goto out;
if(*f_pos + count > dev->size)
count = dev->size - *f_pos;

item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;

dptr = scull_follow(dev, item);

if (dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out;

if (count > quantum - q_pos)
count = quantum - q_pos;

if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return(retval);
}


ssize_t scull_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = 0;

if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if(*f_pos >= dev->size)
goto out;
if(*f_pos + count > dev->size)
count = dev->size - *f_pos;

item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;

dptr = scull_follow(dev, item);

if (dptr == NULL)
goto out;
if (!dptr->data) {
dptr->data = kmalloc(qset *sizeof(char *), GFP_KERNEL);
if (!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!dptr->data[s_pos])
goto out;
}

if (count > quantum - q_pos)
count = quantum - q_pos;

if (copy_from_user(dptr->data[s_pos] + q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;

/* 更新文件大小 */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return(retval);
}


scull_read从filp的f_pos处读取count个字节,写到buf中。
首先要作的是,确定f_pos的位置,根据f_pos,我们要确定写的位置,位于第几个item的
第s_pos数组的第q_pos个量子处,然后根据item返回我们要的scull_qset结构体。最后使用
copy_to_user完成读操作。

写操作和读操作很相似,使用了逆向的copy_from_user函数。

这两个重要的系统调用实现,都使用了跨越内核和用户空间的写操作:
copy_to_user(void *to, void *from, int count);
copy_from_user(void *to, void *from, int count);

六、总结
scull_init_module
* alloc_chrdev_region
* scull_setup_cdev
** cdev_init, cdev_add
** scull_fops(llseek, read, write, ioctl, open, release)
* scull_create_proc
* scull_p_init
* scull_access_init

_

2008年10月14日星期二

[TIC++] C13. Dynamic Object Creation

代码阅读<Thinking In C++>
Chapter 13. Dynamic Object Creation

一、在堆区创建内存

// c13: malloc_class.cpp
// malloc with class objects
#include <cstdlib>
#include <cstring>
#include <cstring>
#include <iostream>
using namespace std;

class obj {
int i, j, k;
enum { sz = 100 };
char buf[sz];
public:
void initialization() { // can't use constructors
cout << "initialization obj" << endl;
i = j = k = 0;
memset(buf, 0, sz);
}
void destroy() {
cout << "destroy obj" << endl;
}
};

int main()
{
obj *o = (obj*)malloc(sizeof(obj));
if (o == 0)
return -1;
o->initialization();
//sometimes later...
o->destroy();
free(o);
}
/* result:
initialization obj
destroy obj
*/

当C++对象创建的时候,将会发生两件事情:
1) 为空间分配内存
2) 调用构造方法来初始化这块内存

而空间创建问题(1),可以有以下选择:
1) 在程序开始前创建静态空间,位于静态内存区,拥有文件生命期
2) 在执行点处创建堆栈空间,他将在执行跳出方法的时候释放。堆栈的处理是处理器完成
的,自然非常高效
3) 在执行点处创建堆空间(heap),这被成为动态内存分配(!)。方法必须在运行调用的时候
创建堆空间,这意味着你可以决定任何时候创建,当然也要为释放负全权责任————生命期完
全由程序员掌控。

上面的例子展示了C处理堆区空间的方法:
这里的obj o对象的创建,没有使用到构造方法!这是非常糟糕的情况,因为你有可能选择
是否初始化,甚至可能忘记,初始化的丢失极为可能带来bug。

于是C++引入了一种新方法:

// c13: new_and_delete.cpp
#include <iostream>
using namespace std;

class tree {
int height;
public:
tree(int tree_height): height(tree_height) {}
~tree() { cout << "*"; }
friend ostream& operator <<(ostream &os, const tree* t) {
return os << "tree height is: "
<< t->height << std::endl;
}
};

int main(void)
{
tree *t = new tree(40);
cout << t;
delete t;
}
/* result
tree height is: 40
*/

new和delete保证了构造方法的正常调用,甚至还会检查内存申请是否调用成功。
这一切显得像堆栈内存分配一样简单。

二、delete void*

// c13: bad_void_pointer_deletion.cpp
#include <iostream>
using namespace std;

class object {
void *data;
const int size;
const char id;
public:
object(int sz, char c): size(sz), id(c) {
data = new char[size];
cout << "constructing object" << id
<< ", size = " << size << endl;
}
~object() {
cout << "desctructing object " << id << endl;
delete []data; // OK, just release storage;
// no desctructor calls are necessary;
}
};

int main() {
object *a = new object(40, 'a');
delete a;
void *b = new object(40, 'b');
delete b;
}
/* result:
constructing objecta, size = 40
desctructing object a
constructing objectb, size = 40
*/

object是一个包含 void *data的一个类,在这个类的析构方法中,我们看到使用了delete
来析构data,这没什么问题,因为这里要作的只是释放内存。

但是在main()中,对于delete来说,对象类型信息就非常必要了。由于delete a知道a是一
个class object,所以他使用到了~object()析构方法,data也被释放了。可是void *b就遇
到麻烦,他无法得知void*的类型信息,就无法调用析构方法,因此data将会永远的丢失,
这是一个静静的内存泄露。

如果你在C++工程中有内存泄露问题,那就要好好检查你的delete了。

三、介绍new handler

// c13: new_handler.cpp
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;

int count = 0;

void out_of_memory()
{
cerr << "memory exhausted after " << count << " allocations" << endl;
exit(1);
}

int main()
{
set_new_handler(out_of_memory);
while(1) {
count++;
new int[1000];
}
}

当new无法找到一个足够大的堆空间来存放对象,会出现何种情况呢?
这样的情况下,就会调用new-handler。默认的new-handler会抛出异常,当然可以定义新的
new-handler,打印出信息告诉你发生了什么。

这里就是用<new>的set_new_handler设置了方法,他会打印在经历多少次存储之后耗尽内存。

_

[TIC++] C11. References & the Copy-Constructor

代码阅读<Thinking In C++>
Chapter 11. References & the Copy-Constructor

一、再次介绍引用

// reference.cpp
int *f(int *x)
{
(*x)++;
return x;
}

int &g(int &x)
{
x++;
return x;
}

int &h()
{
int q;
//! return q; // error
static int x;
return x;
}

int main()
{
int a = 0;
f(&a);
g(a);
}

reference可以被简单视作一个常量指针,而这个指针会在预编译期间自动被解释掉。
ref最常用到的地方,就是作为参数和返回值使用了。对参数而言,任何形参的改变会作用
到实参;针对返回值,要注意的是返回的东西只是引用,必须保证要返回的内存在返回地可
见。
* 因此,你不能在h()中返回局部变量。

在介绍copy constructor之前,让我们来看常引用情况 ————

// const_ref.cpp
void f(int&) {}
void g(const int&) {}

int main() {
//! f(1); //invalid initialization of non-const reference
g(1);
}

当传递1这个实参的时候,编译器会为1分配内存,然后创造一个引用(int&)绑定到这个内存
地址。如果1没有被声明为const,那将是毫无意义的。f(1)将得到一个编译时错误。

最后是复合化的引用,指向指针的引用 ————

// c11: ref_to_pointer.cpp
#include <iostream>
using namespace std;

void increment(int* &i) { i++; }

int main()
{
int *i = 0;

cout << "i = " << i << endl;
increment(i);
cout << "i = " << i << endl;
}

这里阵阵改变的是指针(int*)i,而不是指针指向的值,但是这里传递的i。
在C中要做到这点,那就要使用二级指针了。

二、copy-constructor

// c11: howmany.cpp
#include <fstream>
#include <string>

using namespace std;
ofstream out("howmany.out");

class howmany {
static int object_cnt;
public:
howmany() { object_cnt++; }
static void print(const string& msg = "") {
if (msg.size() != 0)
out << msg << ": ";
out << "object_cnt = " << object_cnt << endl;
}
~howmany() {
object_cnt--;
print("~howmany()");
}
};

int howmany::object_cnt = 0;

// pass and return by value:
howmany f(howmany x)
{
x.print("x argument inside f()");
return x;
}

int main()
{
howmany h;
howmany::print("after construction of h");
howmany h2 = f(h);
howmany::print("after call to f()");
}
/* result:
after construction of h: object_cnt = 1
x argument inside f(): object_cnt = 1
~howmany(): object_cnt = 0
after call to f(): object_cnt = 0
~howmany(): object_cnt = -1
~howmany(): object_cnt = -2
*/

首先我们构建howmany h,调用构造函数,会得到object_cnt = 1;
然后我们使用f()来构建第二个对象h2 = f(h),编译器传递h拷贝,并没有使用构造函数来
创建h2。但是意想不到的是,离开f()的时候发生了析构,导致object_cnt = 0。
而最后的两个析构,使得object_cnt得到负数。

这是为什么呢?
因为在f()内,函数使用了C形式的bitcopy,我们得到的只是一份h的拷贝,他没有调用构造
方法,所以我们没有看到f()中objecgt_cnt = 2。而在后来,C++特性去保证了最后一步析
构,因此出现了object_cnt不升反降的结果。

C++中为了保证构造的完整性,因此提出了copy-constructor————

// c11: howmany.cpp
#include <fstream>
#include <string>
using namespace std;

ofstream out("howmany2.out");

class howmany2 {
string name;
static int object_cnt;
public:
howmany2(const string &id = ""): name(id) {
++object_cnt;
print("howmany2()");
}
~howmany2() {
--object_cnt;
print("~howmany()");
}
howmany2(const howmany2 &h): name(h.name) {
name += " copy";
++object_cnt;
print("howmany2(const howmany2&)");
}
void print(const string& msg = "") const {
if (msg.size() != 0)
out << msg << endl;
out << '\t' << name << ": "
<< "object_cnt = " << object_cnt << endl;
}
};

int howmany2::object_cnt = 0;

// pass and return by value:
howmany2 f(howmany2 x)
{
x.print("x argument inside f()");
return x;
}

int main()
{
howmany2 h("h");
out << "entering f()" << endl;
howmany2 h2 = f(h);
h2.print("h2 after call to f()");
out << "call f(), no return value" << endl;
f(h);
out << "after call to f()" << endl;
}
/*
howmany2()
h: object_cnt = 1
entering f()
howmany2(const howmany2&)
h copy: object_cnt = 2
x argument inside f()
h copy: object_cnt = 2
howmany2(const howmany2&)
h copy copy: object_cnt = 3
~howmany()
h copy: object_cnt = 2
h2 after call to f()
h copy copy: object_cnt = 2
call f(), no return value
howmany2(const howmany2&)
h copy: object_cnt = 3
x argument inside f()
h copy: object_cnt = 3
howmany2(const howmany2&)
h copy copy: object_cnt = 4
~howmany()
h copy copy: object_cnt = 3
~howmany()
h copy: object_cnt = 2
after call to f()
~howmany()
h copy copy: object_cnt = 1
~howmany()
h: object_cnt = 0
*/

我们可以看到,f()传递参数的时候,编译器将会把拷贝传值的过程交给copy-constructor,
同样return返回值,也需要这样的拷贝。
entering f()
howmany2(const howmany2&)
h copy: object_cnt = 2
x argument inside f()
h copy: object_cnt = 2
howmany2(const howmany2&)
h copy copy: object_cnt = 3

而后离开f(),这需要析构刚才创建的临时变量,保留return的值。

第二次f()没有返回值,因此情况稍微发生改变。此种调用忽略了返回值,编译器也会聪明
的直接在调用点析构此临时对象。

三、选择copy-construction
我们来看如何迫使系统自建拷贝构造方法

// c11: default_cc.cpp
// automatic creation of the copy-constructor
#include <iostream>
#include <string>
using namespace std;

class withcc {
public:
withcc() {}
withcc(const withcc &) {
cout << "withcc(withcc &)" << endl;
}
};

class wocc {
string id;
public:
wocc(const string &ident = ""): id(ident) {}
void print(const string &msg = "") const {
if (msg.size() != 0)
cout << msg << ": ";
cout << id << endl;
}
};

class composite {
withcc tc;
wocc oc;
public:
composite(): oc("composite()") {}
void print(const string &msg = "") const {
oc.print(msg);
}
};

int main() {
composite c;
c.print("content of c");
cout << "calling composite copy-constructor" << endl;
composite c2 = c;
c2.print("content of c2");
}
/* reuslt
content of c: composite()
calling composite copy-constructor
withcc(withcc &)
content of c2: composite()
*/

我们在这里看到两个案例:
1) class withcc拥有构造方法和拷贝构造方法。一旦包含拷贝构造函数,这就是在告诉编
译器:永远不要再自作主张,自动创建默认构造函数。因此,如果class withcc没有定义
withcc(),编译器会报错——composite中的tc没有办法创建。

2) class composite拥有构造方法,而没有拷贝构造方法。一旦需要拷贝构造方法,编译器
会为所有的成员对象调用他们的拷贝构造方法。(这通过组合或者继承可以实现composite)
在这里,c2 = c,就强迫调用了withcc和wocc的拷贝构造方法。而wocc没有拷贝构造方法,
因此系统为其自动定义一个,完成bitcopy。

接下来,通过小技巧来避免赋值拷贝

// c10: no_cc.cpp
class nocc {
int i;
nocc(const nocc &);
public:
nocc(int ii = 0): i(ii) {}
};

void f(nocc);

int main(void)
{
nocc n;
// copy-constructor is private!
//! f(n);
//! nocc n2 = n;
//! nocc n3(n);
}

通过将拷贝构造方法设置为私有,来避免赋值拷贝,这是一个非常简单的方法。
在此不用赘述。
_

2008年10月11日星期六

[TIC++] C10. Name Control

代码阅读<Thinking In C++>
Chapter 10. Name Control

一、局部变量作为static

// c10: static_objects_inside_funcs.cpp
#include <iostream>
using namespace std;

class X {
int i;
public:
X(int ii = 0):i(ii) {}
~X() { cout << "X::~X()" << endl; }
};

void f()
{
static X x1(47);
static X x2;
}

int main()
{
f();
} ///:~
/* result:
X::~X()
X::~X()
*/

当局部变量声明为static的时候,此变量将不会放置在堆栈区内,而是放置在程序的静态内
存区。这就意味着static变量:
* 具有全局生命期
* 初始化只进行一次
* 此变量在声明函数外部不可见

来解析一下静态对象的析构方法:

// c10: static_destructors.cpp
// static object destructors

#include <fstream>
using namespace std;

ofstream out("statdest.out");

class obj {
char c;
public:
obj(char cc): c(cc) {
out << "obj::obj() for " << c << endl;
}
~obj() {
out << "obj::~obj() for " << c << endl;
}
};

obj a('a');

void f()
{
static obj b('b');
}

void g()
{
static obj c('c');
}

int main()
{
out << "inside main()" << endl;
f();
//g() not called
out << "leaving main()" << endl;
}
/* statdest.out:
obj::obj() for a
inside main()
obj::obj() for b
leaving main()
obj::~obj() for b
obj::~obj() for a
*/

静态变量具有全局生命期,这样静态对象会在main()函数退出时候,或者调用C标准函数
exit()的时候被自动调用析构方法。这意味着:如果你在静态对象的析构方法内exit,就会
造成一个死循环这是很危险的。另外,调用abort()函数结束程序,将不会自动调用静态
对象的析构方法。

这个例子解释了一些基本概况,a是全局对象,b是f()内部的静态对象,他们的构造和析构
顺序正好相反:
cons a -> cons b -> des b --> des a
(在C++中,全局静态对象的构造函数是在main()之前被调用的)

二、全局变量为static
local_extern.cpp -- 第一个模块(main)

// c10: local_extern.cpp

#include <iostream>

int main()
{
extern int i;
std::cout << i << endl;;
}
/* result:
5
*/

local_extern2.cpp -- 第二个模块

// c10: local_extern2.cpp
int i = 5;

static 和 extern在 C++中是一对反义词。
* 默认全局变量是extern的,extern关键词标明次变量包含外部链接,可以在其他模块中引
用。而static则约束此变量为内部链接,不可外部引用。这对于“非成员”函数也同样适用。
file scope:
1) 全局变量声明默认为extern,而声明为static只会更改可视范围,这样变量只包含内部
链接————它仍旧会位于静态数据区域,无论static还是extern。

2) 局部变量声明默认为auto,若声明为static则会更改可视范围,并且更改存储区域(从
堆栈区更改到静态数据区);若声明为extern,则显示此变量位于别的模块。

3) 方法定义为static/extern,只会改变可视范围。

三、名字空间 namespaces

// c10: continuation.cpp
namespace mylib {
extern int x;
void f();
}

namespace mylib {
extern int y;
void g();
}

int main() {}

* 名字空间的定义必须在全局区内,或者被嵌套在其他名字空间内
* 定义的末尾没有";"
* 可以在多个头文件里添加名字空间定义(原本看起来像重复定义)
* 名字空间可以被引用为其他名字,比如
namespace bob = bobs_super_duper_library;
* 对名字空间内部数据的引用类似于成员操作
* 你不能使用名字空间建立实例,他根本不是一个类型

四、using 指令
namespace_int.h -- 定义名字空间Int

// c10: namespace_int.h
#ifndef __NAMESPACEINT_H
#define __NAMESPACEINT_H

namespace Int {
enum sign { positive, negative };
class Integer {
int i;
sign s;
public:
Integer(int ii = 0): i(ii),
s(i >= 0 ? positive : negative)
{}
sign get_sign() const { return s; }
void set_sign(sign sgn) { s = sgn; }
}
}

#endif /// __NAMESPACEINT_H

namespace_math.h -- 定义名字空间Math

// c10: namespace_math.h
#ifndef __NAMESPACEMATH_H
#define __NAMESPACEMATH_H

#include "namespace_int.h"

namespace Math {
using namespace int;
Integer a, b;
Integer divide(Integer, Integer);
}

#endif /// __NAMESPACEMATH_H

arithmetic.cpp

// c10: arithmetic.cpp
#include "namespace_math.h"
void arithmetic()
{
using namespace Int;
Integer x;
x.set_sign(positive);
}

int main() {}

在arithmetic()的方法中,我们看到了using指令,如果没有他,任何命名空间的内容都需
要全名提供。

五、C++中的static数据成员

// c10: static_init.cpp
#include <iostream>
using namespace std;

int x = 100;

class with_static {
static int x;
static int y;
public:
void print() const {
cout << "with_static::x = " << x << endl;
cout << "with_static::y = " << y << endl;
}
};

int with_static::x = 1;
int with_static::y = x + 1;
// with_static::x not ::x

int main()
{
with_static ws;
ws.print();
}
/*
with_static::x = 1
with_static::y = 2
*/

类的static成员存在于一个独立的空间中,无论建立多少个对象,他们的static成员都是共
享的。更重要的是static成员只有在类对象中可见,它也可以受成员访问符约束。

在此例子中,在没有创建实例的时候,我们就对with_static::x进行初始化,这对于静态成
员是可行的。


// c10: static_array.cpp
class values {
static const int scsize;// = 100;
static const long sclong = 100;

static const int scints[];
static const long sclongs[];
static const float sctable[];
static const char scletters[];
static int size;
static const float scfloat; // differ from tic++
static float table[];
static char letters[];
};
int values::scsize = 100;
int values::size = 100;
const float values::scfloat = 1.1;
const int values::scints[] = { 99, 47, 33, 11, 7 };
const long values::sclongs[] = { 99, 47, 33, 11, 7 };
const float values::sctable[] = { 1.1, 2.2, 3.3, 4.4 };
const char values::scletters[] = { 'a','b','c','d','e','f','g','h','i','j',};
float values::table[4] = { 1.1, 2.2, 3.3, 4.4 };
char values::letters[10] = { 'a','b','c','d','e','f','g','h','i','j',};

int main() { values v; }

这里揭示了static初始化的可行性分析:
1) static 不可以在类中初始化,必须要求const。
2) static const 非原子类型,不可以在类中初始化。
3) static const 原子类型,可以而且必须在类中初始化。

下面来看嵌套类定义和局部类定义中的static情况————

// c10: local.cpp
// static member & local classes
#include <iostream>
using namespace std;

// Nested classes CAN have static data members;
class outer {
class inner {
static int i;
};
};

int outer::inner::i = 47;

//local class can not have static data members;
void f()
{
class local {
public:
//! static int i; // error
// (how could you define i?)
} x;
}

int main()
{
outer x;
f();
}

你可以发现嵌套类定义中的static完全可行,但是在局部类定义中却被禁止了。
我们怎么办呢?实际上,局部类定义是很少使用的。

六、static成员方法

// c10: static_member_funcs.cpp
class X {
int i;
static int j;
public:
X(int ii = 0): i(ii) {
// non-static member function can
// access static member function or data
j = i;
}
int val() const { return i; }
static int incr() {
//! i++; // Error: static member function
// cannot access non-static member data
return ++j;
}
static int f() {
//! val(); // Error: static member function
// cannot access non-static member function
return incr();
}
};

int X::j = 0;

int main()
{
X x;
X *xp = &x;

x.f();
xp->f();
X::f(); // only works with static members
} ///:~

同样,我们在C++中可以像定义成员数据那样定义一个static成员方法,在我们需要创建一
个仅仅作用于类或者对象的方法的时候,我们不希望他在全局区域内造成命名污染,因此,
这个念头就诞生了。

static方法可以直接被引用调用,不需要建立类实例(就像数据成员初始化一样)。有一点
要注意的是:static成员方法,他并不包含this指针,这注定了它:

* 不能够访问数据成员,也不能调用成员方法。


// c10: singleton.cpp
#include <iostream>
using namespace std;

class egg {
static egg e;
int i;

egg(int ii): i(ii) {}
egg(const egg&); // prevent copy-construction
public:
static egg* instance() { return &e; }
int val() const { return i; }
};

egg egg::e(47);

int main()
{
//! egg x(1); // private constructor
cout << egg::instance()->val() << endl;
}

这个例子非常有趣,除了egg::e,你将永远无法创造第二个egg对象。

1) 因为egg的构造函数设置为私有,你无法直接创建私有对象。
2) 使用static成员方法egg::instance(),你可以创建一个实例,这个实例是
static egg e;
3) 由于egg e本身也是e,那么所有的 instance()调用,都会返回这一个对象。其他对象创
建不能。
4) 此例还禁用了拷贝构造方法 egg(const egg&),这意味着,你甚至无法赋值对象,最后
一条路也被封锁了。

七、static 初始化依赖 static initialization dependency
initializer.h

// c10: initializer.h
#ifndef __INITIALIZER_H
#define __INITIALIZER_H

#include <iostream>

extern int x;
extern int y;

class initializer {
static int init_count;
public:
initializer() {
std::cout << "initializer()" << std::endl;
//initialize first time only
if (init_count++ == 0) {
std::cout << "performing initialization"
<< std::endl;
x = 100;
y = 200;
}
}
~initializer() {
std::cout << "initializer()" << std::endl;
//cleanup last time only
if (--init_count == 0) {
std::cout << "performing cleanup"
<< std::endl;
// any necessary cleanup here
}
}
};

static initializer init;

#endif//__INITIALIZER_H

initializer_defs.cpp -- 已知模块

// c10: initializer_defs.cpp
#include "initializer.h"

int x;
int y;
int initializer::init_count;

initializer.cpp -- 测试模块(main)

// c10: initializer.cpp
#include "initializer.h"
using namespace std;

int main()
{
cout << "inside main()" << endl;
cout << "leaving main()" << endl;
}
/* result:
initializer()
performing initialization
initializer()
inside main()
leaving main()
initializer()
initializer()
performing cleanup
*/

在extern的初始化过程中,我们会遇到这样的问题:

extern std::ofstream out;
class Oof {
public:
Oof() { std::out << "ouch"; }
} Oof;

这段代码引用到了其他文件的一个对象ofstream out,如果两个文件位于不同的模块,那么
这意味着out的定义和使用位于两个模块。在编译器编译的时候,往往不能完全决定哪一个
模块先编译,操作系统也没有提供确保初始化顺序的绝对方法。如果是此段代码先编译,那
么面对的问题就是,out还未构造就使用了,这会带来混乱。
这个问题针对的对象是(它们会在main()之前执行初始化):
1) 全局对象
2) 局部static对象

为了解决此问题的发生,我们给出三种方法
1) 永远别这样做,避免此类问题发生是最好的选择
2) 如果必须这样做,把静态对象定义放在一个文件里,这样你可以安排预想的编译次序。
3) 如果不可避免的要把静态对象放在不同模块中,可以采用两种技巧来解决此问题。

本段例子就是技巧1,它是Jerry Schwarz在创建iostream库的时候提出的。
initializer.h *设计目标
initializer_defs.cpp *已知模块
initializer.cpp *已知模块
我们要求的目标是,无论initializer_defs.cpp和initializer.cpp哪一个先初始化,都可
以保证x,y的安全使用。
实现方法:首先在initializer.h中,extern引用x,y,然后定义为x,y专门的初始化类
initializer,根据static成员int_count是否为0,判断x,y是否已经被初始化,如果未初始
化,则实现初始化;否则不要动。以同样的方法定义析构方法。接着在文件末尾添加static
对象initializer init。
最后一步,在x,y对象定义的模块初始化 initializer::init_count = 0;

由于已知模块都已经包含了initializer.h头文件,那么他们将共同拥有init对象,init对
象保证了对象x,y的初始化;由于class initializer的处理,也将屏蔽x,y的重复初始化。

下面介绍技巧2————
dependency1.h

// c10: dependency1.h
#ifndef __DEPENDENCY1_H
#define __DEPENDENCY1_H

#include <iostream>

class dependency1 {
bool init;
public:
dependency1(): init(true) {
std::cout << "dependency1 construction"
<< std::endl;
}
void print() const {
std::cout << "dependency1 init:"
<< init << std::endl;
}
};

#endif//__DEPENDENCY1_H

dependency2.h

// c10: dependency2.h
#ifndef __DEPENDENCY2_H
#define __DEPENDENCY2_H

#include "dependency1.h"

class dependency2 {
dependency1 d1;
public:
dependency2(const dependency1 &dep1): d1(dep1) {
std::cout << "dependency2 construction ";
print();
}
void print() const { d1.print(); }
};

#endif//__DEPENDENCY2_H

dependency1statfun.h

// c10: dependency1statfun.h
#ifndef __DEPENDENCY1STATFUN_H
#define __DEPENDENCY1STATFUN_H

#include "dependency1.h"
extern dependency1 &d1();

#endif//__DEPENDENCY1STATFUN_H

dependency2statfun.h

// c10: dependency2statfun.h
#ifndef __DEPENDENCY2STATFUN_H
#define __DEPENDENCY2STATFUN_H

#include "dependency2.h"
extern dependency2 &d2();

#endif//__DEPENDENCY2STATFUN_H

dependency1statfun.cpp

// c10: dependency1statfun.cpp
#include "dependency1statfun.h"

dependency1 &d1()
{
static dependency1 dep1;
return(dep1);
}///:~

dependency2statfun.cpp

// c10: dependency2statfun.cpp
#include "dependency1statfun.h"
#include "dependency2statfun.h"

dependency2 &d2()
{
static dependency2 dep2(d1());
return(dep2);
}

technique2b.cpp

// c10: technique2b.cpp
// (L) dependency1statfun dependency2statfun

#include "dependency2statfun.h"

int main()
{
d2();
}///:~
/* result:
dependency1 construction
dependency2 construction dependency1 init:1
*/

这个方法显然更加干净清爽:)
dependency1.h * 已知头文件
dependency2.h * 已知头文件
dependency1statfun.h * 设计头文件
dependency2statfun.h * 设计头文件2
dependency1statfun.cpp * 设计模块
dependency2statfun.cpp * 设计模块2
technique2b.cpp *main函数,用来测试
我们发现class dependency2的对象实例必须通过拷贝构造函数来初始化,而且他需要借助
class dependency1的对象。因此以下的初始化总是可能存在的:
dependency2 dep2((dependency1)dep1);
如果dep1和此定义不再同一模块中,我们显然要面对静态对象的依赖问题。

技巧2是这样作的。他通过定义两个模块提供了两个函数d1()和d2(),他们分别包含一个静
态对象dep1和dep2(d1),如果要创建dependency2对象,只有通过d2()返回得到。实际上,
技巧2正是封锁了初始化方法,迫使初始化按照即定的顺序进行。
dependency2& -> d2() -> dep2(d1()) -> d1() -> dep1 -> dependency1: dependency1()

总结

我们看到static带来功能性的同时,也带来了困惑,因为在某些情况下,它用来控制存储位
置,有些时候控制可视范围或者名字的链接属性。
* static存储在静态数据内存区,拥有全局生命域
* 作用在局部变量上,static使得变量局部可见却拥有全局生命期。
* 作用在全局变量上,static控制可视范围,仅仅在本模块内可见。
* 作用在类的数据成员上,static既控制生命期,又限制该标识符仅仅在类中可见。
* 作用在类的成员方法上,static方法除了保留数据成员特性以外,还去除了指针。这意味
着,静态成员不能访问非static数据成员,也不能调用非static成员方法。

namespace 同样给予更加灵活的方法,让您在大工程内部控制代码的增加和繁衍。

class 内部的class是在程序中控制名字的另外一种方法,他不会和全局名字冲突,而在程
序内部享用独立的可视范围和访问控制。这会大大增强你代码的维护性能。

2008年10月10日星期五

We Are On The Cruise! -- OnePiece 十周年纪念



财富、名声、力量——曾经拥有这世界上一切男人,海贼王 Gol D. Roger,他在临刑前的一
句话,让全世界的人们趋之若鹜奔向大海:“想要我的财宝吗?想要的话可以全部给你!伟
大航路!我把所有财宝都放在了那里。”于是男子汉们启程前往伟大航路————
大海贼的时代来临了!
----

这是每次星期天晚上都会听到的话,这一天的早上九点,隔海相望的日本将会定期推出日本
漫画《One Piece》。因此这一天来说,对我总是轻松的一天,可以享受周末的轻松,同样
观看《One Piece》——我从来未曾想过自己会爱上一部动画。

而最新的373集openning再次更改,启用了10周年版本主题曲,我们在openning动
画中看到了从第一章节至今出现过的主要人物,从Buggy,Arlong,Smoker到Red Foot Zeff,
巨鲸Laboon,Vivi,Mr.2,还有冰山先生,七武海Sir Crocodile,最新的CP9。这才发现
《One Piece》已经度过漫长的10周年~

One Piece是啥玩意儿呢?对于一些大叔大婶,从来认为动画是小儿科的人们,咱们还是来
普及一下————

One Piece介绍
One Piece是由日本漫画家尾田荣一郎创作的少年漫画,自1997年8月4日起开始在《周刊少
年JUMP》中连载,而漫画每一章节都由株式会社集英社以单行本的方式发行,至今仍在连载。
(OnePiece漫画创刊号)


其名字“One Piece”在故事中为“一个大秘宝”之意。One Piece讲述的是一个17岁的男孩,
Monkey D. Luffy,从误食恶魔果实获得超自然力,而后伙同一群勇敢的海贼的冒险故事,
他们的海贼团体叫做“草帽海贼团”。Luffy以获得世界的终极秘宝——One Piece为目标,从而
成为新一代的海贼王。

我们所听到的译名“海贼王”为台湾中文版发行权转移之后的新代理商“东立出版社”因版权关
系另取的译名。你可以在内地看到以下译名,他都说的是一个动画————One Piece:
海贼王(台湾 大然文化)
航海王(台湾 东立出版社)
海贼王(香港 天下出版社)

借由1亿4千万的漫画销量,One Piece是《周刊少年JUMP》史上销量第三的漫画作品,同样
One Piece也被认为是最受好评的三大漫画作品之一。

情节
听到上面的解说,你还是对One Piece没有概念,因为描述实在乏味,这和打败BOSS拿钥匙
开宝箱的经典剧情没有啥大分别。那我没的办法,因为我无法用三言两语把十年长篇的漫画
说个清楚透彻。

One Piece中描述的海贼,显然和大家想象中的不一样,“独木腿、铁钩手、黑眼罩、三角帽
子、提着酒瓶劫掠四方的人”,这种《加勒比海盗》中的海贼形象在One Piece的世界里显得
更加简单但是多元化,我们的几个主角将会是各自背景不同,使命非凡,勇于追逐梦想,极
富自由精神的,乐于向上,将朋友视为生命的海贼,这将传统意义上作恶多端的海贼形象完
全颠覆。


主线似乎很明了,少年时代的Luffy,被他心目中的英雄“红发Shanks”鼓舞,而起身寻找传
说的大秘宝,这个宝藏可谓是世界财富的集合与象征,更重要的是,它是“海贼王”称号的标
志。为了成为“海贼之王”,Luffy面临的将是世界上最危险,最诡异的海域:伟大航路
(The Grand Line)。

Luffy在东海位置起步,他面临的首要任务是组织一个草帽海贼团;沿着上一代海贼王,
Gold Roger的足迹,他们将一步步的逼近One Piece秘宝。

我喜欢的OP
我在这里说说这部动画的杰出之处。他和其他漫画有啥分别。

1) OP的故事构架太出色了。
在二十几集的时候和三百多集的时候,我依然抱有同样感觉:海贼王的故事写的太好了!
从四海到伟大航路,再到新世界,这样故事的轮廓非常清晰,而四皇,海军,七武海三大势
力的均衡也很直白,尾田没有在这里隐瞒什么。相反观众和读者的想法都很简单:Luffy是
要朝着新世界去的,在三大势力并存的新世界,草帽海贼团显然不是个旁观者。他将会与这
些恶势力搏斗,最终取得One Piece。

传统动画上呢?你几乎永远摸索不到故事的主题,当然如果“主角变强变强再变强”也算主题。
(中将Monkey D. Garp介绍四皇)


2) OP启用了多元化的叙述方式,让故事显得更加具有立体感。
大体轮廓确定之后,尾田先生在漫画的主干上面耗费了太多太多的精力,为我们呈现出来极
其精彩的故事。

我们开始的时候都会知道,Shanks是Luffy的启蒙英雄,由他激励鼓舞,Luffy踏上了海贼王
的征程。那么依据观众的理解,Shanks将来肯定会在伟大航路和Luffy碰面,还会向Luffy提
供帮助和指引。对,没错,不过Shanks后来展现在大家面前的时候还是吓了一跳:他就是在
新世界里君临海上的四皇之一“红发Shanks”。这种落差感,让我们对他们的相遇期待度大增。

尾田的这种起承的手法用了非常非常的多,包括巨鲸Laboon的约定,Zoro对Mihawk的誓言等
等等等。

再来说说,第24集动画的时候,Luffy遭遇Sanji,努力想纳入麾下。但是面对的主要矛盾则
是,摆脱红脚Zeff的干扰和努力说服Sanji,但是红脚Zeff可是穿越伟大航路的大海贼,所
以我们开始把视线聚焦到Luffy vs. Zeff的问题上来。但是后来东海霸主Kreig来袭,我们
又确定他才是本场的强敌。不过更恐怖的才来到我们面前,世界第一剑客,同时是七武海之
一的鹰眼Mihawk紧随Kreig来到东海,而又发生Zoro单挑鹰眼的事件。这让观众才和Zoro一
样惊叹:这才是世界第一的力量!这种层层迭加的紧张感觉,更加白热化的场景,实在让海
贼王更加趋近于电影化。

这样的情节在海贼王屡见不鲜,空岛被迫牵连到本地人的纠纷,司法岛引出Nico Robin离奇
的身世,处处显示出尾田先生的高明。

3) 尾田创作的是一部笑着流泪的漫画。
环顾当今《周年少年JUMP》的三大漫画,OP一定是最为轻松的一个了。Luffy,Usopp和
Chopper本身就是一个超级无敌高效组合……尾田先生在剧情中添加了非常非常多夸张的肢体
语言,可以让猥琐的敌人更加幽默,让恐怖的局势稍显放松。

然而OP努力在人格刻画上费劲心思,Shanks为拯救Luffy而断臂,Zeff为Sanji忍受饥饿,
Laboon厮守和Brook的约定,Vivi和草帽海贼团的惜别之情。这些OP迷们耳熟能详的场面,
每每重复的时候,仍旧能让大家泪流满面。

想到动画开始的时候真的是非常痛苦,不明白是制作成本,还是知名度不高的原因,OP动画
的前几十集真的是朴实到“粗糙”,长时间以来人们都称之为“一流漫画,二流动画”。即便是
到后来,OP仍旧没有引用广泛的3d技术,没有神乎其神的PK情景,绚烂的招式噼里啪啦打上
天昏地暗,我才明白,这才是OP,用真情信义感动大家的海贼王。
(草帽一伙与Vivi惜别)


4) OP的场景和角色非常多元化。
能坚持从头看到今天,我真是对画家佩服的五体投地。十年动画里涌现出来的各式新鲜玩意
儿,实在让人目瞪口呆。

那么那么多的建筑风格,那么那么多的恶魔果实,那么那么多的珍禽异类,那么那么奇怪的
大叔大婶,那么那么搞笑的敌人。。。尾田脑子里天天在想些什么呢?

5) OP自始自终保留了故事的神秘美感,这是能够在十年以来保持长久不衰的重要原因。
当然我在这里就不再讨论七武海是啥能力,四皇最后一位是谁了,那是些论坛上永久不会停
歇的话题(除了尾田直接说出来)。我只说一些重头戏:

历史文本之谜。空岛的老头曾经说过:“20年前也有一个家伙来到空岛,那家伙真是豪爽阿!
”紧接着,在空岛的历史正文旁边,草帽们发现了海贼王Roger的签名。在之后的水之都章节
里,我们已经发现Nico Robin隐藏着巨大的秘密,这显然印证了青稚所言:那个女人身后的
黑暗不是你们可以理解的。司法岛一战才告诉我们,恶魔之子Nico Robin正是可以读懂历史
正文的最后一人。由此即将揭示的空白历史到底描述了什么呢?

还有一些热度非常高的未解之谜:Luffy的父亲Dragon到底在进行什么样的事业,海贼王
Roger投降的原因呢?恶魔果实的来源?

漫画达到10年历程,我们还是被这样的迷题折腾的死去活来,这无疑让One Piece保持着神
秘的美感,延续着它的青春和生命力。
(革命家Dragon一切都是个迷)


6) 最后,轻松搞笑的海贼王,仍旧具有深度和前瞻性。
世界政府掩盖历史,无论如何,这都会让人联想到日本政府掩盖真相的事实。这个隐喻不言
而喻,真正的历史是属于人类的,它们一定不会被抹杀。

在尾田的笔下,人物焕发出来的精神和气质值得当代青年人学习:
这里有Sanji表现出来的骑士精神,Zoro为梦想坚持不懈,永不言放弃,Usopp唯唯诺诺,需
要不断超越自我,Brook作死守誓约的男人,Luffy重视朋友轻易,这一群爱理想的少年焕发
出来的自由注意精神,永远值得我们称道!

对海贼王的问候

最后在十周年之际,向他们致以问候!

Monkey D. Luffy,船长。没啥可说的。


三刀流 Roronoa Zoro,Zoro在OP世界里以传统职业——剑士的身份,通过苦行不断磨练自己,
最终达到终极目标————打败世界第一剑客 Dracule Mihawk!


航海士 Nami,在地理位置复杂,气候恶劣的Grand Line,一位杰出的航海士将会是顺利航
行的关键。清纯可爱的Nami对宝箱可是有着天生的直觉。


狙击手 Usopp,他用来完成远程狙击人物,在尾田的笔下,Usopp性格幽默,但身材柔弱,
胆小怕事,这使得Usopp更加趋近于常人,形象具有警世的价值!
值得一提的是,Usopp的声优是非常出名的山口胜平,你可以想象金田一,犬夜叉,怪盗基
德,乱马……实乃声优界的超级大腕。


厨师 Sanji,在Sanji看来,没有什么比荒岛上饿肚子更加恶劣的体验了。平日里的Sanji,
形象俊朗,见女士腿软,极富“骑士精神”。


医师 Tony Tony Chopper,队中唯一的一位动物角色,Chopper极富仁义的天性和羞怯可爱
的性情,使得他胜任医生重要一职。为其献声的因为皮卡秋誉满全球的大谷育江。


恶魔之子 Nico Robin,通晓古文字的她可以破解“冥王”之谜,而她黑暗离奇的身世将会在
司法岛章节揭开。从Miss All Sunday到草帽海贼团一员,尾田对于Robin花费了太多的手笔,
Nico Robin 也是我个人最喜欢的动漫女性角色。


造船师 Franky,在水之都对草帽海贼团建造新船——万里阳光号,这无疑是给海贼团们进行
了大换血。


音乐家 Brook,拥有88岁的年纪,他将是One Piece的第八个队员。Brook是一个借助恶魔果
实保持生命的骷髅,他有着儒雅的风度,却保留了很龌龊的个性(喜欢问女孩子:可以看一
下内裤吗……)。Brook的目标是绕行伟大航路一周,最后回到双子岬完成50年的约定。


第一只船,黄金梅利号(Going Merry),这是来自Usopp相知相识的朋友Kaya赠送给草帽们
的珍贵礼物。


第二之船,万里阳光号(Thousand Sunny),在司法岛事件之后,Franky送给草帽海贼团一
件大礼,借助自己无以伦比的造船工艺,新船具有更出色的性能,舒适的船舱和强大的功能。


最后是大神尾田荣一郎,现年33岁。他的画风深受著名画家鸟山明的影响。


OP十周年纪念!


-----------------
引用:
1. 少年JUMP官方网站
2. wiki -- onepiece
3. The Rise and Fall of Weekly Shonen Jump:
4. OnePiece官方网站

2008年10月6日星期一

[TIC++] C9. Inline Functions

代码阅读 <Thinking in C++>
Chapter 9. Inline Functions

一、什么是Inline

// c09: macro_side_effects.cpp
#include <fstream>
using namespace std;

#define BAND(x) (((x) > 5 && (x) < 10) ? (x) : 0)

int main()
{
ofstream out("macro.out");
// assure(out, "macro.out");
for (int i = 4; i < 11; i++) {
int a = i;
out << "a = " << a << endl << "\t";
out << "BAND(++a)" << BAND(++a) << endl;
out << "\t a = " << a << endl;
}

}
/* macro.out:
a = 4
BAND(++a)0
a = 5
a = 5
BAND(++a)8
a = 8
a = 6
BAND(++a)9
a = 9
a = 7
BAND(++a)10
a = 10
a = 8
BAND(++a)0
a = 10
a = 9
BAND(++a)0
a = 11
a = 10
BAND(++a)0
a = 12
*/

第一个例子,介绍了宏(macro)带来的负面效应。BAND(x)定义了一个三元运算符,C/C++中
会对选择语句中的与运算和或运算进行优化,(x)>5为false之后,将不会考虑(x)<10,而自
增运算符结合macro会产生预料之外的效果。

为了消除奇异,避免此类情况发生,我们引入了内联方法——

// c09: inline.cpp
#include <iostream>
#include <string>
using namespace std;

class Point {
int i, j, k;
public:
Point(): i(0), j(0), k(0) {}
Point(int ii, int jj, int kk)
: i(ii), j(jj), k(kk) {}
void print(const string &msg = "") const
{
if (msg.size() != 0)
cout << msg << endl;
cout << "i = " << i << ","
<< "j = " << j << ","
<< "k = " << k << endl;
}
};

int main()
{
Point p, q(1,2,3);
p.print("value of p");
q.print("value of q");
}

这里和以前并没有什么异常,这证明人的习惯是很可怕的……我们要说的是,以前在类中定义
的所有函数,都是内联函数(inline functions)。内联函数也只是存在于编译期内,编译的
时候,编译器会打开内联函数的代码,直接迭代到调用处。他起到了宏的作用,而且避免了
负面效用。

考虑一下print(const string&)方法,如果没有内联,那么print代码本身就要将this指针
放入堆栈,并且使用一次汇编语句CALL,大多数机器中,这段代码的长度会比内联展开的代
码量要大,而且花费时间也会更长。

二、使用内联
这个例子包含了三个代码段stash4.h,stash4.cpp,stash4test.cpp。

stash4.h

// c09: stash4.h
// inline functions

#ifndef __STASH4_H
#define __STASH4_H

class stash {
int size;
int quantity;
int next;
unsigned char *storage;
void inflate(int increase);
public:
stash(int sz): size(sz), quantity(0), next(0), storage(0) {}
stash(int sz, int init_quantity): size(sz), quantity(0), next(0), storage(0)
{
inflate(init_quantity);
}

~stash()
{
if (storage != 0)
delete []storage;
}

void *fetch(int index) const
{
if (index >= next)
return 0;
return &(storage[index * size]);
}

// declarations of non-inline functions
int add(void *element);
int count() const { return next; }
};


#endif // __STASH4_H

stash4.cpp

// c09: stash4.cpp
#include "stash4.h"
#include <iostream>
#include <cassert>
using namespace std;

const int increment = 100;

int stash::add(void *element)
{
if (next >= quantity)
inflate(increment);
int start_bytes = next * size;
unsigned char *e = (unsigned char*)element;

for (int i = 0; i < size; i++)
storage[start_bytes + i] = e[i];
next ++;
return(next -1);
}


void stash::inflate(int increase)
{
assert(increase >= 0);
if (increase == 0)
return;
int new_quantity = quantity + increase;
int new_bytes = new_quantity * size;
int old_bytes = quantity * size;
unsigned char *b = new unsigned char[new_bytes];

for (int i = 0; i < old_bytes; i++)
b[i] = storage[i];
delete [](storage);
storage = b;
quantity = new_quantity;
}
///:~

stash4test.cpp

// c09: stash4test.cpp
#include "stash4.h"
#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main()
{
stash int_stash(sizeof(int));

for (int i = 0; i < 100; i++)
int_stash.add(&i);
for (int j = 0; j < int_stash.count(); j++)
cout << "int_stash.fetch(" << j << ") = "
<< *(int*)int_stash.fetch(j)
<< endl;

const int bufsize = 80;
stash string_stash(sizeof(char) * bufsize, 100);
ifstream in("stash4test.cpp");
string line;
while(getline(in, line))
string_stash.add((char*)line.c_str());

int k = 0;
char *cp;
while ((cp = (char*)string_stash.fetch(k++)) != 0)
cout << "string_stash.fetch(" << k << ") = "
<< cp << endl;
}
/* result: 结果中途省略
int_stash.fetch(0) = 0
int_stash.fetch(1) = 1
int_stash.fetch(2) = 2
int_stash.fetch(3) = 3
...
int_stash.fetch(98) = 98
int_stash.fetch(99) = 99
string_stash.fetch(1) = // c09: stash4test.cpp
string_stash.fetch(2) = #include "stash4.h"
string_stash.fetch(3) = #include <fstream>
string_stash.fetch(4) = #include <iostream>
string_stash.fetch(5) = #include <string>
string_stash.fetch(6) = using namespace std;
string_stash.fetch(7) =
string_stash.fetch(8) = int main()
string_stash.fetch(9) = {
...
string_stash.fetch(31) = }
*/

stash中的简单方法在这里用到了inline,他们更加的高效和简洁。但也要留意两个最大的
方法在外部被定义,这样他们就不是inline方法,因为这样作不会带来任何效益。

三、向前引用特性

// c09: evaluation_order.cpp
// inline evaluation order

class forward {
int i;
public:
forward(): i(0) {}
// call undeclared function
int f() const { return g() + 1; }
int g() const { return 1; }
};

int main()
{
forward fwd;
fwd.f();
}

注意到,f()在g()声明或者定义之前调用了g(),这在inline函数世界里变得可能了。
_

2008年10月5日星期日

[TIC++] C8. Constants

代码阅读<Thinking in C++>
Chapter 8. constants

一、介绍常量

//safecons.cpp
#include <iostream>
using namespace std;

const int i = 100;
const int j = i + 10;
long address = (long)&j;
char buf[j + 10];

int main(void)
{
cout << "type a character & CR: ";
const char c = cin.get();
const char c2 = c + 'a';
cout << c2 << endl;
}

1. 宏,它只存在于预编译期(preprocessing),不占据内存空间,不包含类型信息。
2. 常量,也在编译期被翻译。通常情况下默认为内部链接,并且不会为const分配内存。这
被称为const folding原则。但是这个原则在很多情况下会被打破。
特例有很多,主要一点就是常量的外部引用。比如extern声明会强制建立外部链接,这
要求修饰的const也要提供地址(内存空间所在位置)。

二、常量遭遇指针

// c08: const_pointer.cpp
const int *u; // pointer to const int, init not required
int const *v; // still pointer to const int!
int d = 1;
int * const w = &d; // const pointer
const int * const x = &d; // const pointer to const object
int const * const x2 = &d;

int main(void){}

指针也可以被声明常量,这里带来两个歧义:一个是指针指向的东西被声明常量(pointer
to const);另一个是指针包含的地址本身被定义为常量(const pointer)。
上面的例子就列举了两种定义,还有另外指向常量的常量指针。
1) 指向常量的指针(`const*')
可以不被初始化
2) 常量型指针(`*const')
必须初始化
3) 指向常量的常量指针(`const * const')
必须初始化


// c08: pointer_assignment.cpp
int d = 1;
const int e = 2;
int *u = &d;
//! int *v = &e; // illegal -- a const
int *w = (int*)&e; // legal but bad practice

int main(){}

这里举例说明了指针赋值。
1. 常量不可以再被非常量直接引用(指针和引用均不可以)。
2. 但是他可以被强制转换,转换的安全性需要程序员负责。
3. 不可以在文件域内部对指向常量指针赋值(p2c,cp都不行)

三、常量返回值 return consts by value

// c08: constval.cpp
// return consts by value
// has no meaning for built-in type
int f3() { return 1;}
const int f4() { return 1;}

int main(void)
{
const int j = f3(); // Works fine
int k = f4(); // But this works fine too!
}

原子类型之间的赋值,只是拷贝,勿需考虑常量情况。


// c08: const_return_values.cpp
// const return by value
// result cannot be used as an lvalue

class X {
int i;
public:
X(int ii = 0);
void modify();
};

X::X(int ii)
{
i = ii;
}

void X::modify()
{
i++;
}

X f5()
{
return X();
}

const X f6()
{
return X();
}

void f7(const X& x) // pass non-const reference
{
x.modify();
}

int main()
{
f5() = X(1);
f5().modify();
// compile-time error
//! f6() = X(1);
//! f6().modify();
//! f7(f5());
//! f7(f6()); // 29
}

将返回值定义为常量,这对于原子类型来说没有意义。而在自定义类型中,却有很大不同。
NOTE!
如果函数返回一个常量类型的类对象,那么返回的值将不能成为左值(不可被定义和修改)。
而原子类型本身就不能成为左值,所以返回常量原子类型是无意义的。
f5()返回了非常量型,而f6()返回的是常量型的类对象,因此f6()的赋值和修改都是非法的
(f6不可以作为左值)。

四、传递和返回地址 passing and returning addresses

// c08: const_pointer2.cpp

void t(int*) {}

void u(const int *cip)
{
//! *cip = 2; // illegal -- modifies value
int i = *cip; // OK -- copies value
//! int *ip2 = cip; // illegal: non-const
}

const char *v()
{
// return address of a static array
return "result of function v()";
}

const int * const w()
{
static int i;
return &i;
}

int main()
{
int x = 0;
int *ip = &x;
const int *cip = &x;

// passing addresses
t(ip); // non-const* to non-const*
//! t(cip); // const* to non-const*
u(ip); // non-const* to const*
u(cip); // const* to const*

// returning addresses
//! char *cp = v(); // const* to non-const*, not OK
const char * ccp = v(); // const* to const*
//! int *ip2 = w(); // const* to non-const*, not OK!
const int * const ccip = w(); // const* to const*
const int * cip2 = w();
//! *w() = 1; // modify consts, not OK
}

无论是传递地址,还是返回地址。从const*到non-const*的传递(或赋值)都是被禁止的,
而如果选择将const转换为non-const,则需要确保转换的安全性。

五、常量成员的初始化

// c08: const_init.cpp
#include <iostream>
using namespace std;

class Fred {
const int size;
public:
Fred(int sz);
void print();
};

Fred::Fred(int sz) : size(sz) {}
void Fred::print() { cout << size << endl; }

int main()
{
Fred a(1), b(2), c(3);

a.print(), b.print(), c.print();
}

这里展示了构造方法的初始化列表的用法。形如:
Fred::Fred(int sz) : size(sz) {}
函数列表括弧后的单位就是,常量初始化的地方。唯有这里可以将类中定义的常量成员初始
化,实际上,他们是在进入函数体之前就进行了初始化。

六、const 成员遭遇 static

// c08: string_stack.cpp
// using static const to create a compile-time constant
// inside a class
#include <cstring>
#include <string>
#include <iostream>
using namespace std;

class string_stack {
static const int size = 100; // a static constant
const string *stack[size];
int index;
public:
string_stack();
void push(const string *s);
const string *pop();
};

string_stack::string_stack():index(0)
{
memset(stack, 0, size * sizeof(string*));
}

void string_stack::push(const string *s)
{
if (index < size)
stack[index++] = s;
}

const string *string_stack::pop()
{
if (index > 0) {
const string *rv = stack[--index];
stack[index] = 0;
return rv;
}
return 0;
}

const string icecream[] = {
"pralines & cream",
"fudge ripple",
"jamocha almond fudge",
"wild mountain blackberry",
"raspberry sortbet",
"lemon swirl",
"rocky road",
"deep chocolate fudge"
};

const int icsz = sizeof(icecream) / sizeof(*icecream);

int main()
{
string_stack ss;
for(int i = 0; i < icsz; i++)
ss.push(&icecream[i]);
const string *cp;
while((cp = ss.pop()) != 0)
cout << *cp << endl;
}

这里展示了C++ string的强大与方便。但是更重要的是,解释了compile-time constants的
用法。
static类成员意味着,无论用此类建立多少个对象,static成员实例都只会存在一个。
另外static const的特性要求,static const常量(原子型)必须在定义位置初始化。

从例子中可以看到,push持有const string*参数,pop()则返回const string*,还有
string_stack包含了一个const string*成员。这看来像一个锁链一样,一环套一环。
stack_stack包含const string *stack[],这要求在pop():
* 中间赋值必须用const传递, const string *rv;
* 返回值类型必须是const.

const string *icecream也要求push():
* 持有const参数

三个const约定了字符串以const形式亚栈,而除了push和pop以外,你再也无法更改
成员*stack[]。

七、const 成员方法

// c08: const_member.cpp
class X {
int i;
public:
X(int ii);
int f() const; // const member func
int g();
};


X::X(int ii): i(ii) {}
int X::f() const { return i;}
int X::g() {}

int main()
{
X x1(10);
const X x2(20);

x1.f();
x2.f();
//! x2.g(); // discards qualifiers
}

const成员方法,将会告诉编译器,他可以被常量对象调用。而没有被申明常量的方法,将
不可以被常量对象调用!

const成员方法和const函数(const func)有本质不同:
* const func 是返回一个常量值。
* func const 则是表明此成员方法可以被常量对象调用。他的使用方法是:

为了强调const方法,编译器强迫在const成员方法的声明和定义处都要加上const。

接下来细细观察const成员方法和普通成员方法的不同——


// c08: quoter.cpp
// random quote selection

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

class quoter {
int lastquote;
public:
quoter();
int get_lastquote() const; // const member func
const char *quote(); // member func which returns a const pointer
};

quoter::quoter()
{
lastquote = -1;
srand(time(0));
}

int quoter::get_lastquote() const
{
return lastquote;
}

const char *quoter::quote()
{
static const char *quotes[] = {
"Are we having fun yet?",
"Doctors always know best",
"Is it ... Atomic?",
"Fear is obscene",
"There is scientific evidence "
"to support the idea "
"that life is serious",
"Things that make us happy, make us wise",
};
const int qsize = sizeof quotes / sizeof *quotes;
int qnum = rand() % qsize;

while (lastquote >=0 && qnum == lastquote)
qnum = rand() % qsize;
return quotes[lastquote = qnum];
}

int main()
{
quoter q;
const quoter cq;

cq.get_lastquote();
//! cq.quote(); // 常量型对象无法调用非常量型成员方法
for (int i = 0; i < 20; i++)
cout << q.quote() << endl;
}

这里比较了const对象和普通对象处理的区别,另外揭示了const成员方法的条件:
如果成员方法体内更改了数据成员,那么它就不能声明为const。

比如const char *quoter::quote()更改了成员 lastquote(return语句),它将不能声明
为const;而int quoter::get_lastquote()则没有动,他就可以成为const成员方法,而且
可以被常量对象cq安全调用。

如果我确实声明了const成员方法,我又确实想更改类成员,我该如何是好?

八、bitwise vs. logical const

// c08: castaway.cpp
class Y {
int i;
public:
Y();
void f() const;
};

Y::Y(): i(0) {}

void Y::f() const
{
//! i++;
((Y*)this)->i++;
// Better: use C++ explicit cast syntax:
(const_cast<Y*>(this))->i++;
}

int main()
{
const Y yy;
yy.f();
}

bitwise const意思是说,此对象的每一数据比特都是永久不可变的,这样它的任何一部分
都不可改变;而logical const意思是,虽然从概念上说整个对象不可改变,但是可以从成
员基础上更改它本身。
这其实是编译器给const定义的两面性,一方面编译器小心翼翼地确保const对象是bitwise
的,另一方面,它又提供两个路径来通过_const成员函数_来更改数据成员。

以上就是第一个:转换掉const属性。无论是C还是C++语法,都一样可行。
这样带来的一个问题就是,你无法确认你的更改是否有效。。。

于是提供了第二个方法:


// c08: mutable.cpp
class Z {
private:
int i;
mutable int j;
public:
Z();
void f() const;
};

Z::Z(): i(0), j(0) {}

void Z::f() const
{
//! i++; // error - const mem func
j++;
}

int main()
{
const Z zz;
zz.f();
}

mutable 关键词告诉编译器,被定义为mutable的数据成员,在const成员方法内仍旧可以更
改。相对转型,显然mutable更加优雅一点。

TIC++也提到了只读属性(ROMability),如何指导编译器把const对象放到只读内存中呢?
这个条件比较复杂,任何可以改写数据成员的可能都会阻止只读化:
1) 不包含logical const特性(不能有mutable关键词,也不能有const转型)
2) class或者struct不可以包含任何自定义构造和析构方法
3) 也不可以包含有自定义构造和析构方法的超类和成员对象(子类没有限制)

九、volatile和类

// c08: volatile.cpp

class comm {
const volatile unsigned char byte;
volatile unsigned char flag;
enum { bufsize = 100 };
unsigned char buf[bufsize];
int index;
public:
comm();
void isr() volatile;
char read(int index) const;
};

comm::comm(): index(0), byte(0), flag(0) {}

// only a demo; won't actually work as an interrupt service routine:
void comm::isr() volatile
{
flag = 0;
buf[index++] = byte;
if (index >= bufsize)
index = 0;
}

char comm::read(int index) const
{
if (index < 0 || index >= bufsize)
return 0;
return buf[index];
}

int main()
{
volatile comm port;
port.isr(); // OK
//! port.read(0); // Error, read() not volatile
}

volatile关键字和const是极其相似的修饰词,只不过他们的意义不同。
通常情况下,编译器会告诉我们:我把此数据读入到了寄存器,但我保证不会再去碰它。下
次需要读取此变量,直接从寄存器中读取即可(而不需要到内存中寻找)。
而volatile告诉编译器:这个数据成员的活动,将会超过编译器的理解范围。如果需要读取
此数据,不要尝试从寄存器中取值,因为内存和寄存器可能已经出现了不同步。

同const一样,volatile对象也只能调用volatile成员方法。

十、总结
const一章的例子比其他章节要多,原因是const类型在C++中遇到了多种繁杂的情况,而且
他们也表现出来各自的特性。总结来说,我们探讨了以下情况:

* 原子类型申明为const(这里也讨论了const*和*const)
const声明意味着,这意味着该内存为只读属性。const存在于编译期,通常情况下默认为
内部链接,并且不会为const分配内存,这被称为const folding原则。const类型的局部
标识符必须在定义时候初始化。
const指针包括了指向常量的指针(const*)和常指针(*const),const*可以选择是否初始
化,但是他不可以显性修改指针指向的内存(*x = y illegal);*const不可以修改指针
本身((int*)a = (int*)b illegal)。

* 返回值为const
const返回值将不可作为左值,赋值修改都是非法的。而作为右值时候,const返回值的左
值必须为const,const到non-const的传递和赋值都是非法的。

* 传递参数为const
const到non-const的传递仍旧是非法的。要切记这一规则。

* 成员数据为const
const成员数据不可以在普通成员方法中更改。这里也给出了特例(const_init.cpp):
使用构造方法的初始化列表来完成常量数据成员的初始化。

* 成员数据为static const
static说明所有的成员实例都会共享一个static数据成员。static const额外要求必须在
定义位置实现初始化。

* 成员方法为const
首先,一个const成员方法将永远不会更改类的数据成员。
再次,声明为const的成员方法允许被const对象调用,而普通成员方法是不允许调用的。

* 对象为const
const对象表现出两个特性,一个是表面上他的整体不可改变(bitwise const),另一个
则是通过某些特殊方法实现更改(logical const)。
logical const的实现有两种方法,一个在const成员方法使用强制类型转换
(castaway.cpp),另一个是在类定义中给想要修改的值添加mutable修饰词
(mutable.cpp)。
_

2008年10月4日星期六

[TIC++] C7. Function Overloading and Default Arguments

代码阅读<Thinking in C++>
Chapter 7. Function Overloading and Default Arguments

一、C++中的union

//unionclass.cpp
#include
using namespace std;

union u {
private:
int i;
float f;
public:
u(int a);
u(float b);
~u();
int read_int();
float read_float();
};

u::u(int a)
{
i = a;
}

u::u(float b)
{
f = b;
}

u::~u(void)
{
cout << "u::~u()\n"; } int u::read_int(void) { return i; } float u::read_float(void) { return f; } int main(void) { u X(12), Y(1.9F); cout <<>

这个例子展示了union的用法。union仍就是C++的关键词,union也和class一样,他也可以
有constructor,destructor,成员方法甚至访问控制。
union和class的不同之处在于:
1. union含有独特的数据存储方式。
2. union不能被继承,这也是由特性1决定的。这使得它在C++里无法大展身手。

二、匿名union

//supervar.cpp
#include
using namespace std;

class supervar {
enum {
character,
integer,
floating_point,
} vartype;
union { // anonymous union
char c;
int i;
float f;
};
public:
supervar(char ch);
supervar(int ii);
supervar(float ff);
void print();
};

supervar::supervar(char ch)
{
vartype = character;
c = ch;
}

supervar::supervar(int ii)
{
vartype = integer;
i = ii;
}

supervar::supervar(float ff)
{
vartype = floating_point;
f = ff;
}

void supervar::print(void)
{
switch (vartype) {
case character:
cout << "character: " <<>

1. class supervar定义了一个enum类型,但是没有给此类型标注类型名。因为他只是用此
enum定义了一个变量实例vartype.
2. class supervar定义了一个union类型成员,这是一个匿名union(anonymous union),这
样在此类的成员中应用union成员,将不必使用成员操作符`.'。
如果匿名union定义在文件类型,_一定_要声明为static,这样他就有了内部链接。
_

2008年10月2日星期四

国庆第二天-疯狂的南山巡游

国庆到达第二天了,没有能出去爬山,也没有太多能力腐败了。想起酝酿许久的自行车旅
行,觉得今天真是个好日子。曾胖花费¥1400的自行车,多好的条件,于是立刻出发!

地图路线:
http://maps.google.com/maps/ms?ie=UTF&msa=0&msid=102563878911537749872.0004584479d8054cb6ac0
(注意卫星地图和交通图是不一致的,选择卫星地图)



首先从大新居委会出发,西转到深圳西车站,然后顺着月亮湾大道南下。沿途大部分交通工
具都是货车,在港湾和机场之间来回。不过一会,就来到南山区的西南角——妈湾。


接着从妈湾东转到赤湾,这一带是集装箱盛集的地方。


然后来到赤湾岛部分,伸出的小岛部分被集装箱覆盖。


然后进入到赤湾村,我在深圳不知道曾经坐过多少次226和204,这次就来到他们的终点站——
赤湾。赤湾的景点,最出名的就是宋少帝陵。
引自百度百科:宋少帝陵位于蛇口赤湾,是广东省境内仅有的黄陵。1278年蒙古入侵,南宋
王朝败退至广东新会,爆发"崖海大战".南宋政权军队被歼始尽,危急关头,宰相陆秀夫背
负仅8岁的少帝跳海殉国.少帝陵处还有陆秀夫背帝的石像.


经过兴海大道,蛇口港,终于来到海上世界。
曾经在07年的时候,我曾经赴海上世界参加百度贴吧深圳吧的聚会,认识了大熊,老男人,
大黄等人。跟着一帮人混达了很长时间,现在大家都是常聚的好友了。因此海上世界是一个
非常有意义的地方,让我想起了一年前的那个时侯。


傍晚时候沿着南海大道狂奔。由于夜色降临,路途急忙,没能拍到照片,敬请见谅。今日行
程也告一段落。

最后给第二主角露一小脸。祝愿大家开心!~


_