1.main函数返回值
大多数C语言的实现都通过函数main的返回值来告诉操作系统该函数的执行是成功还是失败。典型的处理方案是,返回值为0代表程序执行成功,返回值非0则表示程序执行失败。如果一个程序的main函数并不返回任何值,那么有可能看上去执行失败。所以建议我们的C程序的main函数应该如下编写:
int main()
{
return 0;
}
当然如果main函数需要接受参数的话将参数声明加上更加完美。
2.连接器
一个C程序可能是由多个分别编译的部分组成,这些不同的部分通过一个通常叫做连接器的程序合并为一个整体。连接器的输入是一组目标模块和库文件,连接器的输出是一个载入模块。
连接器通常把目标模块看成是由一组外部对象组成的,每个外部对象代表着机器内存中的某个部分,并通过一个外部名称来识别。因此,程序中的每个函数和每个外部变量,如果没有被声明为static,就是一个外部对象。某些C编译器会对静态函数和静态变量的名称做一定改变,将它们也作为外部对象。
大多数连接器都禁止同一个载入模块中的两个不同外部对象拥有相同的名称。然而在多个目标模块整合成一个载入模块时,这些目标模块可能就包含了同名的外部对象,连接器的一个重要工作就是处理这类命名冲突。
除了外部对象之外,目标模块中还可能包含了对其他模块中的外部对象的引用。例如,一个调用了函数printf的C程序所生成的目标模块,就包括了一个对函数printf的引用。所以在连接器生成载入模块的过程中,它必须同时记录这些外部对象的引用。
3.外部对象的声明
extern int a;这条语句是对外部变量a的声明,即使其出现在一个函数的内部,也仍然具有相同的含义,因为这种形式的声明是对一个外部对象的显式引用。
如果语句int a = 7和语句int a = 9出现在同一个源文件中,将会出现什么样的情形呢?这个问题的答案与系统有关,不同的系统可能有不同的处理方式。严格的规则是,每个外部变量只能够定义一次。如果上述这种情况出现的话,大多数系统都会拒绝接受该程序。但是如果一个外部变量在多个源文件中定义却并没有指定初始值,那么某些系统会接受这个程序,而另外一些系统则不会接受。
4.外部对象使用注意
两个具有相同名称的外部对象实际上代表的是同一个对象,即使编程者的本意并非如此,但系统却会如此处理。因此,如果在两个不同的源文件中都包含了定义:
int a;
那么,它或者表示程序错误,或者在两个源文件中共享a的同一个实例。static可以很好的解决这个问题。例如,如下声明语句:
static int a;
a的作用域将被限制在一个源文件内,对于其他源文件,a是不可见的。因此,如果若干个函数想要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要用到的对象也都在同一个源文件中以static修饰符声明。需要注意的是static同样可以适用于函数。
5.系统默认函数声明
如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整型。C语言中的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。当然这在很多时候会造成错误,所以在使用函数的时候一定要保证在函数调用之前已经有过函数的声明。
6.如下的这段代码有什么问题?
int main()
{
char c;
while((c = getchar()) != EOF)
putchar(c);
return 0;
}
分析:这段代码的问题在于隐性的假设了EOF的值是在char的表示范围内。然而很多实际情况下,EOF的值char类型是无法容纳的,所以如果想改正这个程序,需要将c定义为int类型。
7.文件读写注意
许多操作系统的标准输入/输出库都允许程序打开一个文件,同时进行写入和读出的操作:
FILE *fp;
fp = fopen(file, "r+");
编程者也许认为,程序一旦执行上述操作完毕,就可以自由地交错进行读出和写入的操作。遗憾的是,为了保持与过去不能同时进行读写操作的程序向下兼容性,一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用。推荐如下的写法:
while(...)
{
fread(...);
fseek(...);
fwrite(...);
fseek(...);
}
8.有缓冲与无缓冲
程序输出有两种方式:一种即时处理方式,另一种是先暂存起来,然后再大块写入的方式,前者往往造成系统较高的负担。因此,C语言实现通常都允许程序员进行实际的写操作之前控制产生的输出数据量。这种控制能力一般都是通过库函数setbuf实现的,如果buf是一个大小适当的字符数组,那么
setbuf(stdout, buf);
语句将通知输入/输出库,所有写入到stdout的输出都应该使用buf作为输出缓冲区。
注意:这里的buf如果被定义为局部变量将可能导致错误,因为系统最后一次清理buf将会是在main函数执行完成后,这个时候如果buf都已经被系统释放,就有可能出现错误,所以一般将buf设置为static或者设置为全局变量再或者使用malloc分配,这三种方法都是延长buf的生存期。
9.程序错误代码
在调用库函数时,我们应该首先检测作为错误指示的返回值,确定程序执行已经失败,然后在检查errno(当很多库函数程序执行失败的时候,往往会通过这个外部变量通知程序该函数调用失败),来具体查清楚出错的原因,而不是直接检测errno的值来判断是否出现某种错误。
10.禁止缓冲
当一个程序异常终止时,程序输出的最后几行常常会丢失,原因是什么?我们能够采取怎么样的措施来解决这个问题?
分析:一个异常终止的程序可能没有机会来清空其输出缓冲区,因此,该程序生成的输出可能位于内存的某个位置,但却永远不会被写出了。在某些系统上,这些无法写出的数据可能长达好几页。
对于试图调试这类程序的编程者来说,这种丢失的情况经常会误导他们,因为它会造成这样一种假象,程序发生失败的时刻比实际上运行失败的真正时刻要早得多。解决方案就是在调试时强制不允许对输出进行缓冲。要做到这一点,不同的系统有不同的作法,这些作法虽然存在细微差别,但大致如下:
setbuf(sedout, (char *)0);
这个语句必须在任何输出被写入到stdout(包括任何对printf函数的调用)之前执行。该语句的最恰当的位置就是作为main函数的第一个语句。
大多数C语言的实现都通过函数main的返回值来告诉操作系统该函数的执行是成功还是失败。典型的处理方案是,返回值为0代表程序执行成功,返回值非0则表示程序执行失败。如果一个程序的main函数并不返回任何值,那么有可能看上去执行失败。所以建议我们的C程序的main函数应该如下编写:
int main()
{
return 0;
}
当然如果main函数需要接受参数的话将参数声明加上更加完美。
2.连接器
一个C程序可能是由多个分别编译的部分组成,这些不同的部分通过一个通常叫做连接器的程序合并为一个整体。连接器的输入是一组目标模块和库文件,连接器的输出是一个载入模块。
连接器通常把目标模块看成是由一组外部对象组成的,每个外部对象代表着机器内存中的某个部分,并通过一个外部名称来识别。因此,程序中的每个函数和每个外部变量,如果没有被声明为static,就是一个外部对象。某些C编译器会对静态函数和静态变量的名称做一定改变,将它们也作为外部对象。
大多数连接器都禁止同一个载入模块中的两个不同外部对象拥有相同的名称。然而在多个目标模块整合成一个载入模块时,这些目标模块可能就包含了同名的外部对象,连接器的一个重要工作就是处理这类命名冲突。
除了外部对象之外,目标模块中还可能包含了对其他模块中的外部对象的引用。例如,一个调用了函数printf的C程序所生成的目标模块,就包括了一个对函数printf的引用。所以在连接器生成载入模块的过程中,它必须同时记录这些外部对象的引用。
3.外部对象的声明
extern int a;这条语句是对外部变量a的声明,即使其出现在一个函数的内部,也仍然具有相同的含义,因为这种形式的声明是对一个外部对象的显式引用。
如果语句int a = 7和语句int a = 9出现在同一个源文件中,将会出现什么样的情形呢?这个问题的答案与系统有关,不同的系统可能有不同的处理方式。严格的规则是,每个外部变量只能够定义一次。如果上述这种情况出现的话,大多数系统都会拒绝接受该程序。但是如果一个外部变量在多个源文件中定义却并没有指定初始值,那么某些系统会接受这个程序,而另外一些系统则不会接受。
4.外部对象使用注意
两个具有相同名称的外部对象实际上代表的是同一个对象,即使编程者的本意并非如此,但系统却会如此处理。因此,如果在两个不同的源文件中都包含了定义:
int a;
那么,它或者表示程序错误,或者在两个源文件中共享a的同一个实例。static可以很好的解决这个问题。例如,如下声明语句:
static int a;
a的作用域将被限制在一个源文件内,对于其他源文件,a是不可见的。因此,如果若干个函数想要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要用到的对象也都在同一个源文件中以static修饰符声明。需要注意的是static同样可以适用于函数。
5.系统默认函数声明
如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整型。C语言中的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。当然这在很多时候会造成错误,所以在使用函数的时候一定要保证在函数调用之前已经有过函数的声明。
6.如下的这段代码有什么问题?
int main()
{
char c;
while((c = getchar()) != EOF)
putchar(c);
return 0;
}
分析:这段代码的问题在于隐性的假设了EOF的值是在char的表示范围内。然而很多实际情况下,EOF的值char类型是无法容纳的,所以如果想改正这个程序,需要将c定义为int类型。
7.文件读写注意
许多操作系统的标准输入/输出库都允许程序打开一个文件,同时进行写入和读出的操作:
FILE *fp;
fp = fopen(file, "r+");
编程者也许认为,程序一旦执行上述操作完毕,就可以自由地交错进行读出和写入的操作。遗憾的是,为了保持与过去不能同时进行读写操作的程序向下兼容性,一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用。推荐如下的写法:
while(...)
{
fread(...);
fseek(...);
fwrite(...);
fseek(...);
}
8.有缓冲与无缓冲
程序输出有两种方式:一种即时处理方式,另一种是先暂存起来,然后再大块写入的方式,前者往往造成系统较高的负担。因此,C语言实现通常都允许程序员进行实际的写操作之前控制产生的输出数据量。这种控制能力一般都是通过库函数setbuf实现的,如果buf是一个大小适当的字符数组,那么
setbuf(stdout, buf);
语句将通知输入/输出库,所有写入到stdout的输出都应该使用buf作为输出缓冲区。
注意:这里的buf如果被定义为局部变量将可能导致错误,因为系统最后一次清理buf将会是在main函数执行完成后,这个时候如果buf都已经被系统释放,就有可能出现错误,所以一般将buf设置为static或者设置为全局变量再或者使用malloc分配,这三种方法都是延长buf的生存期。
9.程序错误代码
在调用库函数时,我们应该首先检测作为错误指示的返回值,确定程序执行已经失败,然后在检查errno(当很多库函数程序执行失败的时候,往往会通过这个外部变量通知程序该函数调用失败),来具体查清楚出错的原因,而不是直接检测errno的值来判断是否出现某种错误。
10.禁止缓冲
当一个程序异常终止时,程序输出的最后几行常常会丢失,原因是什么?我们能够采取怎么样的措施来解决这个问题?
分析:一个异常终止的程序可能没有机会来清空其输出缓冲区,因此,该程序生成的输出可能位于内存的某个位置,但却永远不会被写出了。在某些系统上,这些无法写出的数据可能长达好几页。
对于试图调试这类程序的编程者来说,这种丢失的情况经常会误导他们,因为它会造成这样一种假象,程序发生失败的时刻比实际上运行失败的真正时刻要早得多。解决方案就是在调试时强制不允许对输出进行缓冲。要做到这一点,不同的系统有不同的作法,这些作法虽然存在细微差别,但大致如下:
setbuf(sedout, (char *)0);
这个语句必须在任何输出被写入到stdout(包括任何对printf函数的调用)之前执行。该语句的最恰当的位置就是作为main函数的第一个语句。