《C专家编程》:编译器的金科玉律(一)

        C诡异离奇,缺陷重重,却获得了巨大的成功。   

                                                                                         ---- --Dennis Ritch

        好记性不如烂笔头,但是作为一个计算机专业的小菜选手,用笔而不同键盘是不是有点儿对不起互联网了,怎么的也要挤进这个知识互联共享的大家庭里。最近在看基本编程相关的书籍,这本曾经读过的[美]Peter Van Der Linden著 徐波 译的《C专家编程》回头看一看,发现有很多知识点,虽然和其他文章有重复,但是鉴于它的重要性和易错性,决定每天再看一看,并用bolg记录下来,让自己加深印象,深刻理解,这也是一个重复学习和监督的过程。我始终相信技术类的书不能一目十行,需要一步一步的向前走,想想以前也学了很多,看了很多,但是真正能握在手中的又有多少。这可不是练太极--“无形胜有形”!时间虽然紧张,但还得脚踏实地慢慢来,1*1虽然简单,但是每个人都曾付出努力去背过!不成功怎么知道它的简单,不失败怎么知道它的艰难。切记心浮气躁,切记走马观花,切记眼高手低!在这个过程中如果有幸也能帮到您一点,我也是十分的高兴,如果文章有错,敬请指出,不深感激!文章也会不断的进行完善,以后可能还有修改增删。
C语言的史前阶段:

          C语言的出现证明了一件事儿,那就是失败时成功之母。因为一直被人们朝圣的C语言竟然源于一个失败的项目。1969,通用电气、麻省理工工学院和贝尔实验室联合创立了一个庞大的项目--Multics。该项目的目的是创建一个操作系统,但是最后失败了。当心灰意冷的贝尔实验室的专家们撤离Multics工程后,他们又去寻找其他的任务。其中一个叫Ken Thompson的研究人员对另一个操作系统很感兴趣。所以Thompson和Denis Ritchie自娱自乐,把Thompson的“太空旅行”软件移植到不太常用的PDP-7(由迪吉多公司所研发的一款迷你电脑)系统上。与此同时,Thompson为PDP-7编写了一个简单的OS,它比Multics简单的多,并取名为UNIX。

语言早期的发展:

BCPL(Basic Combined Programing Language)--基本组合编程语言。

1、编译器设计者的金科玉律:效率就是一切。
       在编译器中,效率几乎就是一切。
      编译器的效率主要包括两个方面: 运行效率(代码运行的速度)和编译速度(产生可执行代码的速度)。其中运行效率起决定性作用。很多的编译器会选择延长编译时间,缩短运行时间。还有一些优化措施(清楚无用代码和忽略运行时的检查)既能缩短编译时间,又能减少运行时间,还能减少内存的使用量。但是效率就是一切也不是绝对的,因为如果结果错误,就算编译器效率再高也是无用的。 由于早期的程序员就是那些编译器设计者,所以由很多语言特性是根据编译器设计者的思路发展而来的。
(1)数组的下标从0开始而不是1。但是人类是习惯从1开始的。
(2)C语言的基本类型直接与底层硬件相对应;
(3)auto关键字显然是摆设。它只对常见符号表入口的编译器设计者有意义。
(4)表达式中的数组名可以看作是指针;
(5)float被自动扩展为double;
(6)不许嵌套函数;
(7)register关键字。经常使用的变量可以放到寄存器。简化了编译器,把麻烦丢给程序眼员。
2、宏的替换时空格所引起的问题:
例如:#define a(y)  b(y)
a(x)被替换为:b(x);
而如果是#define a (x) b(y)
此时:
a(x) 被替换为:(x) b(y)x
 这将被替换的面目全非,所以宏就是单纯的文本替换,使用时要注意。
3、一个非比寻常的bug;
       C语言从Algol中继承了一个特性,就是复合赋值符。它允许一个重复出现的赋值符只写一次而不是两次;因为最早的时候,B语言中词法分析器里有一个技巧:实现=op比op=要方便:
     例如现在的:b+=3   为什么不是b=+3呢?
    因为这样会产生歧义?
     一个简单的例子,若是b=-3;那么翻译是:b=b-3呢?还是b=-3?负数。
      所以这时早前编译器出现的一些bug。
