2008年9月30日星期二

从内存对齐到结构对齐

对于直接操控内存的程序员来说,数据对齐不该是个陌生的术语。它和程序如何良好运行,
甚至和程序是否能正常运转都有很大关系。




在这里我将先简要说明一下内存对齐的基础知识。这些内容来自于IBM developerworks

内存访问粒度 memory access granularity

程序员可以先将内存简单的认为是一个一维数组。在C和衍生语言里,char*代表着这样一块
内存,而在Java中甚至会用到byte[]来表示内存块。

程序员眼中的内存


我们可以看到内存从0到最大值顺序排列,每一个内存单元都被一个数字索引,这就是内存
地址。

但是现代的处理器却往往不是以一个比特为单位来访问内存的,而是以2,4,8,16,甚至
32个字节为单位。我们把这个尺寸称之为内存访问粒度,用来表示处理器一次存取的内存大
小。

处理器眼中的内存


内存对齐基础知识

我们用固定比特读取这样一个例子,来分析内存对齐。

首先是访问粒度为1比特的处理器

这和程序员脑子中的内存模型是一致的,每次读取一个比特。那么从地址0读取和地址1读
取4个字节,情形完全相同。

来看看访问粒度为2的情况

从0读取的时候,粒度为2的处理器将会花费1/2的时间来读取同样大小的内存(与粒度1相
比)。读取4个字节,将只需要2次存取。

然而,地址1的读取则大不相同,因为地址1没有落在处理器的内存边界上,处理器的活儿就
多了。这样的地址被成为“不对齐地址”。由于地址1没有对齐,那么粒度为2的处理器将会多
花费1个存取次数,显然降低了访问速度。

最后我们来查看粒度为4的处理器

从边界地址上读取4个字节,这里只访问了一次内存。但是从“不对齐地址”处读取,依然会使
读取次数加倍。

懒惰的处理器

回想到我们上面所陈述的3种情况,我们来总结一下非对齐地址的读取。

首先处理器需要从非对齐地址读取第一个内存块,然后将不需要的内存位移出内存块,然后
再从非对齐地址读到第二个内存块,同样移除不需要的内存地带。最后将两者合并到一起,
放置到寄存器里。OH,这真是一个麻烦的任务~

而且……有些处理器不愿意为你作这样的工作。

原先的68000就是粒度为2的处理器,他却缺乏处理非对齐地址的相应电路,因此当遇到此类
访问的时候,处理器会抛出异常。早期的Mac OS对待这种异常非常不友好,很可能会要求用
户重启计算机。听起来实在无奈~

现在所有的处理器都会使用有限数量的晶体管来完成此类任务,在这类问题上会更上一层楼。
但无论怎样,非对齐地址的内存访问始终大大落后于对齐地址,这里处理器必须面对的一个
现实。

编译器中的数据对齐

struct P{
char a;
long b;
char c;
};

struct P的尺寸会是多少?很多程序员会不假思索的回答:6(1+4+1)。他们假想的元素布局
可能会是这样:
Field TypeField NameField OffsetField EndField Size
chara00
1
longb144
charc55
1
Total Size in Bytes:6

然而,问问你的编译器就很知道,sizeof(struct P)是12 (GCC4)。真实的结构体布局是:
Field TypeField NameField OffsetField EndField Size
chara00
1
longb4
7
4
charc8
8
1
Total Size in Bytes:12

这和我们刚才所说的内存对齐紧密相连。为了保持较高的内存访问效率,编译器也会在数据
结构存储上应用到数据对齐。对齐原则针对平台,编译器,可能会有较大不同,这也是这篇
文章的重点。

首先引入对齐模数概念,编译器会要求数据结构的成员地址是某个数k的倍数,这个常数k则
被称为该数据类型的对齐模数 (alignment modulus,余下简称modulus)。它是成员地址的
公约数,也是成员偏移量的公倍数。

结构体对齐原则

(下面的对齐原则,仅仅针对于GCC)

struct A {
char a;
char b;
};
//sizeof(struct A) = 2;

struct B {
short a;
short b;
short c;
};
//sizeof(struct B) = 6;

struct C {
int a;
int b;
int c;
};
//sizeof(struct C) = 12;

这样看很简单。sizeof似乎没有什么问题。char,short,int的长度分别是1,2,4,这样可以
轻而易举的加到结果。


struct D {
short a;
int b;
unsigned c;
};
//sizeof(struct D) = 12;

这里先确定对齐模数,针对VC和GCC编译器,他们各有不同。
cl.exe:
选取成员中的最宽类型的字长为modulus(对齐模数)
GCC:
同样选取成员中字长最大值,但是对齐模数只在三个值间选择,他们是1,2,4。
这意味着GCC结构体的最大modulus只能是4。

struct D中最长类型为int,那么它在GCC中的对齐modulus是sizeof(int) = 4;

同样,这里阐述结构体对齐的三条原则:
1) 结构体变量的首地址必须能够被modulus所整除
2)* 结构体每个成员相对于首地址的偏移量(Offset)必须能被modulus整除,如有需要,在
成员间填充空白字节(这被称为internal padding)

