现在的位置: 首页 > 综合 > 正文

C语言的产生和我理解的C语言“缺陷”

2013年03月06日 ⁄ 综合 ⁄ 共 2778字 ⁄ 字号 评论关闭
 气死我了,写了两个多钟头的文章竟然没了,又得重写
晕啊 ~~~~~~~~~~~~~

先简单做个记录吧,以后再写
C语言产生的背景:
          C语言产生于一次失败的项目……

一、 C标准中的未定义和类型扩展
int类型的大小依赖于系统,并且其符号也依赖于系统。ANSI标准没有定义char、int、long这样的内部类型,这取决于编译器,为使编写的代码具有可移植性,建议明确定义类型的符号,如 signed int 和 unsigned int

这段代码的输出结果是什么?

#include <stdio.h>
int array[]={12,32,36,54,7,5};

#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0]))

int main()
{
  int d=-1;
  if(d<TOTAL_ELEMENTS - 2)
  printf("hello world/n");
  else
  printf("%d/n",d);

  return 0;
}

         答案是-1,很奇怪吗?-1怎么会比6-2还大呢?这里涉及到类型转化的问题。在C语言标准里有这样一句话:当进行算术运算时,如果类型不一样将进行类型提升,一般是向着精度更高,长度更长的方向转化。
        定义宏TOTAL_ELEMENTS 用来计算数组的长度,注意到sizeof的返回类型是unsigned  int  ,-1与TOTAL_ELEMENTS-2比较时将被转化成 unsigned  int ,在32位的系统里,int表示4个字节,-1unsigned  int 值为0XFFFFFFFF,显然比4大多了。

二、数据溢出
#include <stdio.h>
#define UCHAR_MAX 256

int main()
{
   unsigned char c;
   char Alpha[UCHAR_MAX];
   for(c=0;c<UCHAR_MAX;c++)
       Alpha[c] = c;

   return 0
}

         这段代码将产生死循环,unsigned char的范围是0-255当循环至c等于255时,c+1等于256?不是,c上溢为0循环不会停止。

#include <stdio.h>

int main()
{
   int a[5]={1,2,3,4,5};
   a[5] = 6;
   printf("%d/n",a[5]);
   return 0;
}

         这段代码的结果可想而知,但我们奇怪的是C语言竟然没有规定编译器对数组的边界进行检查。
也许你会认为这段代码没有造成什么危害,那下面的呢?

#include <stdio.h>
#include <string.h>

char a[6] = "12345";
char b[10] = "abcdefghi";
char c[5] = "6789";

int main()
{
   strcat(a,c);
   puts(a);
   puts(b);
   puts(c);

   return 0;
}

         如果你熟悉C语言中的内存分配也熟悉库函数strcat和puts的用法,你可能猜出答案,但我想大多数初学C语言的人并不理解过程。
该程式的输出结果如下:
123456789
789
6789

        我们来分析一下,首先编译器在静态存储区上创建了三个全局变量,并初始化,注意到a b c分配的空间是连续的(在C语言中除了堆上分配的空间不连续以外,栈和静态存储区都是连续的)
         执行strcat(a,c)由于a的空间不够,strcat不管三七二十一地把剩下的字符拷贝到a后面的空间里,也就是b中并破坏了原来b里面的内容。此时b变成789/0efghi/0,而puts的也相当野蛮你只要给它一个字符串的指针(函数在传递数组时以指针的形式)的地址,它就输出所有的字符直到遇到结束符标志ASCII 0 ,而不理会字符数组的真实长度,实际上puts也不可能知道。

三、C语言中有缺陷的函数malloc、free、realloc、puts、gets、strcat、getchar等
       在使用这些函数时,如果用法不当的话,很容易造成内存泄露或者内存溢出。
       1、malloc函数存在两个无定义的行为。第一当请求分配长度为0的内存块时结果无定义,第二当malloc分配成功时,它返回的内存块无定义,该内存块的内容可以是0或者其他无用的信息。
        无定义的行为所引发的错误也是不得而知的,malloc没有对请求分配的长度进行检验,所以我们在使用时要先进行0值的检查。对于malloc返回内存块的无定义似乎没什么危害,关键的问题是,我们经常会无意地使用未初始化的内存,因为内存块的内容是随机的,一般运行时不会出错,导致无法找到出错的地方,所以最好对内存块进行特殊值的填充,当使用这块未初始化的内存时会出现编译错误,从而帮我们找到问题的所在。
       下面是一个《编程精粹》里的一个例子:
#define bGarbage 0XA3
flag fNewMemory(void **pv, size_t size)
{
      byte **ppb = (byte**)pv;
      ASSERT(pv != NULL  && size!=0);
      *ppb = (byte *)malloc(size);
      #ifdef DEBUG
     {
           if(*ppb != NULL)
           memset(*ppb, bGarbage, size);
    }
    #endif
   return (*ppb != NULL)
}

函数中,0xA3是一条非法的机器语言指令,当引用使用该值填充的未初始化的内存块时将产生错误。
        2、free函数也存在一个未定义的行为:当传递给free无效的指针时。
下面的例子说明了free函数另外一个设计不好的地方。
#include <stdio.h>
#include <stdlib.h>

int main()
{
   char *p;
   p = (char *)malloc(20);
   strcpy(p,"hello world/n");
   printf(p);
   free(p);
   printf(p);
  
   return 0;
}

当使用free释放掉内存以后,指针p仍然指向原来的地址,而且该地址里面的内容仍然保留着原来的数据(在大多数系统里是这样的,但我用的c-free编译器会把内容清掉)如果我们继续使用该内存的话一时间发现不了错误(内存管理程式回收空间需要一定的时间)但以后就可能出错。好的习惯是free完后,把指针置空p=NULL。
        3、相比之下realloc的不足更是一大堆
这个整理以后再写

       4、gets函数会造成缓冲区溢出引发一些难以预料的安全问题。

抱歉!评论已关闭.