`
yidongkaifa
  • 浏览: 4038695 次
文章分类
社区版块
存档分类
最新评论

c陷阱和缺陷学习笔记

 
阅读更多

1.1 = 赋值运算符

== 比较运算符

1.2

&& 逻辑或 ||逻辑与

& 按位或 | 按位与

1.3

如果(编译器)输入流截止至某个字符之前都已经被分解为一个个符号,那么下一个符号将包括从该字符之后可能组成一个符号的最长字符串。(编译器喜欢这么做。)

1.4

进制问题。注意:如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数。

010 = 8

0215 = 141 0195!=141

1.5字符与字符串:

单引号括起来的一个字符代表一个整数,双引号括起来的一个字符代表一个指针。


2 语法“陷阱”

2.1 理解函数声明

float *g(), (*h)();

*g() = *(g())

g是一个函数,该函数的返回值类型为指向浮点数的指针;h是一个函数指针,h是一个所指函数返回值为浮点类型的函数指针。

float (*h)();

h是一个指向返回值为浮点类型的函数的指针。

(float (*)());

一个“指向返回值为浮点类型函数的指针”的类型转换符。

2.2 运算符的优先级问题:查看上一篇博客:http://blog.csdn.net/start530/article/details/8091217

优先级比单目运算符要低的,接下来就是双目运算符。在双目运算符中,算数运算符的优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,赋值运算符,最后是条件运算符。
我们需要记住的最重要的两点是:
1、任何一个逻辑运算符的优先级低于任何一个关系运算符;
2、移位运算符的优先级比算数运算符要低,但是比关系运算符要高。


2.4 switch语句

case与break的对应关系

2.5 函数调用

f(); 运行该语句。

f; 计算函数f的地址

2.6 if与else匹配的问题

(不要被眼睛所迷惑)

3 语义“陷阱”

3.1指针与数组

对于一个数组,我们只能够做两件事;确定该数组的大小,以及获得指向该数组下标为0的元素的指针。

int calender[12][31];

int (*monthp)[31];

monthp = calender;

sizeof(calendar)的值是372(31x12)与sizeof(int)的乘积

3.2非数组的指针

C语言强制要求必须声明数组大小为一个常量

char *r,*malloc();

r=malloc(strlen(s)+strlen(t)+1);

if(!r)

{

complain();

exit(1);

}

strcpy(r,s);

strcpy(r,t);

free(r);

3.3作为参数的数组声明

char *Hello; char 指针;

char Hello[]; char数组;

3.4避免“举耦法”

记住:复制指针并不同时复制指针所指向的数据

ANSI C标准中禁止对string literal修改,试图修改字符串常量的行为是未定义的。

#include <stdio.h>

main()

{

char *p;

char *q;

p="xyz";

q = p;

q[1] = 'Y';

printf("%c/n",p[1]);

}

3.5空指针并非空字符串

在C语言中将一个整数的转换为一个指针,最后得到的结果取决于具体的编译器,只有一个例外:

常数0;

由0转换而来的指针不等于任何有效的指针。

将0 赋值给一个指针变量,绝对不能企图使用该指针所指向的内存存储的内容。

if(p==(char*)0) 合法;

if(strcmp(p,(char*)0)==0) 非法;

strcmp实现中会带有指针参数所指向内存的内容的操作。

如p为一个空指针

printf(p);

与printf("%s",p);

也是未定义的。

3.6边界计算与不对称边界

int i,a[10];

for(i = 1; i <= 10; i++)

{

a[i] = 0;

}

有的编译器认为a[10]这个数所在的内存实际上已分配给了i;所以死循环!

避免“栏杆错误”的两个通用原则:

1)考虑最简单情况下的特例,然后将得到的结果外推;

2)仔细计算边界,绝不掉以轻心。

3.7求值顺序

(&&,||,?:和,)存在规定的求值顺序。
&&和||首先对左侧操作数求值,只有在需要时才对右侧操作数求值。

在a?b:c中,操作数a首先被求值,再根据a的值再求操作数b或c的值。

在,中,首先对左侧操作数求值,然后该值被“丢弃”,再对右侧操作数求值。

分隔函数参数的逗号并非逗号运算符。

例外:当x和y在函数f(x,y)中的求值顺序是未定义的,而在函数g((x,y))中却是确定先x后y的顺序。在后一个例子中,函数g只有一个参数。这个参数的值是这样求得的,先对X求值,然后x的值被丢弃,接着求y的值。

错误实例:i = 0;

while(i < 0)

{

y[i] = x[i++];

}

此代码假设y[i]的地址在i自增操作执行前被求值,这一点没有任何保证。

i = 0;

while(i < n)

{

y[i++] = x[i];

}也不对;

对代码:

i = 0;

while(i < n)

{

y[i] = x[i];

i++;

}

意为:

for(i=0;i<n;i++)

{

y[i] = x[i];

}

对于数组结尾之后的下一个元素,取它的地址是合法的,而且绝少有C编译器能检查出这个错误。

3.8运算符&&,||和!

按位运算中:10||12的结果为1,因为10不是0,而且12根本不会被求值;在表达式10||f()中,f()也不会被求值。

3.9整数溢出

判断a与b是否溢出的关键在于:最高的符号位!

这时,很有可能要判断C编译器中的内部寄存器的状态!(可能为正,负,0和溢出的与或运算)

正确判断溢出的方法:

if((unsigned)a + (unsigned)b > INT_MAX)

complain();

此处INT_MAX是一个已定义常量,代表可能的最大整数值。ANSI C标准在<limits.h>中定义了INT_MAX;其他C语言实现上,读者要自己重新定义。
另一种方法:

if(a > INT_MAX-b)

complain();

3.10 为函数main提供返回值

main()

{

complain();

return 0;

}

第4章 连接

4.1什么是连接器

连接器的输入是一组目标模块和库文件。连接器的输出是一个输入模块。连接器输入目标模块和库文件,同时生成载入模块,看是否已有同步的外部对象。如果没有,连接器就将该外部对象增加到载入模块中;如果有,连接器就要开始处理命名冲突。

如果C语言实现中提供lint程序,切记要使用!

4.2声明与定义

int a;

如果其位置出现在所有函数体外,外部对象a的定义!说明了a是一个外部整型变量,同时为a分配了存储空间。

extern int a;

并不是对a的定义。只说明了a是一个外部整型变量,包括了extern关键字,显式的说明了a的存储空间是在程序的其他地方分配的。从连接器的角度看:这是一个对外部变量a的引用,而不是对a的定义。

4.3命名冲突与static修辞符

static int a;的含义与int a;相同。只不过,a的作用域限制在了一个源文件,对于其他的源文件,a是不可见的。

static也适用于函数

static int g(int x)

{

//g函数体

}

void f()

{

//其他内容

b = g(a);

}

4.4形参,实参与返回值

1.形参变量值在被调用时才分配内存单元,在调用结束时,即可释放所分配的内存单元。

因此,形参只有在函数内部有效,函数调用结束返回主调函数后则不能使用该形参变量。

2.实参可以是常量,变量,表达式,函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便将值传送给形参。

因此应预先用赋值,输入等方法使实参获得确定值。

3.实参和形参在数量上,类型上,顺序上应该严格保持一致。否则会发生“类型不匹配”错误。

4.函数调用中发生的数据传递是单向的,即只能把实参的值传递给形参,而不能把形参的值反向传递给实参。

在函数调用的过程中,形参的值发生变化,而实参中的值不会变化。

4.5检查外部类型

extren int n; 出现在一个源文件中。

long n;出现在另一个源文件中。

这样会产生问题。

保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型。

char filename[] ="/etc/passwd";

与extern char* filename;不同。

4.6头文件

将产生的重复命名的东西放到头文件中去。

库函数

5.1返回整数的getchar函数

getchar()的返回值为int型。

5.2更新顺序文件

为了保持与过去不能同时进行读写操作的程序的向下兼容性,一个输入操作不能直接随后紧跟一个输出操作。

while( fread(char *)&rec , sizeof(rec) , 1 , fp) == 1)

{

//对rec执行某些操作

if(//rec必须重新写入)

{

fseek(fp , -(long)sizeof(rec), 1);

fwrite((char *) &rec, sizeof(rec), 1, fp);

fseek(fp ,0L ,1);

}

}

5.3缓冲输出与内存分配

sendbuf控制程序输出的两种方式:即时处理。

暂存。

setbuf(stdout, buf);

1.static char buf[BUFSIZ];

可以将buf声明完全移到main函数以外。

2.动态分配缓冲区

char *malloc();

setbuf(stdout ,malloc(BUFSIZ));

5.4使用errno检测错误

在调用库函数时,应该首先检测作为错误指示的返回值,确定程序执行已经失败,然后再检查errno。搞清楚出错原因。

//调用库函数

if(返回的错误值)

检查errno

5.5库函数signal

#include <signal.h>

signal(signal type,handler function)

type标识signal函数要捕获的信号类型

handler function指定事件发生时,加以调用的事件处理函数

对于算术错误,signal处理函数唯一安全,可移植的操作就是打印一条出错信息,然后用longjmp或者exit退出程序。

signal函数尽可能简单,并将其组织在一起。

6预处理

6.1不能忽视宏定义中的空格

#define f (x) ((x) - 1)

等于

#define f(x) (x)((x) - 1) 编译报错

6.2宏不是函数

宏中的每一个参数最好加上括号,以免影响优先级的问题。

但有的宏会对变量运行两次,这样的宏一定要注意,能少用就少用。

biggest = x[0];

i = 1;

while(i < n)

biggest = max(biggest, x[i++]);

x[0] = 2;

x[1] = 3; biggest = ((biggest) > (x[i++]) ? (biggest) : (x[i++]));

x[2] = 1;

比较4个数的大小:

biggest = a;

if(biggest < b) biggest = b;

if(biggest < c) biggest = c;

if(biggest < d) biggest = d;

#define max(x,y) ( {/

typeof (x) _x = (x);/

typeof (y) _y = (y);/

(void) (&_x == &_y);/

_x > _y ? _x : _y;} )

第四行是比较x与y的类型是否匹配。

6.3宏不是语句

宏如果定义了if语句而未加else,很可能与程序中的if - else语句产生关联,这样的错误极难发现。

#define assert(e)

( (void)( (e) || _assert_error (_FILE_ , _LINE)) )

6.4宏不是类型定义

#define TI struct foo*

typedef struct foo *T2;

T1 a, b;

T2 a,b;

第一个struct foo *a, b;

第7章 可移植缺陷

7.1应对C语言标准变更

double square(double);

main()

{

printf("%g/n",squre(3));

}

7.2标识符的限制

ANSI标准只能保证的是:C实现必须能够区别前6个字符不同的外部名称。

7.3整数的大小

整数的大小可改,可以定义

typedef long tenmil;再使用,可移植性会好很多。

7.4字符是有符号整数还是无符号整数

如果编程者关注一个最高位是1 的字符其数值究竟是正还是负,可以将这个字符声明为无符号字符,(unsigned char)。这样,无论是什么编译器,在将该字符转换成整数时都只需将多余的位填充为0即可。而如果声明为一般的字符常量,那么在某些编译器上可能作为有符号数来处理,在另一些编译器上会作为无符号数来处理。

7.5移位运算符

在向右移位时,如果为无符号数,空出的位被0填充,

如果为有符号数,空出的位被0,也可用其副本填充。

移位运算允许的范围为:移位对象长为n,

移位计数大于或等于0,严格小于n。

low + high为非负。

mid = (low + high) >> 1;

mid = (low + high) / 2;

前者执行的速度快很多。

7.6NULL指针/内存位置0

#include <stdio.h>

int main(void)

{

char *p;

p = NULL;

printf("Location 0 contains %d/n", *p);

}

揭示了某个机器是如何处理内存地址0的

7.8随机数的大小

ANSI C定义了一个常数RAND_MAX;

7.9大小写的转换

tolower

toupper

分享到:
评论

相关推荐

    C语言陷阱与缺陷(学习笔记)

    精心整理,C语言学习必修,WORD文档,经典,好不容易找到的,感谢作者!

    C语言缺陷与陷阱(笔记)

    C语言缺陷与陷阱(笔记) 对正在学习C语言的同学,有点帮助

    c语言缺陷与陷阱(笔记)

    这是我在阅读的时候记录的经典的东西哦!希望大家可以从中学的到很多自己想要的东西啊!在这里希望大家学习愉快!

    c语言学习笔记

    学习 《c与指针》 以及 《c陷阱与缺陷》 两本书做的c基础内容总结

    史上最强的C语言资料

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与...

    c语言难点分析整理,C语言

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与...

    高级C语言详解

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与...

    高级C语言 C 语言编程要点

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与...

    C语言难点分析整理

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与...

    免费下载:C语言难点分析整理.doc

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与...

    高级进阶c语言教程..doc

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与...

    C语言难点分析整理.doc

    23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数...

    高级C语言.PDF

    C语言缺陷与陷阱(笔记) ............................................................................................................ 107 24. C语言防止缓冲区溢出方法 .......................................

Global site tag (gtag.js) - Google Analytics