struct D的成员 a 使用到了internal padding。

3) 结构体总大小必须是modulus的整数倍,如有需要,在最末元素填充字节(trailing
padding)

成员C就用到了 trailing padding。

根据以上原则我们可以列出struct D内存布局图:
Field TypeField NameField OffsetField End
Field Size
short
a01
2
/
(padding)
2
3
2
int
b
4
7
4
char
c8
8
1
/
(padding)
9
11
3
Total Size in Bytes:12


struct E { // offset/data size
short a; // 0/2,空位填充
int b; // 4/4
char c; // 8/1,邻接元素填充
short d; // 10/2
};
//sizeof(struct E) = 12;

内存布局:
Field TypeField NameField OffsetField EndField Size
short
a012
/
(padding)
2
3
2
int
b
4
7
4
char
c
8
8
1
/
(padding)
9
9
1
short
d
10
11
2
Total Size in Bytes:12

这里引用到了GCC的压缩存储。压缩存储要求,结构体的成员会紧凑的将成员压缩到一个
modulus里。GCC的强压缩方式,又可以忽略元素类型,将不同类型的成员压缩到一个
modulus里。

然后阐述的原则将会对对齐原则2重新定义:
4) 考虑到GCC的压缩存储方式,邻近成员会合并到一个modulus里,合并过程直到可能超出
modulus大小时停止
5) 合并在一个modulus里的数个成员,将会按照“子结构体”的方式来存储。比如拥有更小的
mini modulus,按照mini modulus排列数据成员

struct E 中的元素c,d就使用到了压缩存储。那么c和d将会按照“子结构体”方式存储,
他们拥有的modulus是2 (sizeof(short))。


/* modulus只能考虑基本数据类型,因此结构体成员还需追溯其成员类型 */
struct F {
char a;
struct A b; //
};
//sizeof(struct F) = 3;

对齐模数只能考虑原子数据类型,因此:
6) 成员结构体需要打破结构体边界,追溯成员类型。数组也是一样,modulus将和数组大小
无关

union和class对齐

除了struct以外,C中包含有union,而在C++中更是包含了人见人爱的class支持。他们同
struct一样,是属于符合类型,允许定义多个不同类型的成员。

我们来看看union的特性:

union G {
char a;
int b;
char c[10];
};

通过实验,我们看到结果:sizeof(union F) = 12;
* union仍旧选用最宽数据类型作为modulus(和struct一样)

union G 的modulus是4 (int)。

* 而union采用完全完全压缩方式,它占有的总内存大小,将和最大成员字长一样。

union G的最大成员是char c[10],再考虑modulus=4,因此取总字长为12。

然后我们写出union G的内存布局。
Field TypeField NameField OffsetField EndField Size
chara00
1
int
b0
3
4
charc0
1112
Total Size in Bytes:12




class H {
private:
int a;
static char b; // static 不占空间
short c;
unsigned short d;
void func0(); // public func不占空间
public:
int e; // 公私有对sizeof无影响
virtual void func1(); // 一个类的virtual只能占据一个指针
void func2();
virtual void func3(); //
virtual void func4();
virtual void func5(); //
};
//sizeof(H) = 16;

class概念是C++的根基,作为C的超集,C++在class定义中添加了非常多的特性。我们简单
说一下class对齐和内存占用原则:

1) modulus大小和原子类型排列的原则和struct相同
class H将仍旧选取4(int)为对齐modulus。

2) 内存占用和访问控制修饰符无关(比如private,class,protected)
成员a, e各自享用4个字节。

3) static 变量和成员函数,在class结构中不占用空间
static char b将不占内存空间。

4) 如果类包含虚函数,那么要算上一个指针的空间(虚指针)
所有的虚函数(virtual)都共享一个虚指针,原class字长+4

5) 子类将使用基类的modulus。由于继承了成员,子类也将会包含基类的所有字节大小,在
此基础上定义的新成员则按照上述3个法则计算


引用:
1. wikipedia wiki: sizeof
2. Rentzsch, Jonathan. "Data alignment: Straighten up and fly right."
3. king. "sizeof(结构体)和内存对齐"

2008年9月26日星期五

[TIC++] C5. Hiding the implementation

代码阅读<Thinking in C++>
Chapter 5. Hiding the implementation

一、友元


//friend.cpp

// public, private, protected
// friends

struct X;
struct Y {
void f(X*);
};


struct X {
private:
int i;
public:
void initialize();
friend void Y::f(X*); // struct member friend
friend struct Z; // Entire struct is a friend
friend void g(X*, int); // Global friend
friend void h();
};

void X::initialize()
{
i = 0;
}

void Y::f(X *x)
{
x->i = 47;
}

struct Z {
private:
int j;
public:
void initialize();
void g(X *x);
};

void Z::initialize()
{
j = 99;
}

void Z::g(X *x)
{
x->i += j;
}


void g(X *x, int i)
{
x->i = i;
}

void h()
{
X x;
x.i = 100;
}

