(经典)C语言陷阱和缺陷

合集下载

C陷阱与缺陷读书笔记

C陷阱与缺陷读书笔记

首先释放,然后重新分配
在UNIX系统中允许先释放,再分配内存(如realloc函数的机制),在这种情况下,可以有以下的写法: free(p);
p = realloc(p,newsize); for( p = head ; p != NULL; p = p->next) free((char *)p);
讨论交流:xiangdong_9013@
除法运算时发生的截断
1.除法运算时的截断原则有三条,需放弃其中一条以满足现实是32位?根据具体机器决定。
大小写转换
C语言的历史上对大小写的转换有函数和宏两种方式,但是宏会有一个问题,当不是大写字母时,对其进行tolower操作就会得到乱码,但与此同时,函数却会带来性能上的 开销。
4.连接
什么是连接器? 分别编译的思想 处理命名冲突 输入为一组目标模块和库文件,输出为一个载入模块 声明与定义 声明
int a;声明了一个外部整型变量 int a = 7; extern int a;声明了一个对外部整型变量a的引用
每个外部变量只定义一次 命名冲突与static修饰符
1.两个具有相同名称的外部对象实际上表示同一个对象。(例如在多个源文件中都定义了int a); 2.static修饰符将变量作用域限制在一个源文件中;
讨论交流:xiangdong_9013@
4.y[i] = x[i++]中i的求值顺序是未定义的,可能造成意想不到的结果; 5.&&与||运算符,在左边操作数已经能够确定表达式的真值时,不会再对右侧操作数求值;
整数溢出
有符号操作数运算时容易发生整数溢出
为main函数提供返回值
main() { ****; return 0; } 以上才是正确的写法,不写return 0 在某些情况下会导致问题。

C陷阱与缺陷

C陷阱与缺陷

1.1&,| :按位运算&&,|| :逻辑运算= 不同于==来看这样一个例子,while (c=' ' || c== '\t' || c=='\n')c = getc(f);由于程序员在比较字符… ‟和变量c时,误将== 写成= ,那么while后的表达式恒为1,因为' '不等于零,它的ASCII码值为32。

为了避免这种情况,我们可以这样写while(''==c || '\t'== c || '\n'==c) ,这样即使不小心将== 写成= ,编译器也会报错。

★★在C中,单引号括起来表示一个整数,双引号括起来表示指针。

例如,字符和字符串。

1.3 词法分析中的“贪心法”C编译器读入一个符号的规则:每一个符号应该包含尽可能多的字符,方向为自左向右。

.大嘴法编译器在解析字符的时候会尽可能的解析成可能组成一个符号的最常的字符串。

