整数溢出:
C语言存在两类运算,有符号和无符号,在无符号运算中是不存在溢出一说的:所有的无符号运算都是基于2的n次方为模。如果算数运算符中的一个操作数是有符号整数,另一个是无符号整数,那么有符号整数会被转化为无符号整数,则溢出就不会发生。然而当两个操作数都是有符号整数的时候,溢出就是可能的。那么我们怎么判断不越界呢?
//wrong:
if(a+b<0)
complain();
//right
if((unsigned)a+(unsigned)b>INT_MAX)
complain();
//Ps:limits.h内定义了INT_MAX的概念
为main函数提供返回值:
一般来说的话,main函数如果没显式声明返回类型就会默认为整形,但是main(){}这一段并没有给出什么返回值,通常来说是不会有什么问题的,一个返回值为整形的的函数如果返回失败实际上是返回了某个“垃圾”的整数,只要这个值不被用到那就无关紧要。
然而有的时候main函数的返回值却并非无关紧要。大部分C语言的实现都是根据main函数的返回值来告知OS该函数是否执行成功。返回值为0说明succeeded,否则表示执行失败。比如你还写了一个软件管理系统,来关注程序调用后是成功还是失败,那么可能会得到惊讶的结果。
所以严格来说的话,还是应该用return 0;或者 exit(0).
什么是连接器:
C的一个重要思想就是分别编译,就是若干不同的源程序可以在不同的时候分别编译然后再恰当的时候整合到一起。但是一般连接器和C编译器是分离的,不可能了解C的诸多细节。那么连接器的工作机理是什么呢?
答案是它理解机器语言和内存布局。编译器的责任是把C的源程序翻译成对连接器有意义的形式这样连接器就能读懂c的源程序了。典型的连接器会将编译器生成的若干目标模块整合成一个称为可执行文件的实体,该实体能被OS直接执行,有一些是直接作为输入提供给连接器的,有一些是需要根据连接过程的需要,从包括类似printf函数的库文件中取得。连接器一般把目标模块看成一组外部对象,每一个外部对象代表着内存的某一个部分。因此程序中每一个函数和外部变量如果没有被声明为static就都是一个外部对象。某些C compiler也会对静态函数和变量进行名称处理将他们也作为外部变量。
声明与定义:
extern int a;这不是对a地定义,只是说明了a是一个外部的整型变量,显式地说明了a地存储空间是程序地其他部分分配的。上述声明在连接器的角度来看只是对外部变量a的引用而不是定义,所以一旦出现了extern int a这句话,则必然在程序的其他部分出现int a。外部变量最好最好只定义一次。
命名冲突和static修饰符:
两个同名的外部对象实际上代表同一个对象,而static能够减少此类命名冲突的问题,在一个这个static int a所在的文件中,a是可见的对于其他源文件来说a是不可见的。因此若干函数需要共享一组外部对象,可以把他们放在一个源文件中,把他们需要用到的对象也都在同一个源文件中用static来修饰。static还可以用在函数中:假设函数f需要调用另一个函数g,而且只有函数f需要调用函数g,我们可以把f和g都放在同一个源文件中,并且把g声明为static,比如:
static int g(int x)
{
/*函数体*/
}
void f()
{
/*其余函数体*/
b=g(a);
}
检查外部类型:
supposed that we have a .c program which is constituted of two source file.One of them includes :extern int n;
and another includes :long n
这里假定两个语句都不在任何一个函数体内,因此n是外部变量。
这是一个无效的C程序,因为同名的变量在不同的文件中被声明为了相同的类型,但是很多编译器检测不到,当这个程序运行的时候可能会有很多的情况。这个我们不深究。
再看:
char filename[]="fuck you man"; //in A source file
extern char *filename; /in B source file
//which is wrong because filename in A is a statement of array name,while
//in B is a statement of a pointer type
//so we need to come to a unification(or unity,anyway)
char filename[]="fuck you man"; //in A
extern char filename[];
//or in this way
char *filename="fuck you man"
extern char *filename';
设置头文件.h:
其实以上的一些问题都可以用头文件的方式来解决,在头文件中声明,在哪里用到就在哪里包含这个自己设置的头文件即可。
形如#include ”fuck.h“
更新顺序文件:
许多操作系统中的标准I/O库都允许程序打开一个文件,同时进行写入和读出的操作:
FILE *fp;
fp=fopen(file,"r+");
上述代码打开了文件名由file指定的文件,对于存取权限的设定表明程序希望对这个文件进行输入和输出操作。
编程者或许认为程序一旦执行上述操作完毕就可以自由的交错进行读出和写入的操作,遗憾的是,一个输入操作不能随后直接紧跟一个输出操作,反之亦然,如果要同步进行输入和输出的话,必须在其中插入fseek函数的调用。
//下面的代码段似乎更新了一个顺序文件中选定的记录
FILE *fp;
struct record rec;
...
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);
}
}
这一段代码看似没有问题:&rec在传入fread和fwrite函数时被小心翼翼的转化成了字符指针类型,sizeof(rec)被转化成了long型(fseek要求第二个参数必须是long型,因为int型可能无法包含一个文件的大小;sizeof返回一个unsigned值,所以必须先转化为有符号类型才可能将其反号)。但是这段代码仍然可能失败,并且错误方式很隐晦。
问题出在:如果一个记录需要被重新写入文件,fwrite得到执行,对这个文件执行的下一个操作应该是fread,因为在fwrite函数调用与fread调用之间缺少一个fseek函数调用,所以无法执行上述操作,解决的方法是改写如下(虽然看起来fseek什么也没做,但是改变了文件的状态)
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);
}
}
由于本blogger还不是很熟练文件的操作,近期将会学习C higher file operation= =,并且总结完了写篇水文,我太菜了!!!