int main()
{
X x;
Z z;
z.g(&x);
}

此代码示范了friend友元的用途,声明友元的类在告诉大家,此友元可以访问我的私有成员。
* 从struct X的定义可以看出,在public区域定义了四种友元,他们都可以试图访问修改私
有成员i,当然也包括自己的成员函数。

全局友元函数 global friend
在函数体内,可以任意修改struct X类对象的成员i。
类成员作为友元 struct member friend
可以更改传递的X*参数。
整个类作为友元 entire struct
这样此类的所有成员函数,都拥有友元性质。

二、嵌套的友元


//nested_friend.cpp

#include <iostream>
#include <cstring>
using namespace std;

const int sz = 20;

struct Holder {
private:
int a[sz];
public:
void initialize();
struct Pointer;
friend struct Pointer;
struct Pointer { // nested struct friend
private:
Holder *h;
int *p;
public:
void initialize(Holder *h);
void next();
void previous();
void top();
void end();
int read();
void set(int i);
};
};

// type class::nested_class::variable;
void Holder::initialize()
{
memset(a, 0, sz * sizeof(int));
}

void Holder::Pointer::initialize(Holder *rv)
{
h = rv;
p = rv->a; // access private member of super class
}

void Holder::Pointer::next()
{
if (p < &(h->a[sz - 1]))
p++;
}

void Holder::Pointer::previous()
{
if (p > &(h->a[0]))
p--;
}

void Holder::Pointer::top()
{
p = &(h->a[0]);
}

void Holder::Pointer::end()
{
p = &(h->a[sz-1]);
}

int Holder::Pointer::read()
{
return *p;
}

void Holder::Pointer::set(int i)
{
*p = i;
}

int main()
{
Holder h;
Holder::Pointer hp, hp2;
int i;

h.initialize();
hp.initialize(&h);
hp2.initialize(&h);
for (i = 0; i < sz; i++) {
hp.set(i);
hp.next();
}
hp.top();
hp2.end();
for (i = 0; i < sz; i++) {
cout << "hp = " << hp.read()
<< ", hp2 = " << hp2.read() << endl;
hp.next();
hp2.previous();
}
}
/* result:
hp = 0, hp2 = 19
hp = 1, hp2 = 18
hp = 2, hp2 = 17
hp = 3, hp2 = 16
hp = 4, hp2 = 15
hp = 5, hp2 = 14
hp = 6, hp2 = 13
hp = 7, hp2 = 12
hp = 8, hp2 = 11
hp = 9, hp2 = 10
hp = 10, hp2 = 9
hp = 11, hp2 = 8
hp = 12, hp2 = 7
hp = 13, hp2 = 6
hp = 14, hp2 = 5
hp = 15, hp2 = 4
hp = 16, hp2 = 3
hp = 17, hp2 = 2
hp = 18, hp2 = 1
hp = 19, hp2 = 0
*/

这里的Holder类中又包含Pointer类定义,这是个嵌套类。例子在展示子类作为友元时的用
途。
在这里定义了Holder类对象h,和子类Pointer对象hp,hp2,他们包含有h的指针,和父类私
有成员a的位置。作为友元类,hp和hp2可以访问、修改父类对象h的私有成员int a[];

三、初识构造函数和析构函数


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

class Tree {
int height;
public:
Tree(int initialHeight);
~Tree();
void grow(int years);
void printsize();
};

Tree::Tree(int initialHeight)
{
height = initialHeight;
}

Tree::~Tree()
{
cout << "inside tree destructor " << endl;
printsize();
}

void Tree::grow(int years)
{
height += years;
}

void Tree::printsize()
{
cout << "Tree height is " << height << endl;
}

int main()
{
cout << "before opening brace " << endl;
{
Tree t(12); //constructor called
cout << "after Tree creation" << endl;
t.printsize();
t.grow(4);
cout << "before closing brace" << endl;

//destructor called(endline of t)
}
cout << "after closing brace " << endl;
}
/* result:
before opening brace
after Tree creation
Tree height is 12
before closing brace
inside tree destructor
Tree height is 16
after closing brace
*/

例子展示了constructor和destructor的性能。
* constructor在建立对象的时候,由编译器插入语句,完成初始化行为,在对象生命期内
只执行一次。
* destructor相反,在注销对象的时候执行。
* 代码中使用了{}圈定对象Tree T的生命域,它在`}'之后被注销。
下面讲述constructor一个特性。


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

class X {
public:
X();
};

X::X() {}

void f(int i)
{
if (i < 10) {
// crosses initialization of `X x1'
// goto jump1;
}
X x1;

jump1:
switch (i) {
case 1:
X x2;
// crosses initialization of `X x3'
break;
// case 2:
X x3;
break;
}
}

int main()
{
f(9);
f(11);
}