例如,a---b <==> (a--) -b而y = x/*p; 中/* 被编译器理解为一段注释的开始,如果本意是用x除以p所指向的值,应该重写如下y = x/ *p; 或更加清楚一点,写作y = x/(*p);★★在用双引号括起来的字符串中,注释符/* 属于字符串的一部分,而在注释中出现的双引号""属于注释的一部分。

整数常量的第一个字符是数字0,那么该常量将被视为八进制数。

单引号所括起的字符代表一个整数,双引号括起的则代表一个指针(字符数组,自动加0)1.理解函数声明通过认识这个让人“不寒而栗”的式子:(*(void(*)())0)()1)把0强制转换成了类型void(*)(),这是一个指向函数的指针,所指向的这个函数返回值为voidps:a)float *g():这是一个函数,返回指针float*,因为()的优先级比*高。

C语言之陷阱与缺陷详解

C语言之陷阱与缺陷详解

C语⾔之陷阱与缺陷详解⽬录⼀、前⾔⼆、字符指针三、边界计算与不对称边界1.经典错误①2.经典错误②3、⼩结四、求值顺序五、运算符&& ||和!总结⼀、前⾔⼆、字符指针结论⼀:复制指针并不会复制指针所指向的内容。

两个指针所指向位置相同,实际为同⼀个指针。

结论⽽:开辟两个数组,即使两个数组内容相同,地址也绝不相同。

三、边界计算与不对称边界1.经典错误①int main(){int i = 0; int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };for (i = 0; i < 13; i++){arr[i] = 0;printf("haha");}return 0;}计算的结果是程序陷⼊死循环分析:1.栈区默认先使⽤⾼地址,再使⽤低地址2.数组内元素随下标增长,地址由低到⾼变化调试后即可发现,i与arr[9]的地址相差3字节,所以i即为实际不存在的arr[12].[补充知识:ANSI c标准允许这种⽤法——数组中溢界元素的地址位于数组所占内存之后,这个地址可以进⾏赋值和⽐较,但是不能解引⽤(若是数组之前存在溢界则语法不允许)] 2.经典错误②⼗⽶长的围栏每⼀⽶就需要⼀根栏杆⽀撑,则共需要⼏根栏杆? 113、⼩结栏杆问题你若不假思索可能会回答为10。

栏杆问题的根源正是加减⼀带来的困惑对此我们坚持以下原则原则⼀:考虑最简单的特例(如考虑20到10间有⼏个数,20-10还要+1吗。

不妨考虑10到10有⼏个数)原则⼆:仔细计算边界⽽在实际编程中,⼀个编程技巧则可以"⼀⾔以蔽之",即不对称边界。

x>=0 && x<16 要优于 x>=0 && x<=15不对称边界上界-下界就是之间所包含的数。

四、求值顺序总结:c语⾔中只有四个运算符(&& ;|| ;?: ;,)明确规定了求值顺序&&和||先对左边求值,只在需要时对右边求值:if(y!=0 && x/y>a)如此避免除0错误。

C语言技术常见陷阱及避免方法

C语言技术常见陷阱及避免方法

C语言技术常见陷阱及避免方法C语言作为一门广泛应用于嵌入式系统和操作系统开发的高级编程语言,具有高效、灵活和可移植等优点。

然而,由于其语法灵活性和底层操作的特性,C语言也存在一些常见的陷阱,如果不注意避免,可能会导致程序的错误和不稳定性。

本文将介绍一些常见的C语言技术陷阱,并提供相应的避免方法。

1. 内存管理陷阱在C语言中,内存管理是程序员需要特别关注的一个重要方面。

常见的内存管理陷阱包括内存泄漏和野指针。

内存泄漏指的是程序在分配内存后没有正确释放,导致内存资源浪费。

而野指针则是指指向已经释放或未初始化的内存地址,会导致程序崩溃或产生未定义行为。

避免内存泄漏的方法是在每次分配内存后,及时使用free()函数释放已分配的内存。

另外,可以使用内存分析工具来检测内存泄漏问题。

避免野指针问题的方法是在指针使用前进行初始化,并在释放内存后将指针置为NULL,以避免误用。

2. 数组越界访问陷阱在C语言中,数组越界访问是一种常见的错误操作。

当程序试图访问数组的越界元素时,可能会导致数据损坏、程序崩溃或者安全漏洞。

为避免数组越界访问问题,可以使用循环结构和条件判断来控制数组的访问范围。

另外,可以使用辅助变量来记录数组的长度,以避免超出数组边界的访问。

3. 整数溢出陷阱在C语言中,整数溢出是一种常见的错误情况。

当一个整数变量的值超出了其数据类型所能表示的范围时,会导致溢出现象,结果可能会产生错误的计算结果。

为避免整数溢出问题,可以使用合适的数据类型来存储变量,并在计算过程中进行溢出检查。

此外,可以使用条件判断来避免溢出情况的发生。

4. 字符串处理陷阱在C语言中,字符串处理是一项常见的任务。

然而,由于C语言中字符串以null字符结尾,容易引发一些错误情况,如缓冲区溢出、内存泄漏和安全漏洞等。

为避免字符串处理问题,可以使用安全的字符串处理函数,如strncpy()、strncat()等,来限制字符串的长度。

此外,可以使用合适的缓冲区大小来存储字符串,并进行输入验证,以避免缓冲区溢出问题。

C陷阱与缺陷

C陷阱与缺陷

C陷阱与缺陷P6:当程序员本意是作比较运算时,却无意写成了赋值运算,例如:本意是想检查x是否等于y,if(x=y) break;而实际上市将y赋值给了x,然后检查该值是否为零,再例如:本例的循环语句是想跳过文件的空格符、制表符和换行符:while(c=’‘||c==’\t’||’\n’)由于程序员在比较字符’’和变量c时,误将比较运算符==写成赋值运算符=,因为赋值运算符的优先级低于逻辑运算符||,因此是这样的:’‘||==’\t’||’\n’因为’’不等于零(’’的ASCII码值是32),那无论变量c此前是何值,上述表达式求值都是1,循环一直到整个文件结束。

P9:y=x/*p;本意是用x除以p所指的值,再把商给了y,但是这里有/*是注释的开始,所以不会顾及到p,应该是y=x/(*p);P16:float ff();这个声明的含义是:表达式ff()求值是一个浮点数,也就是说ff是一个返回值为浮点类型的函数,类似地,flaot *fp;这个声明含义为*fp是一个浮点数,fp是一个指向浮点数的指针。

Float *g(),(*h)();表示*g()与(*h)()是浮点数表达式,因为()优先级高于*,即*g()也就是*(g()):即g是一个函数,该函数的返回值是指向浮点数的指针,同理,h 是一个函数指针,h所指函数的返回值为浮点类型。

P20:if(flags&FLAG!=0) 这有一个错误的语句,因为!=运算符的优先级要高于&运算符,所以上式被解释为:if(flags&(FLAG!=0)),因此除了FLAG恰好为1,FLAG为其他式子都是错误的。

P21:如果p是一个函数指针,要调用p所指向的函数,必须写成:(*p)(),如果写成*p(),编译器会解释成*(p())P23:while(c=getc(in)!=EOF) 这个的本意是想c首先被赋予函数gstc(in)的返回值,然后与EOF比较是否达到文件结尾以便决定是否终止循环,但是由于赋值运算要低于任何一个比较运算,所以实际上c的值是gstc(in)的返回值予与EOF比较的结果,此处getc(in)的返回值只是一个临时变量,在与EOF比较后被丢弃。

C语言中的缺陷与陷阱

C语言中的缺陷与陷阱

C语言中的缺陷与陷阱(博雅课期末作业:计算机网络安全与防护)刚进大学,学的第一门计算机语言就是C语言,这是我第一次接触到计算机语言,对它一无所知。

有些同学之前已经学过了一些类似的语言,例如VB、PASCAL等等。

因为从来没有学过计算机语言,所以在学习C语言的过程中,遇到了很多困难,许多问题都不理解。

因此虽然我已经学完了这本书,但是对书中的内容都不知甚解。

学过C语言的人都知道,指针是C语言中的一个重要概念,也是它的一个重要特色。

正确而灵活地运用指针,可以有效地表示复杂的数据结构;能动态分配内存;方便地使用字符串;有效而方便地使用数组;在调用函数时能获得一个以上的结果;能直接处理内存单元地址等。

掌握指针的应用,可以使程序简洁、紧凑、高效。

但是任何事情都有两面性,C语言中的指针也是如此。

举几个例子来说:1.指针与数组在新的一年里我们需要将日历(calendar)数组清空:int (*monthp)[31];for(monthp=calendar;monthp<&calendar[12];monthp++){int *dayp;for(dayp=*monthp;dayp<&monthp[31];dayp++)*dayp=0;}在这个程序中用指针monthp以步进的方式遍历数组calendar。

使用指针来操纵数组,常常需要跟C语言中最为“晦暗不明”的部分打交道,并常常遭遇到潜伏着的编译器bug。

因此使用指针时,我们必须得小心翼翼,否则,程序很容易出错。

2.非数组的指针有一段程序如下:char *r,*malloc();r=mollac(strlen(s)+strlen(t));strcpy(r,s);strcat(r,t);这个例子有三个错误:一、malloc函数有可能无法提供请求的内存,这种情况下malloc 函数会通过返回一个空指针来作为“内存分配失败”事件的信号。

二、给r分配的内存在使用完成之后应该立即释放。

阅读《C陷阱与缺陷》的知识增量

阅读《C陷阱与缺陷》的知识增量

看完《C陷阱与缺陷》,忍不住要重新翻一下,记录一下与自己的惯性思维不符合的地方。

记录的是知识的增量,是这几天的流量,而不是存量。

这本书是在ASCI C/C89订制之前写的,有些地方有疏漏。

第一章词法陷阱1.3 C语言中解析符号时使用贪心策略,如x+++++y将被解析为x++ ++ +y,并编译出错。

1.5 单引号引起的一个字符代表一个对应的整数,对于采用ASCII字符集的编译器而言,'a'与0141、97含义一致。

练习1.1 嵌套注释(如/*/**/*/)只在某些C编译器中允许,如gcc4.8.2编译时是不支持的。

第二章语法陷阱∙ 2.6 else始终与同一个括号内最近的未匹配的if结合第三章语义陷阱∙ 3.1 int a[12][31]表示的是一个长度12的数组,每个元素是一个长度31的数组。

∙ 3.1 在需要指针的地方如果使用数组名来替换,那么数组名就被视为其下标为0的元素的指针,p = &a的写法是非法的(gcc4.8.2只是警告)。

∙ 3.2 如何连接两个给出的字符串s、t?细节很重要,书中给出的答案如下:char *r,*malloc()//原文称不能直接声明一个s、t长度之和的数组,但c99可以声明变长数组,已经可以了//记得要把长度加1r = malloc(strlen(s) + strlen(t) +1);//必须判断内存是否分配成功if(!r){complain();exit(1);}strcpy(r,s);strcat(r,t);......//完成之后一定要释放rfree(r);∙ 3.6 如何正确计算数组的边界?原则一,考虑最简单情况下的特例;原则二,仔细计算边界。

∙ 3.6 以下一段代码为何引起死循环?这是因为在内存地址递减时,a[10]就是i。

int i,a[10];for(i = 1; i<=10; i++)a[i] = 0;∙∙ 3.6 边界的编程技巧:用第一个入界点和第一个出界点表示数值范围,即[low,high)。

15个c语言易犯错误

15个c语言易犯错误

陷阱1:忽略大小写的区别1#include<stdio.h>2void main()3{4int a=10;5a+=a;6printf("%d\n",A);7}复制代码这个很简单,是基础,c语言变量区分大小写。

代码中的a与A不是同个变量,编译出现A 没定义的错误。

陷阱2:“{}”与“()”使用不当造成错误8#include <stdio.h>9void main()10{11int i,j;12int a[2][3]={(1,2,3),(4,5,6)};13printf("array a:\n");14for(i=0;i<=1;i++)15{16for(j=0;j<=2;j++)17{18printf("%d",a[i][j]);19}20printf("\n");21}22}2324}复制代码程序结果不能正常输出数组每个元素,编译{(1,2,3),(4,5,6)};时,先进行括号内的逗号运算(取逗号最后的数值),编译生成{3,6};其它元素为0。

正确的写法:{{1,2,3},{4,5,6}};陷阱3:在if,while,#include,#define,for后直接加分号,如for(int a=1;a<10;a++);如果是while,程序一般执行死循环,int a=1;while(a);如果是if,判断语句无效果,比如。

if(a>0);a=-1;无论a是否大于0,结果都是a=-1;如果是#include,程序编译的时候提示错误,无法引用库文件;如果是#define,比如#define a 200;程序在预编译的时候,200;包括分号一同被替换进程序,程序不可能正常编译如果是for循环,跟if一样,事与愿违,循环做无用功,本想循环的printf语句只执行一次。

for(int a=1;a<10;a++);printf("%d",a);程序只输出一次a值。

C陷阱与缺陷_笔记

C陷阱与缺陷_笔记

C陷阱与缺陷笔记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 (*)())表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。

一旦我们知道如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只需要把声明中的参量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。

读C陷阱和缺陷(三)

读C陷阱和缺陷(三)

读C陷阱和缺陷(C Traps and Pitfalls)(三)5、预处理器使用预处理器大致有两个方向的原因:(1)需要将某个变量在程序中所有实现的实例全部修改。

这一条在C++中已用const定义变量替代了。

(2)宏处理,来替代一个简单的函数,如putchar等。

这一条在C++中已用内联函数替代了。

宏只对文本起作用,提供了一种对组成C程序的字符进行变换的方式,面并不作用于程序中的对象。

不能忽视宏定义的中空格;最好在宏定义中把每个参数都用括号括起来,同样把整个结果表达式也用括号括起来。

此外宏可能会遇到的尴尬可能是在自增和自减中违背了原来的意思:#define abs(x) ((x)>=0?(x):-(x))abs(x);//则可能出问题。

宏不是语句。

_FILE_,_LINE_是内建于处理器的宏,会自动扩展为所在文件的文件名和所处理代码行的行号。

宏不是类型定义。

#define T1 struct Me*typedef struct Me *T2;T1 a,b;=>struct Me *a,b;T2 c,d;=>struct Me *c,*d;6、Koenig和Moo语录对C++(1)所谓面向对象编程,就是使用继承和动态绑定机制编程。

(2)用类来表示概念。

(3)避免使用指针。

(4)使用程序库。

(5)避免重复。

如果你发现自己在程序的两上不同部分里做了相同的事情,试着把两具部分合并到一个子过程中。

如果你发现两个类的行为相近,试着把他们的相似部分统一集中到基类或模块中。

7、可移植性错误1)如果c是一个字符变量,使用(unsigned)c就可得到与c等价的无符号数,这会失败的。

因为在将字符c转换为无符号整数时,c将首先被转换成int型整数,面此时就可能非预期的结果(最高位会扩展,而字符的最高位是1还是0,则关系到扩展后是正数还是负数)。

正确的方法是:使用语句(unsigned char)c,因为一个unsigned char类型在字符在转换成无符号整数时无需要首先转换为int型整数,而是直接进行转换。

C语言技术中需要注意的常见陷阱

C语言技术中需要注意的常见陷阱

C语言技术中需要注意的常见陷阱C语言作为一门广泛应用于系统开发和嵌入式领域的编程语言,其灵活性和高效性备受开发者青睐。

然而,正是由于其底层性质和灵活性,C语言也存在一些常见的陷阱,容易导致程序错误和安全问题。

本文将探讨一些常见的C语言陷阱,并提供相应的解决方案。

1. 内存管理错误C语言中的内存管理是开发者必须重视的问题之一。

常见的内存管理错误包括内存泄漏、野指针和缓冲区溢出。

内存泄漏指的是程序在分配内存后未及时释放,导致内存资源浪费。

野指针则是指指向已经释放或未分配的内存地址,使用野指针可能导致程序崩溃或产生不可预料的行为。

缓冲区溢出是指向数组或缓冲区写入超过其容量的数据,可能导致数据覆盖和安全漏洞。

解决这些问题的方法包括合理使用malloc和free函数进行内存分配和释放、及时检查指针的有效性,以及使用安全的字符串处理函数(如strcpy_s和strcat_s)来避免缓冲区溢出。

2. 整数溢出C语言中整数溢出是一个常见的错误,特别是在进行数值计算时。

当一个整数超过其数据类型所能表示的范围时,会发生溢出,导致结果错误。

例如,当一个无符号整数变量达到最大值后再加1,结果会变为0,而不是正确的数值。

解决整数溢出的方法包括使用适当的数据类型来存储数值,进行溢出检查,以及使用安全的数值计算函数(如加法函数add_with_overflow)来避免溢出问题。

3. 字符串处理C语言中的字符串处理需要格外小心,容易导致缓冲区溢出和安全漏洞。

常见的问题包括未对字符串长度进行检查,使用不安全的字符串处理函数(如strcpy和strcat),以及未对输入进行验证和过滤。

解决这些问题的方法包括使用安全的字符串处理函数(如strncpy和strncat),对字符串长度进行检查,以及对用户输入进行验证和过滤,以防止恶意输入导致的安全问题。

4. 多线程并发在多线程并发编程中,C语言需要特别注意线程同步和竞态条件问题。

竞态条件指的是多个线程同时访问共享资源,导致结果不确定或错误。

c语言经典书籍详讲c陷阱和缺陷

c语言经典书籍详讲c陷阱和缺陷

C语言是一门经典而又复杂的编程语言,许多程序员在学习和使用C语言时都会遇到各种陷阱和缺陷。

为了帮助大家更好地理解和应对这些问题,本文将重点介绍几本经典的C语言书籍,帮助读者深入了解C语言的陷阱和缺陷。

一、C语言陷阱和缺陷的挖掘C语言是一门非常古老的编程语言,虽然经典而强大,但也有其独特的陷阱和缺陷。

这些陷阱和缺陷可能使得程序出现意想不到的错误,给程序的开发和维护带来极大的困难。

1. 指针操作的陷阱指针是C语言中非常重要的概念,但指针操作也是C语言中的一个大坑。

误操作指针可能导致内存泄漏、段错误等严重问题,因此程序员在使用指针时一定要格外小心。

《C陷阱和缺陷》一书对指针操作的陷阱进行了深入的挖掘和讲解,帮助读者更好地理解和避免指针操作中的常见问题。

2. 内存管理的缺陷C语言中的内存管理需要程序员手动进行,而手动内存管理往往容易出现内存泄漏、段错误等问题,给程序的开发和维护带来很大的麻烦。

《C陷阱和缺陷》一书对内存管理的缺陷进行了全面的剖析,帮助读者理解内存管理中的常见问题,并提出了一些解决方案。

3. 其他常见陷阱和缺陷除了指针操作和内存管理外,C语言中还存在许多其他常见的陷阱和缺陷,如数组越界、类型转换、宏替换等问题。

《C陷阱和缺陷》一书对这些常见问题进行了系统的总结和分析,帮助程序员更好地理解和应对这些问题。

二、经典C语言书籍推荐在学习和使用C语言时,选择一本好的教材非常重要。

下面将推荐几本经典的C语言书籍,帮助读者更好地学习和理解C语言的陷阱和缺陷。

1. 《C陷阱和缺陷》这本书是C语言领域的经典之作,作者对C语言中的各种陷阱和缺陷进行了深入的研究和总结,帮助读者避免在实际编程中犯同样的错误。

这本书的内容非常丰富,覆盖了C语言中的各个方面,具有很高的参考价值。

2. 《C专家编程》这本书是C语言领域的另一部经典之作,作者是计算机科学界的泰斗之一。

这本书不仅介绍了C语言的基本概念和语法,还深入剖析了C 语言中的一些高级技巧和陷阱,是一本非常值得一读的书籍。

C陷阱与缺陷

C陷阱与缺陷

C陷阱与缺陷C陷阱与缺陷第⼀章词法“陷阱”符号(token):指的是程序的⼀个基本组成单元,起作⽤相当于⼀个句⼦中的单词=不同于==&和|不同于&&和||词法分析中的“贪⼼法”C语⾔规则:每个符号应该包含尽可能多的字符整型常量整形常量的第⼀个字符是数字0,那么这个常量将被视作⼋进制数字符和字符串⽤单引号引起的⼀个字符实际上代表⼀个整数,整数值对应于该字符在编译器采⽤的字符集中的序列值。

⽤双引号引起的字符串,代表的却是⼀个指向⽆名数组起始字符的指针。

第⼆章词法“陷阱”理解函数声明构造表达式的规则:按照使⽤的⽅式来声明。

硬件调⽤⾸地址为0位置的⼦例程(*(void(*)())0)();第⼀步,0为地址,对0地址取内容:(*0)()第⼆步,*必须要⼀个指针来做操作数,这个指针还应该是函数指针,因此对0作类型转换,转换后类型为:“指向返回值为void类型的函数的指针:void (*)()0从⽽得到:(*(void(*)())0)();若⽤typedef解决typedef void (*funcptr)();(*(funcptr)0)();运算符的优先级运算符结合性() [] -> .⾃左向右! ~ ++ -- - (type) * & sizeof⾃右向左* / %⾃左向右+ -⾃左向右<< >>⾃左向右< <= > >=⾃左向右== !=⾃左向右&⾃左向右^⾃左向右&&⾃左向右| ?: | ⾃右向左 || assignnments | ⾃右向左 |任何⼀个逻辑运算符的优先级低于任何⼀个关系运算符移位运算符的优先级⽐算术运算符要低,但是⽐关系运算符要⾼注意作为语句结束标志的分号if(...);xx = xx;switch语句break别遗漏函数调⽤“悬挂”else引发的问题else始终与同⼀对括号内最近的未匹配的if结合第三章语义“陷阱”指针和数组C语⾔只有⼀维数组,⽽且数组的⼤⼩必须在编译期就作为⼀个常数确定下来。

C语言避免常见程序错误与陷阱

C语言避免常见程序错误与陷阱

C语言避免常见程序错误与陷阱C语言是一种广泛使用的程序设计语言,许多程序员在开发过程中经常会遇到各种错误和陷阱。

本文旨在帮助读者避免常见的C语言程序错误,提供一些有效的解决方案,以确保程序的正常运行。

1. 内存管理错误内存管理是C语言中容易出错的一个方面。

常见的内存错误包括内存泄漏和悬空指针。

内存泄漏指的是程序在分配内存后没有释放掉它,导致内存越来越少,最终使程序崩溃。

为避免内存泄漏,应当始终在分配内存后及时释放,使用free()函数来释放动态分配的内存。

悬空指针是指指向已释放或无效的内存地址的指针,访问悬空指针将导致程序崩溃。

为避免悬空指针,可以在释放内存后将指针设置为空指针,即将其指向NULL。

2. 数组越界错误在C语言中,数组的索引从0开始,若访问超出数组范围的元素,会导致程序崩溃或产生不可预测的结果。

为避免数组越界错误,应始终确保访问数组元素时不超出数组的有效下标范围。

可以使用条件语句或循环控制数组索引的范围,以确保程序的安全性。

3. 逻辑错误逻辑错误是程序中最常见的错误类型之一。

逻辑错误是指程序的执行结果与预期不符,但不会导致程序崩溃。

要避免逻辑错误,应仔细检查程序中的条件语句、循环以及各种逻辑表达式的正确性。

使用断言(assert)可以帮助在程序执行过程中验证逻辑条件是否满足,及时发现错误。

4. 输入输出错误输入输出错误可能导致程序无法从用户或外部文件读取正确的数据,或无法将数据正确地写入文件或显示给用户。

为避免输入输出错误,应使用合适的检查函数来验证用户输入的有效性。

在文件操作中,应检查文件是否打开成功,以及是否成功读写数据。

5. 空指针错误使用空指针可能导致程序崩溃。

当试图在空指针上执行操作时,会产生未定义行为。

为避免空指针错误,应始终在使用指针之前进行有效性检查。

比如,通过判断指针是否为空指针,可以避免在空指针上执行操作。

6. 类型转换错误C语言中存在不同类型之间的隐式类型转换,但使用不当可能导致错误的结果。

《C陷阱与缺陷》学习笔记(一):词法陷阱、语法陷阱 (待续)

《C陷阱与缺陷》学习笔记(一):词法陷阱、语法陷阱 (待续)

《C陷阱与缺陷》学习笔记(一):词法陷阱、语法陷阱(待续)自从上高校起,接触C也很久了,但是向来不怎么深化,也疏于练习。

课程学习之余,特地的C只看过《C程序设计语言》、《C primer plus》,现在最终有了点时光看看更多的书了。

本文主要记录阅读和学习《C陷阱与缺陷》的一些心得体味。

11月15日前言和导读得心应手的工具在初学时的困难程度往往超过那些简单上手的工具。

比较认同这句话。

我至今觉得自己其实还是个刚入了门的初学者。

第一章词法陷阱因为之前学过编译原理,对编译器词法分析(主要是符号识别过程)比较了解,理解起来不困难。

在讲到"="和"=="、"|"和"||"、" "和" "时,联想起以前见过一些程序中浮现了类似于"define || OR"这样的语句。

当初以为可能是为了照看习惯其他语言的用法者的阅读偏好,现在看来这样做的确可以避开一些错误。

固然用法不用法这种编程风格就是另外一回事了。

对于词法分析的运用到的贪心法,之前虽然知道原理和规章,但的确没故意识到这是种贪心法。

这样一来,对于简单引起编译器错误的格式,写成另一种格式更好一些。

y = x/*p // 本需要举行指针取值、除法和赋值,这个色彩已表示编译器并不这么认为,因此只能写成下一种形式 y = x/(*p) 至于为整型常量用0补首位以便对齐,反而使得编译器将其误认为八进制数的状况倒是从来碰到也没考虑到过。

单引号' '中的字符上代表一个整数,双引号" " 引起的字符串代表的是一个指向无名数组起始字符的指针。

前半句以前就知道,后半句有点意思。

后半句说明了为什么char *slash = '/' 这个语句有错误。

同时,书中指出整型普通为16位或32位,可以容纳多个字符(普通为8位),所以用单引号的'yes'或许能够被一些编译器正确识别,只是巧合而已。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C语言陷阱和缺陷[1]原著:Andrew Koenig - AT&T Bell Laboratories Murray Hill, New Jersey 07094原文:收藏翻译:lover_P[译序]那些自认为已经“学完”C语言的人,请你们仔细读阅读这篇文章吧。

路还长,很多东西要学。

我也是……[概述]C语言像一把雕刻刀,锋利,并且在技师手中非常有用。

和任何锋利的工具一样,C会伤到那些不能掌握它的人。

本文介绍C语言伤害粗心的人的方法,以及如何避免伤害。

[内容]·0 简介· 1 词法缺陷o 1.1 =不是==o 1.2 &和|不是&&和||o 1.3 多字符记号o 1.4 例外o 1.5 字符串和字符· 2 句法缺陷o 2.1 理解声明o 2.2 运算符并不总是具有你所想象的优先级o 2.3 看看这些分号!o 2.4 switch语句o 2.5 函数调用o 2.6 悬挂else问题· 3 链接o 3.1 你必须自己检查外部类型· 4 语义缺陷o 4.1 表达式求值顺序o 4.2 &&、||和!运算符o 4.3 下标从零开始o 4.4 C并不总是转换实参o 4.5 指针不是数组o 4.6 避免提喻法o 4.7 空指针不是空字符串o 4.8 整数溢出o 4.9 移位运算符· 5 库函数o 5.1 getc()返回整数o 5.2 缓冲输出和内存分配· 6 预处理器o 6.1 宏不是函数o 6.2 宏不是类型定义·7 可移植性缺陷o7.1 一个名字中都有什么?o7.2 一个整数有多大?o7.3 字符是带符号的还是无符号的?o7.4 右移位是带符号的还是无符号的?o7.5 除法如何舍入?o7.6 一个随机数有多大?o7.7 大小写转换o7.8 先释放,再重新分配o7.9 可移植性问题的一个实例·8 这里是空闲空间·参考·脚注0 简介C语言及其典型实现被设计为能被专家们容易地使用。

这门语言简洁并附有表达力。

但有一些限制可以保护那些浮躁的人。

一个浮躁的人可以从这些条款中获得一些帮助。

在本文中,我们将会看一看这些未可知的益处。

这是由于它的未可知,我们无法为其进行完全的分类。

不过,我们仍然通过研究为了一个C程序的运行所需要做的事来做到这些。

我们假设读者对C语言至少有个粗浅的了解。

第一部分研究了当程序被划分为记号时会发生的问题。

第二部分继续研究了当程序的记号被编译器组合为声明、表达式和语句时会出现的问题。

第三部分研究了由多个部分组成、分别编译并绑定到一起的C 程序。

第四部分处理了概念上的误解:当一个程序具体执行时会发生的事情。

第五部分研究了我们的程序和它们所使用的常用库之间的关系。

在第六部分中,我们注意到了我们所写的程序也不并不是我们所运行的程序;预处理器将首先运行。

最后,第七部分讨论了可移植性问题:一个能在一个实现中运行的程序无法在另一个实现中运行的原因。

1 词法缺陷编译器的第一个部分常被称为词法分析器(lexical analyzer)。

词法分析器检查组成程序的字符序列,并将它们划分为记号(token)一个记号是一个有一个或多个字符的序列,它在语言被编译时具有一个(相关地)统一的意义。

在C中,例如,记号->的意义和组成它的每个独立的字符具有明显的区别,而且其意义独立于->出现的上下文环境。

另外一个例子,考虑下面的语句:if(x > big) big = x;该语句中的每一个分离的字符都被划分为一个记号,除了关键字if和标识符big的两个实例。

事实上,C程序被两次划分为记号。

首先是预处理器读取程序。

它必须对程序进行记号划分以发现标识宏的标识符。

它必须通过对每个宏进行求值来替换宏调用。

最后,经过宏替换的程序又被汇集成字符流送给编译器。

编译器再第二次将这个流划分为记号。

在这一节中,我们将探索对记号的意义的普遍的误解以及记号和组成它们的字符之间的关系。

稍后我们将谈到预处理器。

1.1 = 不是 ==从Algol派生出来的语言,如Pascal和Ada,用:=表示赋值而用=表示比较。

而C语言则是用=表示赋值而用==表示比较。

这是因为赋值的频率要高于比较,因此为其分配更短的符号。

此外,C还将赋值视为一个运算符,因此可以很容易地写出多重赋值(如a = b = c),并且可以将赋值嵌入到一个大的表达式中。

这种便捷导致了一个潜在的问题:可能将需要比较的地方写成赋值。

因此,下面的语句好像看起来是要检查x是否等于y:if(x = y)foo();而实际上是将x设置为y的值并检查结果是否非零。

在考虑下面的一个希望跳过空格、制表符和换行符的循环:while(c == ' ' || c = '\t' || c == '\n')c = getc(f);在与'\t'进行比较的地方程序员错误地使用=代替了==。

这个“比较”实际上是将'\t'赋给c,然后判断c 的(新的)值是否为零。

因为'\t'不为零,这个“比较”将一直为真,因此这个循环会吃尽整个文件。

这之后会发生什么取决于特定的实现是否允许一个程序读取超过文件尾部的部分。

如果允许,这个循环会一直运行。

一些C编译器会对形如e1 = e2的条件给出一个警告以提醒用户。

当你趋势需要先对一个变量进行赋值之后再检查变量是否非零时,为了在这种编译器中避免警告信息,应考虑显式给出比较符。

换句话说,将:if(x = y)foo();改写为:if((x = y) != 0)foo();这样可以清晰地表示你的意图。

1.2 & 和 | 不是 && 和 ||容易将==错写为=是因为很多其他语言使用=表示比较运算。

其他容易写错的运算符还有&和&&,或|和||,这主要是因为C语言中的&和|运算符于其他语言中具有类似功能的运算符大为不同。

我们将在第4节中贴近地观察这些运算符。

1.3 多字符记号一些C记号,如/、*和=只有一个字符。

而其他一些C记号,如/*和==,以及标识符,具有多个字符。

当C编译器遇到紧连在一起的/和*时,它必须能够决定是将这两个字符识别为两个分离的记号还是一个单独的记号。

C语言参考手册说明了如何决定:“如果输入流到一个给定的字符串为止已经被识别为记号,则应该包含下一个字符以组成能够构成记号的最长的字符串”。

因此,如果/是一个记号的第一个字符,并且/后面紧随了一个*,则这两个字符构成了注释的开始,不管其他上下文环境。

下面的语句看起来像是将y的值设置为x的值除以p所指向的值:y = x/*p /* p 指向除数 */;实际上,/*开始了一个注释,因此编译器简单地吞噬程序文本,直到*/的出现。

