气死我了,写了两个多钟头的文章竟然没了,又得重写
晕啊 ~~~~~~~~~~~~~
先简单做个记录吧,以后再写
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函数会造成缓冲区溢出引发一些难以预料的安全问题。
晕啊 ~~~~~~~~~~~~~
先简单做个记录吧,以后再写
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函数会造成缓冲区溢出引发一些难以预料的安全问题。