这是一个诡异的例子,首字符注释段取消注释之后,编译器将报错: `cross
initialization'。GCC将不允许goto,case语句等,来跳过任何对象的定义(初始化)阶段。

四、定义stack


//stack.h
#ifndef _STACK_H
#define _STACK_H

class stack {
struct linklist {
void *data;
linklist *next;
linklist(void *dat, linklist* nxt);
~linklist();
}* head;
public:
stack();
~stack();
void push(void *data);
void *peek();
void *pop();
};

#endif // STACK_H


//stack.cpp
#include "stack.h"

stack::linklist::linklist(void *dat, linklist *nxt)
{
data = dat;
next = nxt;
}

stack::linklist::~linklist() {}

stack::stack()
{
head = 0;
}

void * stack::peek()
{
if (head != 0)
return head->data;
else
return 0;
}

void stack::push(void *dat)
{
head = new linklist(dat, head);
}

void * stack::pop()
{
if (head == 0)
return 0;
void *result = head->data;
linklist *oldHead = head;
head = head->next;
delete oldHead;
return result;
}

stack::~stack()
{
if (head != 0)
return;
}


//stacktest.cpp
#include "stack.h"

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
// requireArgs(argc, 1);
ifstream in(argv[1]);
if(in == 0)
return -1;
// assure(in, argv[1]);

stack textlines;
string line;
while (getline(in, line))
textlines.push(new string(line));
string *s;
while ((s = (string *)textlines.pop()) != 0) {
cout << *s << endl;
delete s;
}
}
/* 执行命令 ./a.out Makefile
* 将按行逆序打印Makefile内容。
*/

stack.h,stack.cpp,stacktest.cpp是链表式堆栈的实现。
* linklist是节点数据结构的实现,他是stack数据结构的成员。
* 无论对于父子类对象,他们都会有自己的构造函数和析构函数。
* push函数非常漂亮!
* 使用new 和 delete,来分配堆区空间,这会使得C++代码更加优雅。

2008年9月25日星期四

Gentoo配置无限网卡ipw2100

无限网卡的配置让我倍受折腾,在一两个月内,我不知道翻阅了多少资料,问了多少人,尝
试了多少遍,和放弃了多少遍。昨天我转换一下思维,直接从命令行里敲iwconfig命令,居
然连接上了。因此赶快留下记录,供以后查阅。

硬件环境
IBM Thinkpad R50
- Intel PRO/Wireless LAN 2100 无线网卡
- ATI Radeon Mobility 7500

软件环境

linux-2.6.26-gentoo, i686
Gentoo profile: default/linux/x86/2008.0/desktop
gcc-4.3.1
glibc-2.8_p20080602-r0

配置方案

内核驱动 + ipw2100固件 + iwconfig

下面简述安装配置过程;

一、安装内核驱动

ipw2100作为应用广泛的网卡,已经在内核里加入支持,而且你在gentoo的portage tree中
也找不到ipw2100的驱动了。因此,你只需要在内核里编译即可(建议编译成模块),编译
后的模块名称为ipw2100。

编译选项为:

Symbol: IPW2100 [=m]
│ Prompt: Intel PRO/Wireless 2100 Network Connection
│ Defined at drivers/net/wireless/Kconfig:126
│ Depends on: NETDEVICES && !S390 && PCI && WLAN_80211
│ Location:
│ -> Device Drivers
│ -> Network device support (NETDEVICES [=y])
│ -> Wireless LAN
│ -> Wireless LAN (IEEE 802.11) (WLAN_80211 [=y])
│ Selects: WIRELESS_EXT && FW_LOADER && IEEE80211


二、安装firmware

firmware是不公开的驱动程序需要的数据。ipw2100的固件在gentoo里可以直接安装,安装
后的固件在 /lib/firmware 中。

wickyl@ ~: eix ipw2100
[I] net-wireless/ipw2100-firmware
Available versions: (1.3) 1.3
Installed versions: 1.3(1.3)(04:50:13 PM 08/31/2008)
Homepage: http://ipw2100.sourceforge.net/
Description: Firmware for the Intel PRO/Wireless 2100 3B miniPCI adapter
wickyl@ ~: sudo emerge ipw2100


至此ipw2100的驱动已经安装完毕了,加载模块ipw2100正确之后,可以用ifconfig查找到新
增的网卡借口(我的是eth1),也可以在dmesg中发现模块打印的信息。

wickyl@ ~: sudo ifconfig
eth1 Link encap:Ethernet HWaddr 00:0c:f1:0c:87:17
inet addr:172.1.2.191 Bcast:172.1.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:13955 errors:28 dropped:0 overruns:0 frame:0
TX packets:2493 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2311035 (2.2 MiB) TX bytes:507580 (495.6 KiB)
Interrupt:11 Base address:0x2000 Memory:c0204000-c0204fff

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:265 errors:0 dropped:0 overruns:0 frame:0
TX packets:265 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:20472 (19.9 KiB) TX bytes:20472 (19.9 KiB)


wickyl@ ~: dmesg | grep ipw2100
ipw2100: Intel(R) PRO/Wireless 2100 Network Driver, git-1.2.2
ipw2100: Copyright(c) 2003-2006 Intel Corporation
ipw2100: Detected Intel PRO/Wireless 2100 Network Connection
firmware: requesting ipw2100-1.3.fw


三、安装必备软件支持

必备的软件包括wireless-tools, dhcpcd,gentoo的官方文档中提醒你,无线的配置工具可
以使用 wpa_supplicant 和 iwconfig(wireless-tools),但是我始终无法使用
wpa_supplicant 成功配置无线网卡,如果你成功了,请告诉我,让我也惊讶一下。

wickyl@ ~: sudo emerge wireless-tools dhcpcd

These are the packages that would be merged, in order:

Calculating dependencies... done!
[ebuild R ] net-wireless/wireless-tools-29 USE="nls -multicall" 0 kB
[ebuild R ] net-misc/dhcpcd-4.0.1 USE="compat zeroconf" 0 kB
...


四、配置网络接口 (eth1)

网络接口的配置,在 gentoo 里显得规范而且有特色,它的主配置文件在 /etc/conf.d/net
里,在官方文档中详细的介绍了无线网络服务的配置。

配置步骤分两步,首先建立 /etc/init.d/net.eth1 链接。

wickyl@ ~: ln -s /etc/init.d/net.lo /etc/init.d/net.eth1


然后是配置 /etc/conf.d/net,以下是我的配置(eth1):

#### wireless networking configuration
#### ipw2100 + wpa_supplicant
config_eth1=("dhcp");
modules=("iwconfig");
key_<essid1>="[1] <passwd> key [1] enc open"
key_<essid2>="off"

preferred_aps=("<essid1>" "<essid2>")


简单说明一下,我的eth1是使用dhcp来配置网络和路由器地址的,这需要你安装dhcpcd。我
从没有使用静态地址配置无线网络,那样显得很不实际。

modules=("iwconfig");
指定无线配置接口,可选项是:iwconfig/wpa_supplicant。

key_<essid1>="[1] <passwd> key [1] enc open"
key_<essid2>="off"
指定AP接入点和加密方式。这里的<essid1>使用了开放方式,需要用<passwd>指定密码。
而<essid2>则没有设立密码,一般的公网就是不设定密码的。

preferred_aps=("<essid1>" "<essid2>")
preferred_aps指定AP的优先顺序。你也可以添加接入点APs的更多选项,包括屏蔽接入点等
等。这在手册中都有介绍。

五、启动 net.eth1 服务来完成配置


wickyl@ ~: sudo rc-config start net.eth1


配置完成之后,可以用iwconfig来查询信息。这是南山图书馆一个AP的配置信息。

wickyl@ ~: sudo iwconfig eth1
Password:
eth1 IEEE 802.11b ESSID:"nslib" Nickname:"ipw2100"
Mode:Managed Frequency:2.412 GHz Access Point: 00:02:6F:05:CF:7A
Bit Rate=11 Mb/s Tx-Power:16 dBm
Retry short limit:7 RTS thr:off Fragment thr:off
Encryption key:off
Power Management:off
Link Quality=98/100 Signal level=-49 dBm
Rx invalid nwid:0 Rx invalid crypt:0 Rx invalid frag:0
Tx excessive retries:0 Invalid misc:4 Missed beacon:4



为什么我消耗老长的时间?

长久以来,我都没有爱好选择习惯,如果一个事物有更多的好处,那我偏向于倾注经历在此
事物上。
在gentoo的手册上,明确写着:“wpa_supplicant is the best choice”。我听之任之,一
直都在使用 wpa_supplicant 来坚持配置无限网卡。

szlug上,我张贴询问了配置问题,上面列举了我怎么在这个问题上消磨生命的。


ieee80211_crypt: registered algorithm 'NULL'

这东西我不知道所言何物,按照rae老兄的指示,我用Ubuntu LiveCD测试了一下,乖乖!
Gnome下的NetworkManager工具直接提示我输入无限网接入密码。实在是太自卑了!
我查看了一下dmesg info。

ieee80211_crypt: registered algorithm 'WEP'

这显然是ieee80211加密算法的问题。我重新编译内核,加入了ieee80211_crypt_wep模块。

然后又是启动服务的时候报错,我使用的是wpa_supplicant。


ipw2100: Fatal interrupt. Scheduling firmware restart.
ipw2100: exit - failed to send CARD_DISABLE command

我始终没有尝试过转换角度,如果早些时候可以退一步,来使用iwconfig配置,如果我像昨
天一样轻轻的敲入一个iwconfig命令,我就可以发现世界是多么的美好。

事情很奇妙,不是么?有选择,就有出路。这就是Linux。

2008年9月23日星期二

9月21日移步老虎涧

17爬爬吧在最近有了突飞的进步:它的QQ群成为高级群,开始过多容纳更多的驴友;爬爬吧
也有了自己的队旗;他也有了自己的贴吧。

活动组织:http://tieba.baidu.com/f?kz=478862495
GoogleMaps:
http://maps.google.com/maps/ms?ie=UTF&msa=0&msid=102563878911537749872.000456f0206e6b722a6c2

这次的活动,选取了梧桐山的老虎涧。这是非常适合溯溪登山,前往梧桐山的一条捷径。但
是这次的活动人数过多(35人),因此没有选择达到大梧桐峰作为目的地。行程将要选择经
历三个瀑布,然后环山路到恩上村,午饭后搭乘饭馆老板的车,沿着新开的盐田高速下山。

这就是“17爬爬吧”的队旗,上面清楚的写上Q群的号码,和群的性质:深圳户外。


这次的人非常之多,也有了明细的分工。包括领队黄瓜,中间Wick(我),结尾老狼,还有
队医米卡飞飞。装备完善,可谓作足了准备阿!


马上临近十一了,黄金周的气氛洋溢在每个人的脸上。
除了我们以外,仍旧有别的户外组织和我们一起同行。达到第一个瀑布,已经有小伙子们按
捺不住,跳入清泉。


第二个瀑布最热闹,大堆的小伙子前赴后继跳入瀑布中,有如下饺子一样。


老虎涧坡度要比马峦山陡峭,水流更多,岩石更滑,因此难度也更大。如果要选择溯溪,一
定要有防滑鞋。还有不爱穿袜子的女孩子,一定要注意了,这对爬山都相当不利。



梧桐山也是一个出色的绿色生态系统,有着繁多的动植物品种。但是沿途只发现蚂蚁和蜘蛛
……


第三个瀑布,很是漂亮。从这里分道,一队直奔大梧桐峰,而大部队则环山绕道恩上村。


恩上村一路平坦,会经过梧桐山的爬坡。小队推进速度很快。


面朝沙头角的一面,可以看到盐田港,对面就是香港。很多联通的朋友可以收到香港的信号。
还可以看到Minsk航母。


登山路途艰辛,到达恩上村时开始一路平坦。沿途汗水和足迹,印证老虎涧的历程。


恩上大坝,风景优美。这里奉上集体照。



到达恩上村时候已经下午两点,我们开始坐下。村庄里的景色非常优美,和马峦山不相上下。
这里还保持着田园耕作的方式,很多作物可以自给自足,无公害的绿色蔬菜也是美味。
在等菜的当儿,大家伙儿按捺不住,开始搓起麻将。广东麻将有如内地的扑克一样普及啊~


饭馆老板送我们下山,下山路将经过新修的盐田高速公路,环山绕道而下,有种GT赛车的感
觉。(这是沿途汽车抗议我们的偷拍)


这是山脚下的盐田区法院,旁边是盐田区文化馆。从外表来看,宏伟的难以想象啊……


谢谢观赏~

2008年9月20日星期六

[TIC++] C3. C in C++

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


一、指针运算

//ptr_math.c
#include <iostream>
using namespace std;

#define P(EX) cout << #EX << ": " << EX << endl;

int main(void)
{
int a[10];

for (int i = 0; i < 10; i++)
a[i] = i;

int *ip = a;
P(*ip);
P(*++ip);
P(*(ip + 5));

int *ip2 = ip + 5;
P(*ip2);
P(*(ip2 - 4));
P(*--ip2);
P(ip2 - ip);
}
/* result:
*ip: 0
*++ip: 1
*(ip + 5): 6
*ip2: 6
*(ip2 - 4): 2
*--ip2: 5
ip2 - ip: 4
*/

C中强大的指针在C++里得到很好的保留,指针的灵活也给初学者带来困惑
1. `#EX'宏定义,此宏定义,将直接迭代打印变量名称(而不是变量值)
2. 指针是带有其类型信息的,根据信息,编译器理解指针运算的单位。
int *pi;
char *pc = (char*)pi;
pi++; //int型指针跳转4个字节
pc++; //char指针增加1个字节

