第一章:
一:尽量不要在你的代码中使用无符号类型,以免增加不必要的复杂性,尤其是不要仅仅因为无符号数不存在负值(如年龄,国债)而用它来表示数量。只有在使用位段和二进制掩码时,才可以使用无符号数,应该在表达式中使用强制类型转换,使操作数均为有符号数或无符号数。这样就不必由编译器来选择结果的类型。
二:当执行算术运算时:操作数的类型如果不同,就会发生转换,数据类型一般朝着浮点精度更高,长度更长的方向转变。整型数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。
一个L的NUL用于结束一个ASCALL字符串。ASCALL字符中零的位模式被称为NUL。
两个L的NULL用于表示什么也不指向,(空指针)
第二章:这不是BUG,而是语言特性
2.1 多做之过:
这些特性包括容易出错的switch语句,相邻字符串常量的自动连接和缺省全局范围。
C语言中,几乎从来不进行运行时错误检查---对进行解除引用操作的指针有效性检查大概是唯一的例外。无效的指针可能成为程序员的噩梦,人们很容易用一个无效的指针来引用内存。在所有的虚拟内存体系结构里,一旦一个指针进行解除引用操作时,所引用的内存超出内存地址的虚拟地址空间,操作系统就会结束这个进程。但是MS—DOS并不支持虚拟内存,即使内存访问失败,它也无法立即捕获这种情况。
C语言的理念,程序员应该知道自己正在干什么,而且保证自己的所做所为是正确的。
各个case和default的顺序是可以任意的,但习惯总是把default放在最后。switch存在的一些问题是,其中之一是它对case 可能出现的值太过放纵了,例如可以在switch的做好括号后声明一些变量,从而进行一些局部存储的分配,在最初的编译器中,这是一个技巧--绝大多数用于处理复杂复合语句的代码都可以被复用。switch语句缺省采用”fall through“,在97%的情况下都是错误的。break语句事实上跳出的是最近的那层循环语句或switch语句。
字符串常量的自动合并意味着字符串数组在初始化时,如果不小心漏掉了一个逗号,编译器不会发出错误信息,而是悄无声息的合并在一起。在最后一个字符串末尾的逗号并不是打错字,而是从早期的C语法中继承下的东西,不管存在是否有意义,ANSI
C对它的解释是是C语言自动生成容易些。
太多的缺省可见性
定义C函数时,在缺省情况下名字是全局可见的,可以再名字前面加一个冗余的extern关键字,也可以不加,效果一样的。如果想限制这个函数的访问,就必须加个static关键字。
2.2 误做之过: 
C语言中属于“误做之过
”的特性,就是语言中有误导性质或是不适当的特性,这些特性有些跟C语言的简介有关,有些则更操作符的优先级有关。
C语言存在的一个问题就是它太简洁了,仅增加,修改或删除一个字符就会使程序成另外一个仍然有效却全然不同的程序。更糟糕的是,许多符号是被”重载的“在不同的上下文环境有不同的意思。当sizeof的操作数是个类型名时,两边必须加上括号(这常常使人认为他是一个函数),但操作数如果是一个变量则不必加括号。你让一个符号所表达的意思越多,编译器就越难检测到这个符号在你的使用中所存在的异常情况。
2.3少做之过
属于少做之过的特性就是语言应该提供但未提供的特性。
C语言有最大一口策略,这种策略表示如果下个标记有超过一种的解释方案,编译器将选取最长的字符序列方案。
第三章 声明
参数按照从右到左的次序压倒堆栈中,这种说法过于简单了,参数在传递时首先尽可能地存放到寄存器中(追求速度)。
一个int型变量跟只包含一个Int型成员的结构变量S在参数传递时可能完全不同,一个int型参数一般会被传递到寄存器中,而结构变量s 在参数则很可能被传递到堆栈中。
结构体:
在结构中放置数组,如struct s_tag{int a[100];};现在可以把数组当做第一等级的类型,用赋值语句拷贝整个数组,一传值的方式传递到函数,或者把它作为函数的返回类型。在典型的情况下并不需要频繁的对整个数组进行赋值操作。但是如果需要这样做,可以通过放入结构中实现。
C语言声明的优先级
A 声明从它的名字开始读取,然后按照优先级一次读取。
B 优先级从高到低一次是:
1声明中被括号括起来的
2后缀符号()【】
3前缀符号 *
不要在一个typedef中放入几个声明器,千万不要把typedef嵌到声明的中间部分。
不要为了方便起见对结构使用typedef,这样做唯一的好处是能使你不必书写struct关键字,但这个关键字可以向你提示一些信息,你不应该把它省掉。
typedef int x[10],#define x int[10]的区别
正确思考这个问题的方法是把typedef看成是一种彻底的“封装”类型--在声明它之后不能再往里面增加别的东西。它和宏的区别体现在两个方面。
首先,可以用其他类型说明符对宏类型进行扩展,但对typedef所定义的类型名却不能这样做。如下所示:
#define peach int
unsigned peach i;/没问题
typedef int peach
unsigned peach i;//错误非法。
其次:
在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。
extern 对象声明告诉编译器对戏那个的类型和
名字,对象的内存分配在别处进行。由于并未在声明中为数组分配内存,所以并不需要提供关于数组长度的信息。
并且 extern int * i;
extern int i[];
是不一样的。
第四章 数组和指针
出现在赋值符号左边的符号有时被称为左值(由于它位于“左手边”或“表示地点”),出现在赋值符号右边的符号有时则被称为右值,编译器为每个变量分配一个地址(左值),这个地址在编译时可知,而且该变量在运行时一直保存于这个地址,相反存储于该变量中的值只有在运行时才可知。如果需要用到变量中存储的值,编译器发出指令从指定地址读入变量的值,并将它存于寄存器。
char  *p="abcdef";
char p[]="abcdef';
前者编译器告知p是一个纸箱字符的指针(相反数组的定义告诉编译器p是一个字符序列)p[i]表示"从p所指向的地址开始,前进一步,每步都是一个字符",既然把p声明为指针,那么不管p原先是定义为指针还是数组,都会按照上面所示的三个步骤进行操作;。
前者间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。如果指针有一个小标[1]就把指针的内容更加上一作为地址,从中去数据
后者 直接访问数据,a[1]指示简单地一a+1为地址取数据。
第五章;链接器
收集模块准备执行的三个阶段的规范名称是连接-编辑 , 载入, 动态链接。
如果函数库的一份拷贝时可执行文件的物理组成部分,那么我们称之为静态链接;如果可执行文件只是包含了文件名,让载入器在运行时能够寻程序所需要的库函数,那么我们称之为动态链接。静态链接的模块被连接编辑并载入运行,动态链接的模块被连接编辑后载入,并在运行时进行连接以便运行。程序执行时,在main()函数被调用前,运行时载入器把共享的数据对象载然入到进程的地址空间。外部函数被真正调用之前,运行时载入器不解析它们,所以即使链接了函数库,如果没有实际调用,也不会带来而外开销。即使是在静态链接中,整个文件也没有被全部载如到可执行文件中,所装入的只是所需要的函数。动态链接时是一种动态链接,这意味着程序在运行时必须能够到它们所需要的库函数,连接器吧库文件名或路径名植入可执行文件做到这一点,这意味着,库函数的路径不能够随意移动,
动态链接可以从两个方面提高性能:
一:动态链接可执行文件比功能相同的链接的体积小。
二:所有动态库连接到某个特定的函数库的可执行文件在运行时共享该函数库的一个单独拷贝。操作系统内核保证映射到内存中的库函数可以被所有使用它的进程共享。这就提供了更好
的I/O和交换空间利用率,节省了物理内存,提高了系统的整体性能。
静态库被称为archive,它们通过ar(用于archive的实用工具)来创建和更新。
动态链接库由链接编辑器ld创建根据约定,动态库的文件扩展名为“.so”,表示“shared object共享对象”。
涉及到UNIX的部分没有看***********************
第六章:运行时数据结构
a.out--汇编程序和链接编辑输出格式 它不是胡编程序输出而是连接器输出。
编译器设计者通过不存储为使用的信息来提高速度,其他的优化措施包括把信息保存在寄存器而不是堆栈中,尽管我们谈到了将过程活动记录压到堆栈中,但是过程活动记录并不一定要存在于堆栈中。事实上,尽可能地把过程活动记录的内容更放到寄存器中会使函数调用的速度更快,效果更好。
第七章 对内存的思考
就像堆栈能够根据需要自动增长一样,数据段也包含了一个对象,用于完成这项工作,这就是堆,它用于动态分配的存储。用malloc函数,callvoc 函数与malloc函数类似,但它在返回指针之前先把分配好的
内存的内容清零,realloc函数把内存拷贝到别的地方然后将指向新地址的指针返回给你。这在动态增长的表很有用。alloca()分配在栈上的动态内存,它分配的内存会自动释放。这并不适合那些比创建它们的函数生命周期更长的结构。
数据对齐的意思是数据项只能存储在地址是数据项大小的整数倍的内存位置上。
编译器通过自动分配和填充数据(在内存中)来对齐。当然在磁盘或磁带上并没有这样的对齐要求,所以程序员对它们可以很愉快地不必关心数据对齐,但是当他们把一个char指针转换为Int指针时,就会出现神秘的总线错误。
段错误或段违规是由于内存管理单元(负责支持虚拟内存的硬件)的异常所致,该异常则通常是由于解除引用一个未初始化或非法值的指针引起的。如果指针引用一个并不位于你的地址空间的地址,操作系统便会进行干涉。如int * p=0; *p=17;
一个微妙之处是,导致指针具有非法的值通常不是由于编程错误引起的,和总线错误不同,段错误更像是一个间接的症状而不是错误的原因。一个更糟糕的微妙之处是,如果未初始化的指针恰好具有未对其的值(对于指针所要访问的数据而言),它将会产生总线错误,而不是段错误。
通常导致段错误的几个直接原因:
解除引用一个包含非法值的指针。
解除引用一个空指针
在未得到正确的权限时进行访问。
用完了堆栈或堆空间
下面的说法过于简单,但在绝大多数框架的绝大多数情况下,总线错误意味着Cpu对进程引用内存的一些做法不满,而段错误则是MMU对进程引
用内存的一席情况发出抱怨。
第八章 为什么程序员无法分清楚万圣节和圣诞节
ANSI C函数原型的目的是使C语言成为一种更加可靠的语言。建立原型就是为了消除一种普通(但很难发现)的错误,就是形参和实参类型不匹配。ANSI C的函数原型就是才用一种新的函数声明形式,把参数的类型也包含在声明之中。函数的定义也作为相应的改变以匹配声明。在ANSI C中如果使用了新风格的函数定义,编译器就不会假定参数是准确声明的,于是便不进行类型提升,并据此产生代码。不要在函数的声明和定义中混用新旧两种风格。
复杂的类型转换可以按下面的3个步骤编写
(1)一个对象的声明,它的类型就是想要转换的结果类型
(2)删除标示符(以及任何如extern之类的存储限定符),并把剩余的内容放在一对括号里。
(3)把第二步产生的内容放在需要进行类型转换的对象的左边。
第九章;再论数组
对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。
什么时候数组和指针时相同的
规则一: 表达式中的数组名(与声明不同)被编译为一个指向该数组的第一个元素的指针
规则二: 下标总是与指针的偏移量相同
规则三: 在函数参数的声明中,数组名被编译成当做指向该数组的第一个元素的指针。
规则一个规则二合起来理解,就是对数组下标的引用总是可以写成“一个指向数组的起始地址的指针加偏
移量”。你只要记住在表达式中,指针和数组时可以互换的,因为它们在编译器里最终形式都是指针,并且都可以进行取下标操作,就像加法一样,取下标操作的操作数是可以交换的这就是为什么a[6]=6[a]都是正确的。
在处理一维数组时,指针并不见得比数组块,C语言吧数组下标改写为指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本模型。
之所以要把传递给函数的数组参数转换为指针时出于效率的考虑,要拷贝整个数组的效率时太低。
c编程网
不管程序员实际缩写的是哪种形式,函数并自动知道指针所指向的数组的范围,所以要有个约定,如数组一NUL结尾或另一个附加参数表示结束。注意有一样操作只能在指针里进行二无法再数组中进行,那就是修改它的值。数组名是不可以修改的左值,它的值不能改变的。
多维数组:
定义和引用多维数组唯一的方法就是使用数组的数组。尽管C语言把数组的数组当作多维数组,在思维模式中,吧数组当做是一种向量(即某种对象的一维数组,它的元素可以使另一个数组),就能极大的简化编程语言中这个复杂的领域。
在C语言的多维数组,最右边的下标是最先变换的,