换句话说,这条语句仅仅把y的值设置为x的值,而根本没有看到p。

将这条语句重写为:y = x / *p /* p 指向除数 */;或者干脆是y = x / (*p) /* p指向除数 */;它就可以做注释所暗示的除法了。

这种模棱两可的写法在其他环境中就会引起麻烦。

例如,老版本的C使用=+表示现在版本中的+=。

这样的编译器会将a=-1;视为a =- 1;或a = a - 1;这会让打算写a = -1;的程序员感到吃惊。

另一方面,这种老版本的C编译器会将a=/*b;断句为a =/ *b;尽管/*看起来像一个注释。

1.4 例外组合赋值运算符如+=实际上是两个记号。

因此,a + /* strange */ = 1和a += 1是一个意思。

看起来像一个单独的记号而实际上是多个记号的只有这一个特例。

特别地,p - > a是不合法的。

它和p -> a不是同义词。

另一方面,有些老式编译器还是将=+视为一个单独的记号并且和+=是同义词。

1.5 字符串和字符单引号和双引号在C中的意义完全不同,在一些混乱的上下文中它们会导致奇怪的结果而不是错误消息。

包围在单引号中的一个字符只是书写整数的另一种方法。

这个整数是给定的字符在实现的对照序列中的一个对应的值。