二、介绍引用

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

void f(int& r) //reference
{
cout << "r = " << r << endl;
cout << "&r = " << &r << endl;
r = 5;
cout << "r = " << r << endl;
}

int main(void)
{
int x = 47;

cout << "x = " << x << endl;
cout << "&x = " << &x << endl;
f(x);
cout << "x = " << x << endl;
}
/* result:
x = 47
&x = 0xbfbb29ac
r = 47
&r = 0xbfbb29ac
r = 5
x = 5
*/

引用是C++中的新概念,相当于给变量起了一个新名字。通常用于参数传递。
* C中允许传值,和传址两中方式,在C++又包含了传递引用。
* 传递引用和传址不一样的是,调用方只是传递变量,而非该变量的指针,被调用函数则心
领神会,隐性得到参数地址(&r = &x)。

三、C++中的类型转换

//dynamic_cast.cpp
class Base
{
public:
int m_iNum;
virtual void foo() {}; //缺少虚函数会报错
};

class Derived: public Base
{
public:
char *m_szName[100];
};

int main(void)
{
Base *pb;
Derived *pd1 = static_cast<Derived *>(pb);
Derived *pd2 = dynamic_cast<Derived *>(pb);
}

(非TIC++代码)
dynamic_cast 是C++引入的类型转换方法,其他另外还有三个标准方法,这里将举例介绍
dynamic_cast和const_cast。
static_cast是静态类型转换,他仅要求:不可以在无关类型间转换。在基类和子类的指针
和引用转换过程中,其下行转换是危险的(即便可能通过),因为static_cast不会进行类
型转换。
dynamic_cast则弥补了这个缺陷,上下行转换都会进行类型检查,不符合的时候会返回NULL
指针,表明转换失败(而不是报错)。值得注意的是,下行转换的时候dynamic_cast要求基
类保留纯虚函数,否则会报错(因为纯虚函数拥有包含运行时类型信息的虚函数表,无虚函
数是没有虚函数表的)。


