C语言的特点及程序小测验

1.语言简洁紧凑,只有32个关键字,9中控制语句,与外围设备相关的输入输出操作都放在C库中,这样可以使得C语言尽可能的和硬件平台无关。

2.运算符很丰富,34种,如++、--、取内容运算符*、和取地址符&、针对位运算的<<,>>,&,等,方便进行底层代码编写。

3.数据结构丰富,唯一缺憾是没有处理字符串的类型。

4.具有结构化的控制语句

5.C语言编译时不检查数组越界。既有优点:程序运行速度快;给程序员留更大空间,方便指针使用;缺点:可能会造成程序崩溃。

6.允许直接访问物理地址,能进行位操作,C对物理地址的访问主要通过指针。允许对底层存储器操作。

7.可移植性好。

//

void关键字:

1.用来修饰函数的返回值,若不写返回值时系统默认是 int型,为避免程序员错误引用这个返回值所以引入void

2.用来声明函数的入口函数,表示这个函数没有入口参数

3.声明空类型指针void*,常用于动态内存分配中(void*)malloc(100);

 

程序小测验:

C语言的复杂和灵活主要体现在C语言语法的灵活以及允许程序员对底层存储器的直接操作上(这两点又恰恰是c语言最优美、最强大的地方)。下面我们来做一个小小的测验,请阅读以下的代码并找出其中的错误和潜在的危险因素。注意:这段代码本身并没有什么实际意义,只是将入口参数ptr所指向的内容通过两个内部缓冲区P和q复制到局部数组buf[]中,并将该数组的首地址作为返回值传递到函数外。我在这里只是希望通过这个例子说明c
程序语法的正确性与功能或者逻辑的正确性之间有着本质的区别。   
#include”stdlib.h”
2
char*test(char*ptr)
{
     unsigned char i;
     char buf[8*1024];
     char*P,*q;

/*将数组初始化为0*/
     for(i=0;i<=8*1024:i++)

         buf[i]=OxO;
     P=malloc(1024);
     if(p==NULL)return NULL;/*P申请失败,返回空指针*/
     q=malloc(2048);
     if(q=NULL)returnNULL;/*q申请失败,返回空指针*/

     memcpy(p,ptr,1024);

     memcpy(q,ptr,2048);/*将ptr所指向的内容复制到q*/
     memcpy(buf,p,1024);/*现在我们将p和q中的内容合并到buf数组中*/
     bur=bur+1024:
     memcpy(bur,q,2048);
     free(p);
     free(q);

     return buf;/*将数组bur的首地址返回出去*/
你能在上面的代码中发现几处错误或者隐患呢?还是让我们~起来分析一

    ①代码的第1行即有问题。在C语言中包含文件的有两个符号“”或者<>。双引号“”的意思是告诉编译器首先在当前目录下搜索需要包含的文件,如果当前目录下没有该文件,则在编译选项指定的系统头文件目录中搜索该文件;尖括号<>的意思则是通知编译器首先在系统头文件目录中搜索需要包含的文件。在这个例子中stdlib.h是ANSI C标准库函数的头文件,一般而言这个文件是存放在系统头文件目录中的,因此准确的用法应该是采用尖括号,即#include。虽然在大多数情况下采用双引号的包含方式不会产生错误,但如果系统里(比如Linux下的/usr/include/)有一个叫作math.h的头文件,而你的源代码目录里也有一个自己写的math.h头文件,那么此时系统就会默认使用你自己定义的头文件,而这可能并非你的本意。

    ②第5行的定义unsigned char i是定义一个无符号的8位数,但是请注意一个无符号8位数的范围是0~255,而第9行的for循环中却将i与8×l 024进行比较,如果i是一个无符号8位数的话,那么这个数将永远小于8×1 024,因为当i的值增长到255时再加1后i将重新变为0,这将使得这个for循环成为一个死循环而永远不会结束。

    ③第6行定义了一个8K字节的数组buf[],从语法上来说这个定义没有任何问题,但是如果我们知道一个局部变量是如何在内存中表示的就会对这样的定义倒吸一口凉气了。编译器对局部变量有两种存储方法,对于简单数据类型的变量(比如int、char、short或者指针变量等)编译器会首先尽可能地采用CPU内部的通用寄存器来表示,因为寄存器的访问速度远远高于外部存储器的访问速度;第二种方式是对于那些没有办法用寄存器表示的变量或者数组、结构体等变量采用当前的堆栈空间来存储。对于这段代码数组bus[]显然是需要存放在堆栈中的,然而8K字节的空间对于大多数系统而言是很容易将堆栈空间耗尽的,因此在局部数组中开设大数组是需要仔细评估的,程序员必须非常清楚自己的堆栈空间是否够用。如果算法必须采用大数组,可以采用“static char bu{[8*1024]”的方法来定义,虽然这同时会带来程序不可重入的问题。关于static关键字我们会在第2章中仔细讲解的。

 ④第9行的for循环中,“i<=8*1024”的表达式是错误的,因为在C语言中数组的下标是从0开始的,因此对于bufE]数组,合法的下标取值是从0到(8×1 024—1),所以上述的表达式的正确写法应该是“i<8*1024”。令人遗憾的是C语言对于数组越界是不作任何检查的。如果按照原来错误的写法在最后一次循环中程序执行了“buf[8*1024-]一0x0”的操作,通常情况下程序在当时不会有任何异常,但是其实紧邻最后一个合法元素“bufE8*1024—1]”存放的另外一个变量已经被错误地修改了,程序只有在访问了该变量时才可能出现不正常,而这时可能已经离你修改它的第9行很远了。

    ⑤第12行中的问题虽然不是错误,但却是一个不好的编程风格,malloc()库函数的返回值是一个指向void类型的指针,因此好的编程风格应该是在将这个返回值赋给其他类型的指针变量前进行显式的强制类型转换。所比较合适的写法应该是“P=(char*)malloc(1024)”。第14行的malloc()函数调用也存在同样的问题。

    ⑥第15行代码中有两个非常隐蔽的错误。我们先来分析第一个:“if(q=NULI)”这个判断式的逻辑是错的,正确的写法应该是“if(q==NULL)”。这是几乎所有C语言使用者都犯的错误。令人真正害怕的是前面的表达式在语法上是完全正确的,意思是将NULL赋值给变量q,然后判断q的取值是否不为0,这个判断的取值永远是“否”,也就是说不管原来的q是否真的为空,后面的“return NULL;”语句永远不会执行。更讨厌的是,如果q原来为非空指针,则在经过第15行代码后也会将值改为空。指针,则在经过第15行代码后也会将值改为空。

    ⑦第15行的第二个错误在这个语句的后半部分。如果q指针为空,说明第14行的malloc()函数申请动态内存失败,调用“return NULI。;”语句似乎没有任何问题。但是当程序运行到第15行时实际上有一个潜在的条件,那就是P指针的动态内存申请一定是成功的(否则早在第13行程序就会“return NULl."),因此如果我们在q申请不到时直接返回就会将P指针申请的动态内存永远地丢失,这块内存空间永远也不会被释放回系统(Heap),这就是所谓的“内存泄漏”(Memory Leak)。内存泄漏是一个慢性错误,由于并不影响正常的程序运行,所以通常情况下在内存泄漏的早期,程序的运行没有任何异常,直到系统堆中的内存空间已经“漏”光了,其他程序调用malloc()申请内存时总是失败,这时解决问题的唯一办法就是重新复位系统。这一行的正确写法应该是:
if(q==NULL)
{

    free(P);

    return NULL;
}
⑧第17行中包含了一个隐蔽的错误。调用memcpy()函数将人口参数ptr所指向的内存复制l 024个字节到P指针所指向的内存空问。但程序在将ptr人口参数作为memcpy()函数的参数时没有对ptr是否为空进行检查,这是因为在通常情况下标准C库函数为了效率往往不对人口参数进行合法性检查,如果ptr为空,那么“memcpy(P,ptr,1024);”这个语句一运行系统就崩溃了。

    ⑨第18行应该是“memepy(q,ptr+1024,2048);”。因为前1 024个字节已经被第17行执行完毕,后面的数据应该从ptr指针向后偏移l 024个字节开始。

    ⑩第20行是一个语法错误,但是在写代码时往往被程序员忽略。理解这个语法错误首先要理解C编译器是如何处理数组的。在c语言程序编译的过程中,编译器要为数组所占用的内存分配空间,因此在C中没有动态数组的概念,数组在存储器中的位置(也就是地址)和容量在编译时就已经确定了,并且在程序运行过程中不再发生改变。编译器将数组的名字作为一个符号并将该符号与数组实际存放在内存中的地址对应起来。因此在C语言中数组名就是数组的首地址,这个首地址已经在编译的时候确定,不能再改变了。所以“buf=bur+1024;”这个语句是有语法错误的,编译器会报错。

    ⑩最后一个错是第26行的返回语句“return buf;”。正如前面所述,buf[]数组是通过堆栈存放的,因此将堆栈中的地址作为指针传递到函数外部是非常危险的。因为大家都知道在我们出函数后,该函数的栈帧就已经无效了,原来的这个堆栈空间随时都可能被用作其他用途,返回这段内存的地址毫无意义。如果通过这个指针去修改其所指向的内容,将很容易地将系统堆栈写“脏”,将保留在新的栈帧中的数据覆盖。



 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值