• 2008-07-14

    城市窒息 - [心灵感悟]

    坐在开向公司的班车上,看着窗外熙攘的人群、车水马龙的街道,突然莫名有一种窒息的感觉。

    盛夏炽热的阳光射在身上,总是感觉身体中的水分正在被一只无形的大手一捧一捧的掠走。宽阔的马路两边却少有林荫,无法给行人遮阳。男士们到也不在乎这些,女士们则打着遮阳伞,估计脸上还擦了SPF至少为8以上的厚厚的一层化学物质以低于强烈的紫外线。

    两侧高耸的建筑物让这个城市的散发着足球的现代化的气息,但建筑物外表的整块整块的大玻璃却太阳光无情的反射到路面上,让人们的眼睛感觉甚是不舒服。

    满街的公车、私车混合在一起,从一个交通信号路口排到上一个交通信号路口, 路面上顿时升腾出一股气浪,热腾腾的,还夹杂着浓烈的汽油、柴油混合的味道,不时一辆载满乘客的公交车从身边驶过,留下来一串浓黑的尾气,路边等车的人赶忙向上风处躲闪,做出用手捂住口鼻这样的心理安慰性的动作,大家都知道这样是不行的,尾气早已悄悄地进入到了你的呼吸系统,至于其深远的影响,也许只有God才知道。

    到处是工地,也许这就是现在中国的国情吧。中国富裕了!路,凿了再修;树,砍了再栽;楼,炸了再盖;没有河的地方,挖河引流,拆路造桥;没土地的沿海地区,填海造地;怪不得很多老外每隔一段时间来到中国,都会举起大拇指说:中国又发生巨大变化了,到处都是新的。而"我们"的回答:这是必须的。

    穿过刚刚改为双向通行的三好街,其南端有一处庞大的工地。工地周围的马路上到处是大型工程车辆,胡乱鸣笛,路人无不皱起眉头。

    突然想到了海,感觉心里一阵凉爽之意。东北地区以内陆为主。海,对于大多数人来说都是奢侈之物。羡慕那些生存在海边的人们,也终于理解了为什么很多人选择在临海的城市定居,也许在他们感受到城市话带来的窒息的感觉之余,大海的气息会给他们带来心灵上的慰藉。

    这个月末去看海!
  • 今天北京2008奥运会火炬传递到了著名的春城-昆明,说到昆明,我也不陌生,曾经在昆明待过很长时间,对昆明市内以及周边环境也有所了解。这里把以前写过的一些关于昆明著名景区的旅游文章集合到一起,供大家了解昆明。


    昆明印象·城市


    昆明印象·夜


    昆明印象·金马碧鸡坊和翠湖


    昆明印象·大观楼和滇池一角


    昆明印象·金殿和世博园外景


    昆明印象·市博物馆和特色手工艺品


    昆明圆通山动物园拾趣


    云南九乡游记

    具体内容点击上面各个图片链接!


  • Google


    Baidu


    Sina


    Sohu


    Xinhua


    Yahoo

    黑色,寄托哀思!
  • assert是大家常用的宏,它的用法相信大家都有所了解。P.J Plauger的"The C Standard Library"一书中提到在源代码中切换assert宏定义的方法:
    /* turn assertion on */
    #undef NDEBUG
    #include <assert.h>

    /* turn assertions off */
    #define NDEBUG
    #include <assert.h>

    我顺手写了一个例子如下:
    /* testmacro1.c */
    #define NDEBUG
    #include <assert.h>

    int main() {

            assert(0); // => ((void)0);

    #undef NDEBUG
            #include <assert.h>
            assert(0); // => (void)((0) || (__assert("0", "testmacro.c", 10), 0));
    }
    测试结果正如P.J Plauger的说明。但仔细看来似乎有些疑惑:总觉得第二个assert也应该展开成((void)0)才对啊。由于NDEBUG被定义,在第一次assert.h展开时,assert就被替换成了((void)0),而后虽然NDEBUG被disable了,但此时由于assert.h的header file guard保护,assert的新定义并没有被重新loaded & evaluated,所以assert似乎依然应该被展开为((void)0),但执行结果却不是。

    我自己写了一个程序测试了一下:
    /* testmacro1.h */
    #ifndef TEST_MACRO1_H
    #define TEST_MACRO1_H

    #ifdef X_DEBUG
    #define x_debug(expr)   ((void)0)
    #else
    #define x_debug(expr)   #expr
    #endif
    #endif

    /* testmacro1.c */
    #define X_DEBUG
    #include "testmacro1.h"

    int main() {
            x_debug(0); // => ((void)0);

    #undef X_DEBUG
            #include "testmacro1.h"
            x_debug(0); // => ((void)0)
    }
    果不其然,结果正如我所猜测的:
    #undef X_DEBUG
    #include "testmacro1.h"
    并没有改变x_debug的定义,那么第一个例子到底是怎么回事呢?

    其实这是C标准库设计所致,打开你所在系统的assert.h标准文件,我在sun solairs 9上是这样的:
    #ifndef _ASSERT_H
    #define _ASSERT_H
    ... ...
    #endif

    #undef  assert
    #ifdef  NDEBUG
    #define assert(EX) ((void)0)
    #else
    #define assert(EX) (void)((EX) || (__assert(#EX, __FILE__, __LINE__), 0))
    #endif  /* NDEBUG */

    哈哈,这下子看清楚了,原来assert的定义根本不在Header File Guards的保护下,怪不得我思前想后都对不上呢:),因为没有File Guards的保护。使头文件中的宏有机会被重新loaded&envaluated。

    下面例子中的第三个assert屏蔽掉了标准库中的assert实现:
    /* testmacro3.c */
    #define NDEBUG
    #include <assert.h>

    int main() {

            assert(0); // => ((void)0);

    #undef NDEBUG
            #include <assert.h>
            assert(0); // => (void)((0) || (__assert("0", "testmacro3.c", 10), 0));

    #define assert(exp) (#exp)
            assert(x==0); // => ("x==0");
    }
    这种屏蔽很简单,就不多说了,自己看吧。
  • P.J Plauger的"The Standard C Library"一书的Chapter0的章后练习中有这样的一道题:编写一个包含如下一行语句的正确的程序:
    x:      ((struct x*)x)->x=x(5);
    并描述这行语句中x的5种截然不同的use,这里其实涉及到这么一个知识或者说概念:C语言的命名空间(namespace),在"C语言参考手册"中还被称作: overloading class。

    这里namespace,并非C++中的那个keyword "namespace",这里的namespace更多是编译器为了识别不同范围下的标识符而进行的划分,而不是提供给应用程序员的类似c++中的那个namespace facility。再次注意:C的namespace不是一个关键字。

    简单分析一下这行语句:x:      ((struct x*)x)->x=x(5);
    这里有5个x,第一印象:这样的语句能编译过去么?那既然P.J Plauger提出了这样的问题,那么自然有solution。
    从左到右顺序:
    第一个x -- 毋庸置疑,这是一个标号(label) ;
    第二个x -- 这里的x显然是一个struct tag(结构体标志);
    第三个x -- 这里的x 无法确定其具体身份,可能是一指针类型,也可能就是一个整型;
    第四个x -- x前面有->,显然这个x是某结构体的一个成员变量;
    第五个x -- x(5)让人"浮想联翩",第一印象是函数调用,细致一想还可能是一个宏哦(你肯定会说不可能,呵呵,别着急,慢慢来)

    到底如何增加一些语法元素能让这一行能顺利通过编译,并执行后得到合理结果呢?我们不妨先来温习一下C标准中对C的"命名空间"的诠释。

    在"C语言参考手册"中有如此说明,标准C将其Namespace分成了五种,分别是:
    1) 预处理器宏名
    2) 语句标号
    3) 结构、枚举、联合结构的标志
    4) 成员名
    5) 其他名称 包括变量名、函数名、typedef名称和枚举常量

    有了以上的说明,我们有了第一种方案:
    上面说了,语句x:      ((struct x*)x)->x=x(5)中有三个x都是可以确定的,不确定的是第三个x和最后一个x。我们先考虑让最后一个x为一个函数。

    考虑到最后一个名称空间的说明,一旦最后一个x为函数的话,第三个x就不能为变量名、typedef名称和枚举常量了。如果x是对象宏(不带参数的宏),显然也不合理;那么我们先将x实现为函数看看:
    struct x { //for the 2nd x
            int x;  //for the 4th x
    };

    int x(int a) { //for the 3rd and 5th x
            return a;
    }

    int main() {
    x:      ((struct x*)x)->x=x(5);
    }
    这个在gcc(sunos or mingw on windows下)下编译能顺利通过。但是执行一下编译出的程序,会出现致命错误。初略分析一下也不奇怪。函数x的地址是在代码段,那块内存区域是只读且受保护的,尝试强制赋值显然os是不允许的。

    第一种方案虽然能通过编译,但是执行结果不合理。我们来做第二种尝试:试着将最后一个x实现为一个函数宏(带参数的宏)。
    struct x { //for the 2nd x
            int x;  //for the 4th x
    };

    struct x ax;

    #define x(a)  (a);

    int main() {
            int x = (int)(&ax);
    x:      ((struct x*)x)->x=x(5);  
            printf("%d\n", ((struct x*)x)->x); //output: 5
    }
    这回,我们得到了正确的且合理的solution了。在P.J Plauger的"The Standard C Library"一书中还有一张关于C语言命名空间的图,记起来更形象。
  • 时间定格在公元2008年5月12日,那天是星期一,工薪族们正努力的从周末休假状态转换到工作状态;操场上正在嬉戏打闹的低年级的小学生听到铃声陆续进入教室准备上课;初三、高三的莘莘学子们正伏案刻苦的读书,准备迎接即将来临的中考和高考;幼儿园里孩儿童们依旧在老师的看护下午睡着;盘山公路上、景区的缆车上,兴致勃勃的游客们正在欣赏着大自然的美丽景色。就当人们沉浸在这美好、恬静生活的时候,地球的内部,更精确的说是处于北纬31度,东经103.4度位置的美丽的四川汶川地区下面的地球板块迫于其他板块的压迫发生了运动,地震发生了。

    这次地震波及范围之广实属罕见,除了吉林和黑龙江没有探测到震级外,其他省和地区均有震情报告。灾难面前的中国人民向来是团结一致、众志成城的。食品、药品、棉被、帐篷、照明设施、救灾款项源源不断的送往灾区。如果不是汶川地区地形复杂,道路封闭,更多的人将会会得到重生的机会。

    这次地震后的救援有几大特点:
    1) 政府反应极其迅速,各种预案立即启动,为灾后救援提供了宝贵的时间。值得一提的是:在震后的2个小时左右温总理就已经坐在了飞往震区的飞机上了。
    2) 各大媒体第一时间滚动持续传递震区消息,辟谣以正视听,让老百姓及时了解震区情况,以正确配合救灾工作。
    3) 灾区人民自救得力。在没有救援人员到达的情况下,灾区幸存的人们自发组织进行救援,很多人因此得到及时救援,得以重生。

    这次地震还有几个疑点,值得震后反思和事后算账:
    1) 学校教学楼和宿舍楼损毁严重,这里难道没有人为的因素么?政府应在震后进行取证调查,让那些在此次灾难中逝去的孩子们、老师们以及他们的亲属可以得到慰藉;
    2) 通信设备损毁严重,通讯不畅,影响救援。在年初的南方雪灾中,也出现了类似的情况,这很值得反思,电信、移动、联通在建立基站、铺设线缆时是否给予充分设计、论证?是否考虑到不同地区的不同环境情况而做到因地制宜了。

    一个想法:
    震后,由于道路补偿,救援人员不能及时赶到,而劫后余生的人们在自救的过程中却因没有得力的和专业的救援工具而导致救援缓慢。我想国家是否可以做这样一件事:在全国地质灾害多发地区,设置多个公共救援工具存放处,而不是像目前都放到大城市的集中的储备库中。这样一旦发生灾情,大家可以就近拿到工具,节省时间。另外我们应该向邻国日本学习,定期对国民进行灾难教育,让国民了解自救以及救人的基本技能,或者在教育课程中,加入必要的自救和互救的实习课程,学习知识、工具使用,就好比大学新生必须进行的军训一样,让大家都掌握自救和互救的技能。如果能确实做到未雨绸缪,那么灾难给我们带来的损失将会降低很多,更多的人能生存下去。

    这次汶川地震不禁让我想起了1975年家乡发生的那场地震,与汶川地震不同的是,那次地震被准确的预报出来了,这大大减少了人员伤亡和财产损失。试想如果汶川地震事先有预报,那些孩子们无论如何都不会有如此大的伤亡的。遗憾的是这次地震,事前没有任何预测,正如本文开头所描述的,灾难是突然降临的,人们没有丝毫准备。

    现在距离地震发生的时间已经过去了50多个小时了,我们现在能做的就是力所能及的捐钱捐物,并为灾区那些依旧掩埋在废墟中的人们祈祷,祈祷他们能早日获救,得以重生。

    让我们一起来祈祷吧!

    突然萌生出一个幼稚的想法:转行做地震预报研究去!
  • 2008-05-12

    一分之差 - [休闲生活]

    说来真是遗憾,这不上周五参加了驾驶员考试的理论测试,十分"点背"的是我居然以一分之差没有通过。

    今天驾校的一位中年男老师打来电话,问我是否参加十天之内安排的补考?令我惊奇的是他还对我鼓励了一番:"我相信你一定能过",我也应承着:有你这句话我一定过。虽说教练这句话可能不完全是为我着想,但心里还是莫名的感受到一丝激励。毕竟是为了自己学么。

    其实除了工作忙没时间看题库之外,我觉得最大原因还是我的态度有问题--"不重视",总有一种"侥幸"的心理。就在上周五,也就是考试那天的上午(下午16:00开考), 我试着做光盘里的题库,结果做了100道,错了11道。事实也是惊人的相似,下午的考试,我得到的分数也恰好是89分,一分之差,让我有机会人生第一次需要参加所谓的"补考",从来没参加过什么补考之类的考试,这次我算是栽了。

    考试没过,心情自然是很郁闷的,而且那天是周五,弄得我周末两天也是郁郁寡欢。

    其实一名合格的驾驶员是应该牢牢掌握驾驶理论知识的,而且很多知识都是交通法范畴,懂法其实是提高了自我保护的能力,一旦遇到如题中的情况,法律就是我们手中的一把利剑。另外掌握必要的交通法、驾驶和救护常识也会大大降低以后车祸发生率以及车祸给你带来的伤害程度。既然都是好处,那就好好复习吧,何乐而不为呢!
  • 很多技术人员都有在"技术细节"上"钻牛角尖"的"癖好",对此很多人褒贬不一;无论怎样,我也是属于这类人。C语言的变长参数在平时做开发时很少会在自己设计的接口中用到,但我们最常用的接口printf就是使用的变长参数接口,在感受到printf强大的魅力的同时,是否想挖据一下到底printf是如何实现的呢?这里我们一起来挖掘一下C语言变长参数的奥秘。

    先考虑这样一个问题:如果我们不使用C标准库(libc)中提供的Facilities,我们自己是否可以实现拥有变长参数的函数呢?我们不妨试试。

    一步一步进入正题,我们先看看固定参数列表函数,
    void fixed_args_func(int a, double b, char *c) {
            printf("a = 0x%p\n", &a);
            printf("b = 0x%p\n", &b);
            printf("c = 0x%p\n", &c);
    }
    对于固定参数列表的函数,每个参数的名称、类型都是直接可见的,他们的地址也都是可以直接得到的,比如:通过&a我们可以得到a的地址,并通过函数原型声明了解到a是int类型的; 通过&b我们可以得到b的地址,并通过函数原型声明了解到b是double类型的; 通过&c我们可以得到c的地址,并通过函数原型声明了解到c是char*类型的。

    但是对于变长参数的函数,我们就没有这么顺利了。还好,按照C标准的说明,支持变长参数的函数在原型声明中,必须有至少一个最左固定参数(这一点与传统C有区别,传统C允许不带任何固定参数的纯变长参数函数),这样我们可以得到其中固定参数的地址,但是依然无法从声明中得到其他变长参数的地址,比如:
    void var_args_func(const char * fmt, ... ) {
        ... ...
    }
    这里我们只能得到fmt这固定参数的地址,仅从函数原型我们是无法确定"..."中有几个参数、参数都是什么类型的,自然也就无法确定其位置了。那么如何可以做到呢?在大脑中回想一下函数传参的过程,无论"..."中有多少个参数、每个参数是什么类型的,它们都和固定参数的传参过程是一样的,简单来讲都是栈操作,而栈这个东西对我们是开放的。这样一来,一旦我们知道某函数帧的栈上的一个固定参数的位置,我们完全有可能推导出其他变长参数的位置,顺着这个思路,我们继续往下走,通过一个例子来诠释一下:(这里要说明的是:函数参数进栈以及参数空间地址分配都是"实现相关"的,不同平台、不同编译器都可能不同,所以下面的例子仅在IA-32,Windows XP, MinGW gcc v3.4.2下成立)

    我们先用上面的那个fixed_args_func函数确定一下这个平台下的入栈顺序。

    int main() {
        fixed_args_func(17, 5.40, "hello world");
        return 0;
    }
    a = 0x0022FF50
    b = 0x0022FF54
    c = 0x0022FF5C

    从这个结果来看,显然参数是从右到左,逐一压入栈中的(栈的延伸方向是从高地址到低地址,栈底的占领着最高内存地址,先入栈的参数,其地理位置也就最高了)。我们基本可以得出这样一个结论:
     c.addr = b.addr + x_sizeof(b);  /*注意:  x_sizeof != sizeof,后话再说 */
     b.addr = a.addr + x_sizeof(a);

    有了以上的"等式",我们似乎可以推导出 void var_args_func(const char * fmt, ... ) 函数中,可变参数的位置了。起码第一个可变参数的位置应该是:first_vararg.addr = fmt.addr + x_sizeof(fmt);  根据这一结论我们试着实现一个支持可变参数的函数:

    void var_args_func(const char * fmt, ... ) {
        char    *ap;

        ap = ((char*)&fmt) + sizeof(fmt);
        printf("%d\n", *(int*)ap);  
            
        ap =  ap + sizeof(int);
        printf("%d\n", *(int*)ap);

        ap =  ap + sizeof(int);
        printf("%s\n", *((char**)ap));
    }

    int main(){
        var_args_func("%d %d %s\n", 4, 5, "hello world");
    }

    输出结果:
    4
    5
    hello world

    var_args_func只是为了演示,并未根据fmt消息中的格式字符串来判断变参的个数和类型,而是直接在实现中写死了,如果你把这个程序拿到solaris 9下,运行后,一定得不到正确的结果,为什么呢,后续再说。先来解释一下这个程序。我们用ap获取第一个变参的地址,我们知道第一个变参是4,一个int型,所以我们用(int*)ap以告诉编译器,以ap为首地址的那块内存我们要将之视为一个整型来使用,*(int*)ap获得该参数的值;接下来的变参是5,又一个int型,其地址是ap + sizeof(第一个变参),也就是ap + sizeof(int),同样我们使用*(int*)ap获得该参数的值;最后的一个参数是一个字符串,也就是char*,与前两个int型参数不同的是,经过ap + sizeof(int)后,ap指向栈上一个char*类型的内存块(我们暂且称之tmp_ptr, char *tmp_ptr)的首地址,即ap -> &tmp_ptr,而我们要输出的不是printf("%s\n", ap),而是printf("%s\n", tmp_ptr); printf("%s\n", ap)是意图将ap所指的内存块作为字符串输出了,但是ap -> &tmp_ptr,tmp_ptr所占据的4个字节显然不是字符串,而是一个地址。如何让&tmp_ptr是char **类型的,我们将ap进行强制转换(char**)ap <=> &tmp_ptr,这样我们访问tmp_ptr只需要在(char**)ap前面加上一个*即可,即printf("%s\n",  *(char**)ap);

    前面说过,如果将var_args_func放到solaris上,一定是得不到正确结果的?为什么呢?由于内存对齐。编译器在栈上压入参数时,不是一个紧挨着另一个的,编译器会根据变参的类型将其放到满足类型对齐的地址上的,这样栈上参数之间实际上可能会是有空隙的。上述例子中,我是根据反编译后的汇编码得到的参数间隔,还好都是4,然后在代码中写死了。

    为了满足代码的可移植性,C标准库在stdarg.h中提供了诸多Facilities以供实现变长长度参数时使用。这里也列出一个简单的例子,看看利用标准库是如何支持变长参数的:
    #include <stdarg.h>

    void std_vararg_func(const char *fmt, ... ) {
            va_list ap;
            va_start(ap, fmt);

            printf("%d\n", va_arg(ap, int));
            printf("%f\n", va_arg(ap, double));
            printf("%s\n", va_arg(ap, char*));

            va_end(ap);
    }

    int main() {
            std_vararg_func("%d %f %s\n", 4, 5.4, "hello world");
    }
    输出:
    4
    5.400000
    hello world

    对比一下 std_vararg_func和var_args_func的实现,va_list似乎就是char*, va_start似乎就是 ((char*)&fmt) + sizeof(fmt),va_arg似乎就是得到下一个参数的首地址。没错,多数平台下stdarg.h中va_list, va_start和var_arg的实现就是类似这样的。一般stdarg.h会包含很多宏,看起来比较复杂。在有的系统中stdarg.h的实现依赖some special functions built into the the compilation system to handle variable argument lists and stack allocations,多数其他系统的实现与下面很相似:(Visual C++ 6.0的实现较为清晰,因为windows上的应用程序只需要在windows平台间做移植即可,没有必要考虑太多的平台情况)。

    Microsoft Visual Studio\VC98\Include\stdarg.h中,
    typedef char *  va_list;

    #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
    #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
    #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    #define va_end(ap)      ( ap = (va_list)0 )

    这里有两个地方需要深入挖掘一下:
    1、#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
    我们这里简化一下这个宏:
    #define _INTSIZEOF(n)  ((sizeof(n) + x) & ~(x))
    x = sizeof(int) - 1 = 3 = 0000 0000 0000 0011(b)
    ~x = 1111 1111 1111 1100(b)

    当一个数 & (-x)时,得到的值始终是sizeof(int)的倍数,也就是说_INTSIZEOF(n)的功能是将n圆整到sizeof(int)的倍数上去。sizeof(n) >= 1, sizeof(n)+sizeof(int)-1经过圆整后,一定会是>=4的整数;在其他系统平台上,圆整的目标值有的是4,有的则是8,视具体系统而定。

    2、#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    其实有了var_args_func的实现,这里也就不难理解了。不过这里有一个trick,很多人一开始肯定对先加上_INTSIZEOF(t),又减去_INTSIZEOF(t)很不理解,其实这里是一点就透的:整个表达式((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) 返回的值其实和最初的ap所指向的地址是一致的,关键就是在整个表达式被evaluated后,ap却指向了下一个参数的地址了,就这么简单。

    P.J.Plauger的"The standard C library"一书的第10章节中也有对stdarg实现的分析,那个版本虽然比较老,但我想应该是现有版本的一个雏形。
  • 2008-05-02

    也谈typedef - [语言探索]

    C语言语法简单,但内涵却博大精深;如果在学习时只是止步于表面,那么往往后期会遇到很多困难。typedef是C语言中一个很好用的工具,大量存在于已有代码中,特别值得一提的是:C++标准库实现中更是对typedef有着大量的使用。但很多初学者对其的理解仅局限于:typedef用来定义一个已有类型的"别名(alias)"。正是因为有了这样的理解,才有了后来初学者在typedef int myint和typedef myint int之间的犹豫不决。很多国内大学的C语言课之授课老师也都是如是说的,或者老师讲的不够透彻,导致学生们都是如是理解的。我这里想结合C语言标准文档以及一些代码实例,也说说typedef。

    int    *p;
    这样的代码是C语言中最最基础的一个语句了,大家都知道这个语句声明了一个变量p,其类型是指向整型的指针(pointer to int);如果在这个声明的前面加上一个typedef后,整个语义(semantics)又会是如何改变的呢?
    typedef  int    *p;

    我们先来看看C99标准中关于typedef是如何诠释的?C99标准中这样一小段精辟的描述:"In a declaration whose storage-class specifier is typedef, each declarator defines an identifier to be a typedef name that denotes the type specified for the identifier in the way described in xx"。

    参照这段描述,并拿typedef  int    *p作为例子来理解:在一个声明中,如果有存储类说明符typedef的修饰,标识符p将被定义为了一个typedef name,这个typedef name表示(denotes)一个类型,什么类型呢?就是int *p这个声明(declarator)中标识符(indentifier)p的类型(int*)。

    再比对一下两个声明:
    int    *p;
    typedef  int    *p;
    是不是有点"茅舍顿开"的感觉,int *p中, p是一个变量,其类型为pointer to int;在int *p前面增加一个typedef后,p变为一个typedef-name,这个typedef-name所表示的类型就是int *p声明式中p的类型(int*)。说句白话,typedef让p去除了普通变量的身份,摇身一变,变成了p的类型的一个typedef-name了。

    为了巩固上面的理解,我们再来看看"C语言参考手册(C: A Reference Manual)"中的说法:任何declarator(如typedef int   *p)中的indentifier(如p)定义为typedef-name, 其(指代p)表示的类型是declarator为正常变量声明(指代int  *p)的那个标识符(指代p)的类型(int*)。有些绕嘴,不过有例子支撑:

    [例1]
    typedef double MYDOUBLE;  

    分析:
    去掉typedef ,得到正常变量声明=> double MYDOUBLE;
    变量MYDOUBLE的类型为double;
    => "typedef double MYDOUBLE"中MYDOUBLE是类型double的一个typedef-name。

    MYDOUBLE    d; <=> d是一个double类型的变量

    [例2]
    typedef double *Dp;  

    分析:
    去掉typedef  ,得到正常变量声明=> double *Dp;
    变量Dp的类型为double*,即pointer to double;
    => "typedef double *Dp"中Dp是类型double*的一个typedef-name。

    Dp    dptr; <=> dptr是一个pointer to double的变量

    [例3]
    typedef int* Func(int);

    分析:
    去掉typedef  ,得到正常变量声明=> int* Func(int);
    变量Func的类型为一个函数标识符,该函数返回值类型为int*,参数类型为int;
    => "typedef int* Func(int)"中Func是函数类型(函数返回值类型为int*,参数类型为int)的一个typedef-name。

    Func    *fptr; <=> fptr是一个pointer to function with one int parameter, returning a pointer to int
    Func     f;   这样的声明意义就不大了。

    [例4]
    typedef int (*PFunc)(int);

    分析:
    去掉typedef  ,得到正常变量声明=> int (*PFunc)(int);
    变量PFunc的类型为一个函数指针,指向的返回值类型为int,参数类型为int的函数原型;
    => "typedef int (*PFunc)(int)"中PFunc是函数指针类型(该指针类型指向返回值类型为int,参数类型为int的函数)的一个typedef-name。

    PFunc     fptr; <=> fptr是一个pointer to function with one int parameter, returning int

    [例5]
    typedef    int   A[5];

    分析:
    去掉typedef ,得到正常变量声明 => int   A[5];
    变量A的类型为一个含有5个元素的整型数组;
    => "typedef    int   A[5]"中A是含有5个元素的数组类型的一个typedef-name。

    A   a = {3, 4, 5, 7, 8};
    A   b = { 3, 4, 5, 7, 8, 9}; /* 会给出Warning: excess elements in array initializer */

    [例6]
    typedef    int   (*A)[5]; (注意与typedef    int*    A[5]; 区分)

    分析:
    去掉typedef ,得到正常变量声明 => int   (*A)[5];
    变量A的类型为pointer to an array with 5 int elements;
    => "typedef    int   (*A)[5]"中A是"pointer to an array with 5 int elements"的一个typedef-name。

    int   c[5] = {3, 4, 5, 7, 8};  
    A    a = &c;
    printf("%d\n", (*a)[0]); /* output: 3 */

    如果这样赋值:
    int   c[6] = {3, 4, 5, 7, 8, 9};  
    A    a = &c; /* 会有Warning: initialization from incompatible pointer type */

    [例7]
    typedef struct _Foo_t Foo_t;

    分析:
    去掉typedef ,得到正常变量声明 => struct _Foo_t Foo_t;
    变量Foo_t的类型为struct _Foo_t;
    => "typedef struct _Foo_t Foo_t"中Foo_t是"struct _Foo_t"的一个typedef-name。

    [例8]
    typedef   struct { ... // }   Foo_t;

    分析:
    去掉typedef ,得到正常变量声明 => struct { ... // }   Foo_t;
    变量Foo_t的类型为struct { ... // } ;
    => "typedef   struct { ... // }   Foo_t "中Foo_t是"struct { ... // }"的一个typedef-name。这里struct {...//}是一个无"标志名称(tag name)"的结构体声明。

    参考资料:
    1、"ISOIEC-98991999(E)--Programming Languages--C"之Page 123;
    2、C语言参考手册(中文版) 之 Page 119
  • 今天上午参加了一个公司内部的项目管理工具推广和使用的培训,培训地点在公司新落成不到一载的办公楼的一间视频会议室里,由于是新办公楼,所以这里的设施也都是很新的。特别是会议室里的座椅让人坐起来很是舒服,会议室的椭圆桌摸起来也很有质感,当时就和同事们讨论如果我们的办公环境要是能有这样的座椅和桌子那该多好啊,工作效率肯定能提高不少。目前我们的座椅估计就属于写字楼中最常见的那种,坐起来普遍反映不舒服。

    会议室之所以使用好座椅是因为"面子"问题,但回过头来想想,其使用频率肯定比普通员工的座椅的低很多很多,在这点上不知道公司是如何想的。早在我读大学的时候,当时就有一本叫"人件"的书,书中的第二篇就细致分析了办公环境员工工作的影响,估计公司领导都没有看过这本书:)。记得上次Dreamhead来的时候,给我们简单说了一下Thoughtworks的办公环境,着实让我们这些"没见过世面的"人开了些眼界。相信很多人也都看过网上Google和Microsoft的办公环境大PK,得心应手的"武器"-多台大屏幕液晶、人体工程键盘、标配高性能笔记本;源源不断的"供给"-各种免费的美食和饮品;还有的就是"我的地盘我做主"的自由,在这样的环境下似乎不兴奋都困难。其实我们公司提供的环境属于那种"比上不足,比下有余",毕竟与Google、Microsoft这样的以"精英文化"为主流的公司不同,以国内行业方案和外包为主的公司如果都像Google、微软一样等提供免费食物、饮品等,估计就得做赔本生意了,人太多(有些类似劳动密集型,特别是外包部门),现在行业不好做,外包受到汇率的影响利润也越来越薄。

    再深挖掘一下,除了上述的硬件环境外,能吸引人才的更多是一种软环境,也可以说是公司营造的一种氛围;说的更大一些的话,那就是文化了。Google、Microsoft的目标都是最大化的将员工的潜力激发出来,让员工100%投入到工作或工作相关(包括自我学习)的事情上来,那些不需要员工费心考虑的事情都有公司帮忙去做,比如据说Google为员工提供免费洗衣服务。这些如果套用国内的"口号"就是"以人为本",而且是真正的以人为本。在为员工最大化的解除后顾之忧的同时,像Google这种公司总是保持一种Open的态度,这使公司一直走在业界的前沿,Open让企业更具创造力。反观国内的软件公司,似乎很多走了相反的道路。以我们公司为例,前一阶段以"信息安全"为借口限制员工访问外部网络的做法就是一典型的Close的态度,在如今这个时代,这样的做法只能让我们剩下为之叹息的份儿了。

    小座椅,大道理!