//const_cast.cpp
int main(void)
{
const int i = 0;

int *j = (int*)&i; // deprecated form
j = const_cast<int*>(&i); // preferred, only int* to int*
// long *l = const_cast<long*>(&i); // int* to long*? invalid!
volatile int k = 0;
int *u = const_cast<int*>(&k);
}

const_cast<TYPE>(expression)仍然是C++的标准类型转换方法。他用来转换const和
volatile(比如常量指针转换为非常量指针)。有一个要求,那就是要求TYPE和express类
型必须一致。

[TIC++] C2. Making objects

代码阅读 <Thinking in C++>
Chapter 2. Making Objects


一、第一个C++程序

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

// cout is an object
// `<<' is an overloaded operator
// `namespace' is used to prevent name collisionx
int main(void)
{
cout << "Hello world! "
<< "I am " << 8
<< " today!"<< endl;
}

这是C++的第一个代码程序。和大多其他语言的教材一样,Bruce Eckel也选择了`Hello
world!'这样一个程序来开启C++之门。这个程序的框架和C极其相似,有头文件包含,main
函数等等。

1. <iostream>是C++的标准头文件,这些头文件包含了标准数据结构定义和函数的索引。他
们位于: /usr/lib/gcc/i686-pc-linux-gnu/4.3.1/include
(在`gcc -v'中可以查询configure项:includedir)
2. namespace是C++引入的概念,为了避免命名污染,C++用namespace来指定一些变量的可
视范围(scope),`using namespace std'是使用std标准命名空间。
3. cout是iostream中定义的一系列标准对象,`<<'则实现了cout对象的运算符重载,作用
是将字符等对象输出到标准输出对象cout。