4、函数的原型申明;
老式的函数申明方法:char * strcpy(det,src)
                         char *det,*src;{}
	      现在:char *strcpy(char *det,char *src){}
    可以省略形参名称,但是不能省略类型;
5、参数的赋值条件:
        要是赋值的形式合法,两个操作数都是指向有限定符或无限定符的相容类型的指针, 左边指针所值向的类型必须具有右边指针所指向的类型的全部限定符。
简单的理解:
例如;char * cp;
const char *ccp;  //指向一个具有const限定的字符指针;
ccp=cp;//合法的,没有警告;
cp=ccp;//结果会产生编译警告;
    const最有用之处就是用它来限定函数的形参,这样 该函数将不会修改实参指针所指的数据。
     但是可以通过scanf()和指针来改变值。这里就不细讲了。主要是分清常量指针和指针常量,以及指向常量的指针常量。
6、安静的转变
    (1).在表达式中,char 和 short 类型的值,无论有符号还是无符号,都会自动转换成 int 或者 unsigned int(如果 short 的大小和 int 一样,unsigned short 可表示的最大值就大于 int,在这种情况下,unsigned short 被转换成 unsigned int)。因为它们被转换成表示范围更大的类型,故而我们把这种转换称之为“升级(promotion)”。
    (2).按照从高到低的顺序给各种数据类型分等级,依次为:long double, double, float, unsigned long long, long long, unsigned long, long, unsigned int 和 int。这里有一个小小的例外,如果 long 和 int 大小相同,则 unsigned int 的等级应位于 long 之上。char 和 short 并没有出现于这个等级列表,是因为它们参与运算时就应该已经被升级成了 int 或者 unsigned int。
    (3).作为参数传递给函数时,char 和 short 会被转换成 int,float 会被转换成 double。使用函数原型可以避免这种自动升级。
7、一个微秒的Bug
请看下面一段代码:
int array[]={1,2,3,4,5};
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0]))
int main()
{
	int d=-1,x;
	if(d<=TOTAL_ELEMENTS)
	{
	x=array[d+1];
	}
}
   运行此程序,你会发现改程序if条件不为真,这是why?
     这是becauseTOTAL_ELEMENTS所定义的值是unsigned int类型的,因为sizeof()返回的类型是无符号型的。 if语句在unsigned int和signed int之间测试相等性,则d会被升级到unsigned类型,因为nusigned类型表示的范围更大。那-1转换为一个unsigned就是一个无比大的整数,用补码表示。致使表达式一直是假。看似简单的问题,却在“安静的转换中”隐藏着一个Bug。
  要修改这个问题,只需要强制类型转换一下即可:
if(d<=(int)TOTAL_ELEMENTS-2)  //OK
注:尽量不要使用无符号类型的数据,这样会引起不必要的麻烦。只有在位段和二进制掩码的时候才可以使用无符号数。
其实在某些版本中我们要使用下面的代码
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0]))  (1)
而不是:
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(int))   (2)
  如果是(2)式,我们将array变成char或者float类型,就会出现意想不到的结果。但是前者可以在不修改#define语句的情况下改变数据组的基本类型(比如把int变为char,float等等)。
所以一定要小心这些“安静的转换”!
小知识;
      我们所说的“黑客”(Hacker),它的原先意思是“天才程序员”,后来这个称号被媒体贬损,人言可畏啊,一个好人被硬生生的说成了坏人。使他在局外人的眼中成了“邪恶的天才”的代名词,类似一个bad guy。

自由软件基金:(Free Software Foundation)是一个特殊的组织,它由MIT的顶级黑客Richard Stallman所创立。目标是软件应该是免费的,所有人都可以自由使用。他雄心勃勃想建立一个UNIX的自由软件实现方案,成为GUN(代表GUN is not UUIX。

你可能感兴趣的:(编译器,C编程专家)