因此,在一个ASCII实现中,'a'和0141或97表示完全相同的东西。

而一个包围在双引号中的字符串,只是书写一个有双引号之间的字符和一个附加的二进制值为零的字符所初始化的一个无名数组的指针的一种简短方法。

线面的两个程序片断是等价的:printf("Hello world\n");char hello[] = {'H', 'e', 'l', 'l', 'o', ' ','w', 'o', 'r', 'l', 'd', '\n', 0};printf(hello);使用一个指针来代替一个整数通常会得到一个警告消息(反之亦然),使用双引号来代替单引号也会得到一个警告消息(反之亦然)。

但对于不检查参数类型的编译器却除外。

因此,用printf('\n');来代替printf("\n");通常会在运行时得到奇怪的结果。

由于一个整数通常足够大,以至于能够放下多个字符,一些C编译器允许在一个字符常量中存放多个字符。

这意味着用'yes'代替"yes"将不会被发现。

后者意味着“分别包含y、e、s和一个空字符的四个连续存贮器区域中的第一个的地址”,而前者意味着“在一些实现定义的样式中表示由字符y、e、s联合构成的一个整数”。

这两者之间的任何一致性都纯属巧合。

2 句法缺陷要理解C语言程序,仅了解构成它的记号是不够的。

还要理解这些记号是如何构成声明、表达式、语句和程序的。

尽管这些构成通常都是定义良好的,但这些定义有时候是有悖于直觉的或混乱的。

在这一节中,我们将着眼于一些不明显句法构造。

2.1 理解声明我曾经和一些人聊过天,他们那时在书写在一个小型的微处理器上单机运行的C程序。

当这台机器的开关打开的时候,硬件会调用地址为0处的子程序。

为了模仿电源打开的情形,我们要设计一条C语句来显式地调用这个子程序。

经过一些思考,我们写出了下面的语句:(*(void(*)())0)();这样的表达式会令C程序员心惊胆战。

但是,并不需要这样,因为他们可以在一个简单的规则的帮助下很容易地构造它:以你使用的方式声明它。

每个C变量声明都具有两个部分:一个类型和一组具有特定格式的期望用来对该类型求值的表达式。

相关文档
最新文档