iostream定义标准对象有:
* cout
一个ostream类的对象,用来打印数据到标准输出设备(STDOUT)
* cerr
也是ostream类的对象,将非缓冲输出数据写入到标准错误设备(STDERR)
* clog
和cerr相似,但是使用缓冲方式输出
* cin
istream,用来从标准输入设备中读取数据(STDIN)

二、numconv.cpp

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

int main(void)
{
int number;

cout << "please input the number : ";
cin >> number;
cout << "value in octal = 0" << oct << number << endl;
cout << "value in hex = 0x" << hex << number << endl;
}

程序从终端读取一个整数,以八进制和十六进制将其打印,展现的是重载运算符的灵活用法。
输出说明:
1. 如果输入起始字符不是整数,则打印无规则数据。(?)
2. 如果输入起始字符是整数,则一直读取到不是字符或者换行符,然后打印。

三、文件流介绍

//scopy.cpp---------------------
#include <fstream>
#include <iostream>
using namespace std;

int main(void)
{
ifstream in("Makefile");
ofstream out("output");
string s;

// don't worry about how much storage to allocate for a string
// just add things to it!
while (getline(in, s))
out << s << "\n";
}

读取文件Makefile,按行打印到文件output(一次输出一行)。
* ifstream和ostream是来自<fstream>的类,负责建立文件流对象(读和写)。
* string是C++的内建类,这里不必再去担心如何为string对象分配空间。
* getline来源于<string>。
istream& getline( istream& is, string& s, char delimiter = '\n' );
* out如同标准对象cout一样,可以使用重载运算符`<<'。

四、介绍vector

//getwords.cpp---------------------
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

int main(void)
{
vector<string> v;
ifstream in("Makefile");

// string line;
// while (getline(in, line)) // (1)
// v.push_back(line);
string word;
while (in >> word) // (2)
v.push_back(word);

for (int i = 0; i < v.size(); i++)
cout << i << ": " << v[i] << endl;
}

使用C++标准模板类vector。
1. vector是C++的标准模板类,作为C++代码可重用性的体现,STL一直是C++中变动频繁的
地方。
2. vector<string>将临近元素像数组一样存储。
vector成员赋值和添加元素都可以在O(k)中完成,查找和插入则在O(k*n)中完成。
vector支持一系列操作(cppreference.com):
  
/* vector<TYPE>:
* void assign( size_type num, const TYPE& val );
* void assign( input_iterator start, input_iterator end );
* void insert( iterator loc, size_type num, const TYPE& val );
* void push_back( const TYPE& val );
*/

代码阅读TIC++:序章

一、硬件环境

IBM Thinkpad R50
Intel Pentium M 1400MHz


二、软件环境

Gentoo Linux 2008.0
linux-2.6.26-gentoo
GCC 4.3.1
GNU binutils 2.1.8


GCC编译器,其具体信息可以通过`gcc -v'来查询:

