http://www.cnblogs.com/hbiner/p/3591335.html?utm_source=tuicool&utm_medium=referralhtml
这段时间把《C陷阱和缺陷》看了,没时间本身写总结。就转一下别人的学习笔记吧http://bbs.chinaunix.net/thread-749888-1-1.html数组
Chapter 1 词法陷阱
程序中的单个字符孤立起来看并无什么意义,只有结合上下文才有意义,如p->s = "->";两处的-意义
是不一样的。
程序的基本单元是token ,至关于天然语言中的单词。 一个token的意义是不会变的。 而组成token 的字
符序列则随上下文的不一样而改变。
token之间的空格将被忽略。
1.1 = 不一样于 ==
1.2 &和|不一样于&&和||
1.3 词法分析中的贪心法
token分为单字符token和多字符token,如/ 和 == ,当有岐义时,c语言的规则是:每个token应包括
尽量多的字符。
另外token的中间不能有空白(空格,制表符, 换行符)
y = x /*p 应写为y = x / *p 或者y = x / (*p);
老编译器容许用=+来表明如今+=的含义。因此它们会将a=-1理解为a=- 1 即a = (a-1);
它们还会将复合赋值语句当作两个token,因而能够处理 a>> =1, 而现代的编译器会报错。
1.4 整型常量
常量前加0表明是8进制。
1.5 字符与字符串
用双引号引发的字符串, 表明的是一个指向无名数组起始字符的指针
a+++++b的含义是什么?
C不容许嵌套注释。
Chapter 2 语法陷阱
2.1 构造函数声明
构造函数声明的规则:按照使用的方式来声明。
任何C声明都由两部分组成:类型及相似表达式的声明符(declarator)。
float *g(), (*h)();
g是一个函数,该函数的返回值类型为指向浮点数的指针。 h是一个函数指针, h所指向函数的返回值为
浮点类型。()的优先级高于*。
由于float (*g)();表示g是一个指向返回值为浮点类型的函数的指针。因此(float (*)())表示一个“指向
返回值为浮点类型的函数的指针”的类型转换符。
一旦咱们知道如何声明一个给定类型的变量, 那么该类型的类型转换符就很容易获得了:只须要把声明
中的参量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来便可。
(*(void(*)())0)()表示什么意思呢?
若是fp是一个函数指针, 那么(*fp)()就表示对其所指的函数的调用。简写为fp()。但这只是简写而已。
而*((*fp)())能够简写为*fp()
根据上文(void(*)()) 表示一个“指向返回值为void的函数的指针”的类型。这里不过是对0做强制转换而
已。其实用typedef更好:
typedef void (*funcptr)();
(*(funcptr)0)();
signal的声明以下:
void (*signal(int, void(*)(int)))(int);
或者用typedef:
typedef void (*HANDLER)(int);
HANDLER signal(int, HANDLER);
2.2 运算符的优先级问题
注意条件运算符优先级比赋值运算符高,书上第22页是错的。
& > ^ > |
2.3 分号
2.4 switch 语句
2.5 函数调用
f();
是个函数调用。而f;则计算函数f的地址。
2.6 else
C语言容许初始化列表中出现多余的逗号。
Chapter 3 语义陷阱
3.1 指针与数组
C语言中只有一维数组, 并且数组的大小必须在编译期间就做为一个常数肯定下来。多维数组是经过一维
数组仿真的,由于数组的元素能够是任何对象,固然也能够是数组。
对数组,咱们只能作两件事,肯定其大小,以及得到指向该数组下标为0的元素的指针。其它的有关数组
的操做,其实是经过指针进行的。
若是两个指针指向的是同一个数组中的元素,咱们能够把这两个指针相减。若是它们指向的不是同一个数
组中的元素,即便它们指向的地址在内存中的位置正好间隔一个数组元素的整数倍,所得的结果仍然是无
法保证其正确性的。
若是在应该出现指针的地方出现了数组名,则数组名就被看成指向该数组下标为0的元素的指针。
int a;
p = a;
int *p;
是对的。但p = &a在ansi C中则是非法的。由于&a 是一个指向数组的指针,而p是一个指向整型变量的指针,
它们的类型不匹配。
因为a[i] 即*(a+i);而a+i即i+a;因此a[i]即i[a];但不推荐后者的写法
int cal[12][31];
int *p;
int i;
i = cal[4][7]等于i = *(cal[4] + 7);也等于i = *(*(cal + 4) +7);
p = cal; 是错误的,类型不匹配,后者是指向数组的指针。
咱们来声明指向数组的指针:
int (*ap)[31];
因而咱们能够这样写:
int cal[12][31];
int (*monthp)[31];
monthp = cal;
两 个指针不能相加。负数的移位运算不等于相应的乘或除运算。
3.2 非数组的指针
咱们要将s和t链接成r.
s = "abc";
t = "efg";
char *r;
strcpy(r,s);
strcat(r,t);
这并不能达到目的。
由于一是不能肯定r指向何处, 二是不能保证r所指向的地址处还应该有内存空间可供容纳字符串。
较好的是把第一行改成char r[100];只是这样的话,大小固定了。
正确的应该是:
#include <stdio.h>
#include <ctype.h>
int main (void)
{
char s[10];
char t[10];
char *r;
char *malloc();
r = malloc(strlen(s) + strlen(t) + 1);
if(!r)
{
complain();
exit(1);
}
scanf("%s",s);
/*getchar();*/
scanf("%s",t);
strcpy(r,s);
strcat(r,t);
printf("%s\n",r);
free(r);
}
3.3 做为参数的数组声明
咱们没有办法将一个数组做为函数参数直接传递。数组名会被转为指向该数组第一个元素的指针。
int strlen(char s[]){}
与下面的写法彻底相同:
int strlen(char* s){}
但其它地方就未必相同了。
下面两 个语句是彻底不一样的。
extern char *hello;
extern char hello[];
下面则是同样的
main(int argc, char* argv[]){}
main(int argc, char** argv){}
3.4 避免“举隅法”
复制指针并不一样时复制指针所指向的数据。
3.5 空指针并不是空字符串
把常数0转为指针,则指针不等于任何有效的指针,即 void 指针。其它将整数转为指针获得的结果未定
义。当常数0被转为指针时,这个指针绝对不能被解除引用(dereferenc)。换句话说,当咱们将0赋给一个指
针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。
下面的是合法的:
if (p == (char *) 0)
但下面是非法的
if (strcmp(p, (char *) 0) == 0)
若是p是一个空指针,即便printf(p);和printf("%s",p);的行为也是未定义的。
3.6 边界计算与不对称边界
数组的下标若是用入界口加出界口来表达(即10个元素,其下标为0 <= n < 10 ),则元素个数即为上界与下界
之差,即下界。若为空,则上界等于下界。任何状况下上界也永远不可能小于下界。
尽可能采用非对称边界法。
一个有N个元素的数组 ,咱们可使用a[N]进行比较和赋值,但不能引用其内容。
3.7 求值顺序
C语言只有四个运算符(&&, ||, ?: , 和 ,)存在规定的求值顺序。另外,分隔函数参数的逗号并不是逗号
运算符。例如,在x和y在函数f(x,y)中的求值顺序是未定义的,而在函数g((x,y))是先算x,再算y,y
的值为参数。特别是赋值运算符没有规定求值顺序。
3.9 整数溢出
无符号算术运算中,没有所谓的“溢出”一说。有符号运算中发生溢出,则结果未定义。
下面检测溢出的方法不可靠:
if(a + b <0)
complain();
应该这样:
if((unsigned) a + (unsigned) b >INT_MAX)
complain();
或者这样
if(a > INT_MAX - b)
complain();
3.10 为函数main提供返回值
若是没 有为函数声明返回类型,则默认为int.
free以后最好立刻就p = NULL;
Chapter 4 链接
4.1 什么是链接器
链接器一般把目标模块当作是由一组外部对象组成的。 第个外部对象都表明着机器内存中的某个部分,并
通达一个外部名称来识别。所以, 程序中的每一个函数和每一个外部变量,若是没有被声明为static,就都是一个
外部对象。 某些编译器会对静态函数和静态变量的名称作必定改变,将它们也做为外部对象。
除了外部对象,目标模块还可能包括了对其它模块中的外部对象的引用。
4.2 声明与定义
每一个外部变量只能定义一次。
4.3 命名冲突与static修饰符
4.4 形参、实参与返回值
每一个函数都要在调用以前进行声明定义,否则返回类型为int
若是一个函数没有float,short或者char类型的参数,在函数声明中彻底能够省略类型声明(定义不能省
略)
4.5 检查外部类型
同一个外部变量在不一样的地方被声明为不一样的类型,这种错误大部分编译器是检不出来的。
char file[]= "/etc/password";
与
extern char* file;
是不同的。
4.6 头文件
Chapter 5 库函数
C标准没有定义执行底层I/O操做的read和write函数。
5.1 返回整数的getchar函数
5.2 更新顺序文件
为了与之前的程序保持兼容,一个输入操做不能随后紧跟一个输出操做,反之亦然。若是要同时进行输入
和输出操做,必须在其中插入fseek函数的调用。
FILE *fp;
struct record rec;
while (fread((char *)&rec, sizeof(rec),1,fp) = 1)
{
/* */
if(/* */)
{
fseek(fp, -(long)sizeof(rec), 1);
fwrite((char *)&rec, sizeof(rec), 1,fp);
fseek(fp, 0l,1);
}
}
5.3 缓冲输出与内存分配
#include <stdio.h>
void main(void)
{
int c;
char buf[BUFSIZ];
setbuf(stdout,buf);
while((c = getchar()) != EOF)
putchar(c);
}
这个是不对的。buf最后一次被清空是在何时?答案是在main函数结束以后,做为程序交回控制给操做系
统以前C运行时库所必须进行的清理工做的一部分。可是在此以前buf已经被释放。
解决方法一是加上static 声明。也能够把buf声明彻底移到main函数以外。第二种办法是动态分配缓冲区,
在程序中并不主动释放分配的缓冲区
5.4 使用erron检测错误
不少的库函数,特别是那些与操做系统有关的,当执行失败时会经过一个名称为errno的外部变量,通知
程序该函数调用失败。
下面的是错误的:
/*调用库函数*/
if(errno)
/*处理错误*/
由于,在库函数调用没有失败的状况下,并无强制要求库函数必定要设置errno为0,这样errno的值可能
就是前一个执行失败的库函数设置的值。
下面更正了,可仍是错误的:
errno = 0;
/*调用库函数*/
if(errno)
/*处理错误*/
库函数在调用成功时,既没有强制要求对errno清零,但同时也没有禁止设置errno。
下面才是对的:
/* 调用库函数 */
if(返回的错误值)
检查errno
5.5 库函数signal
从理论上说,一个信号可能在C程序执行期间的任什么时候刻上发生,甚至可能出如今某些复杂的库函数(如
malloc)的执行过程当中。所以从安全的角度讲,信号的处理函数不该该调用上述类型的库函数。基于一样的原
因,从signal处理函数中使用longjump退出,一般状况下也是不安全的:由于信号可能发生在malloc 或者其它
库函数开始更新某个数据结构,却又没有最后完成的过程当中。所以signal处理函数可以作的安全的事情,彷佛
就只有设置一个标志而后返回,期待之后主程序可以检查到这个标志,发现一个信号已经发生。
然而,就算这样作也并不老是安全的。当一个算术运算错误引起一个信号时,某些机器在signal处理函
数返回后还将从新执行失败的操做。所以对于算术运算错误,signal处理函数的唯一安全、可移植的操做就是
打印一条出错消息,而后使用longjump或exit当即退出程序。
当一个程序异常终止时,程序输出的最后几行经常会丢失,缘由是缓冲。
Chapter 6 预处理器
6.1 不能忽视空格
6.2 宏并非函数
6.3 宏并非语句
#define assert(e) ((void)((e)||_assert_error(_FILE_,_LINE_)))
6.4 宏并非类型定义
咱们没有办法在一个C表达式的内部声明一个临时变量。
避免反作用的一个办法就是再引入一个变量。
在某个上下文中本应须要函数而实际上却用了函数指针,那么该指针所指向的函数将会自动地被取得并替换这
个函数指针。
Chapter 7 可移植性缺陷
7.1 应对C语言标准变动
7.2 标识符名称的限制
c标准所能保证的只是,c实现必须可以区别出前6个字符不一样的外部名称,且并无要求区分大小写。
7.3 整数的大小
一个普通(int)整数足够大以容纳任何数组下标。
字符长度由硬件决定
7.4 字符是有符号整数仍是无符号整数
若为有符号,则将其转为int时,应该同时复制符号位,而无符号,则填 0便可。
一个常见的错误是:若是c是一个字符变量,使用(unsigned)c就可获得与c等价的无符号整数。这是错误
的,由于在将字符c转换为无符号整数以前,c将先被转为int型,而此时可能获得非预期的结果。
正确的是使用语句(unsigned char)c,这样就直接转换。
7.5 移位运算符
若是被移位的对象长度是n位,那么移位计数必须大于或等于0,而严格小于n。
即便某些c实现将符号位复制 到空出的位中,有符号整数的向右移位运算也并不等于除以2的某次幂。
(-1)>>1这通常不可能为0,但(-1)/2通常为0.
7.5 内存位置0
NULL指针并不指向任何对象,只能用于赋值或比较运算。
7.7 除法运算的截断
q = a / b;
r = a % b;
C 语言的定义只保证q*b+r==a,以及当a>=0且b>0时,保证|r|<|b|以及r>=0.最好避免a为负值。
7.8 随机数的大小
RAND_MAX
7.9 大小写转换
7.10 首先释放,而后从新分配
注意早期的C实现能够realloc一个已经free了的指示针。
7.11 一个例子
由于字符串常量能够用来表示一个字符数组,因此在数组名出现的地方均可以用字符串常量末端替换。
如:
"0123456789"[n%10]
-n可能溢出,由于最小负数的绝对值大于最大正数的绝对值。因此改亦正数的符号不会有问题,而改变
负数的符号则可能有问题。
void printnum(long n, void (*p)())
{
if(n<0)
{
(*) ('-');
n=-n;
}
if(n>=10)
printnum(n/10,p);
(*p)((int)(n%10) + '0');
}
上面的是有问题的。下面的才是对的:
void printneg(long n, void (*p)())
{
long q;
int r;
q = n / 10;
r = n % 10;
if(r>0)
{r -= 10;
q++;
}
if (n <= -10)
printneg(q,p);
(*p)("0123456789"[-r]);
}
void printnum (long n, void (*p)())
{
if(n < 0)
{
(*p)('-');
printneg(n,p);
}
else
printneg(-n,p);
}安全