Using built-in specs.
Target: i686-pc-linux-gnu
Configured with:
/var/tmp/portage/sys-devel/gcc-4.3.1/work/gcc-4.3.1/configure
--prefix=/usr
--bindir=/usr/i686-pc-linux-gnu/gcc-bin/4.3.1
--includedir=/usr/lib/gcc/i686-pc-linux-gnu/4.3.1/include
--datadir=/usr/share/gcc-data/i686-pc-linux-gnu/4.3.1
--mandir=/usr/share/gcc-data/i686-pc-linux-gnu/4.3.1/man
--infodir=/usr/share/gcc-data/i686-pc-linux-gnu/4.3.1/info
--with-gxx-include-dir=/usr/lib/gcc/i686-pc-linux-gnu/4.3.1/include/g++-v4
--host=i686-pc-linux-gnu
--build=i686-pc-linux-gnu
--disable-altivec
--enable-nls
--without-included-gettext
--with-system-zlib
--disable-checking
--disable-werror
--enable-secureplt
--disable-multilib
--enable-libmudflap
--disable-libssp
--enable-cld
--disable-libgcj
--with-arch=i686
--enable-languages=c,c++,treelang,fortran
--enable-shared
--enable-threads=posix
--enable-__cxa_atexit
--enable-clocale=gnu
--with-bugurl=http://bugs.gentoo.org/
--with-pkgversion='Gentoo 4.3.1 p1.0'
Thread model: posix
gcc version 4.3.1 (Gentoo 4.3.1 p1.0)


三、关于《Thinking in C++》
《Thinking in C++》是C++非常出色的教材类书籍,作者是Bruce Eckel
(同时也是《Thinking in Java》的作者)。
你可以在这里查看到他的全面信息,同样在这里可以下载电子版图书。

四、代码编译
在下载的文件里,一般都包含有书籍里的代码。
编译他们是很简单的,通常情况下,执行一下命令,就可以得到可执行文件 a.out.

g++ filename.cpp


线程的例子需要在加上另外的POSIX thread支持的gcc选项`-pthread'。

g++ filename.cpp -pthread


多文件项目可以自己简单的写出Makefile,在此不再赘述。

2008年9月14日星期日

中秋佳节,马峦山溯溪

深圳的秋老虎气候接近尾声了,对于一个属于盛夏的城市,我们目送深圳夏天离去,一个最
好的方式,就是爬山、流汗。9月14日又是中秋节,因此对马峦山的聚会倍加期待。

那先说一下啥是溯溪,溯溪并非爬山,而是沿着溪流顺山而上。这说明活动围绕水而展开,
而不是徒步登山。

这次登山的目的地,选择了深圳的马峦山,在深圳小梅沙的北部。海拔300~590米,在盛夏
山上气温比深圳市区要低3摄氏度。马峦山没有工业污染,空气清新,没有城市噪音,环境
质量相当之高。

活动:马峦山溯溪
人数:26人
官方Q群:4565678
贴吧:http://tieba.baidu.com/f?kz=477382839

公交线路:103(上下沙),360(银湖),364(深航大厦),387(火车站),833(龙岗)
GoogleEarth地址:
http://maps.google.com/maps/ms?ie=UTF&msa=0&msid=102563878911537749872.000456ae6390c1a543d9d


活动开始……

这是深圳海洋世界正门,这是小梅沙旁边极其出名的水上游乐园,大家在里面可以看到的东
东,从南极企鹅,北极熊,到海豚表演等等。门票100元/人,咱们只是沿途看看,咱们都不
说话。


领队:黄瓜,看这装备可谓具备专业驴友素质,头巾,登山包,还有刚买的Canon EOS
1000D,那是相当的骚包。


大部队跟上,我来垫后。


途径叠翠湖,风景不算精致,倒是没有污染,感觉很好。


溯溪自然是顺溪流直上,因此攀岩必不可少,也具备一定的危险性。如果选择溯溪,定要具
备足够的水源和登山鞋。


沿途的细沙,给了一特写。


除了体验大自然的意趣,团队协作也是有意思的部分。需要大家向朋友伸出双手,感受彼此
的关怀。当然关怀之外有啥大事儿发生,当然需要当事人负责的。。。


非常喜欢的瀑布!我们还跳入水里游泳,打闹。让几天来的郁闷一扫而光。


披荆斩棘之后迎来山顶前的冲刺部分,黄瓜告诉咱们,到山顶就可以吃饭了。有美味的土家
烧鸡等着我们。


到达山顶的农家,店家开始现杀现做,准备咱们的午饭。这里风光无限。


今天是中秋节活动,避不了的是中秋月饼,还有主角土家烧鸡。当然这里可以看到是二十六
个人,分五个月饼,囧rz


待到四点钟下山,沿途疾步如飞。


我们在夕阳下结束一天旅程。


谢谢观赏,而后会更新几张更加别致的照片……

照片更新一张吧。