55、读C陷阱和缺陷(C Traps and Pitfalls)(二)

4、连接和库函数

1)C语言的一个重要思想是分别编译,即将若干个源程序可以在不同的时间单独进行编译,然后在恰当的时候整合到一起。连接器的工作过程大致如下:

连接器的输入是一组目标模块和库文件,输出是一个载入模块。连接器读入目标模块和库文件,现时生成载入模块。对每个目标模块中的每个外部对象,连接器都要检查载入模块,看是否已有同名的外部对象。如果没有,连接器就将该外部对象添加到载入模块中,如果有,连接器就要开始处理命名冲突。

默认情况下,变量定义是外部的,即:

int i 别的文件中也可以看到。

但是如果声明为:

static int a;

则其作用域只局限在一个源文件中,对于其它源文件,a是不可见的。Static可以对函数,变量进行声明。为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,就应当声明该函数为static.

如果一个函数的调用和定义不在同一个文件中,则应当在调用它前进行声明。

一个的好的习惯是将声明放在头文件中,而在需要声明变量的文件中包含头文件。

2)一个典型例子:

#include "iostream"

using namespace std;

int main()

{

int i;

char c;

for(i=0;i<5;i++)

{

scanf("%d",&c);

printf("%d ",i);

}

return 1;

}

c是char型。程序要求输入整数,应该给它传递一个指向整数的指针。而程序中得到的却是一个指向字符的指针,而scanf函数并不能分辨,于是将这个指向字符的指针作为指向整数的指针而接受,并且在指针位置存储一个整数;这样,占了两倍的空间(一般char是一个字节,int是两个字节或更多),所以c将 附近的内存覆盖。本例中,其附近存储的是i的低字节部分,这样,每次读入一个c时,将i的低端部分覆盖为0,而其高端部分也为0,所以就进入了死循环。

当然这些情况在C++中不会出现。

3)getchar()函数是返回整形的,如果将之赋给字符了字符变量,如下

char c;

while((c=getchar())!=EOF)

 putchar(c);

可能会出现意想不到的结果。

4)更新顺序文件

为了保证与过去不能同时进行读写操作的程序的向下兼容性,一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入与输出操作,必须在其中插入fseek函数。如下所示:

int fwrite(void *buf, int size, int count, FILE *fp)

int fseek(FILE *fp, LONG offset, int origin)

int fread(void *buf, int size, int count, FILE *fp)

5)缓冲输出与内存分配

程序输出有两种方式:一种是即时处理方式,另一种是先暂存进来,然后大块写入。对于第二种方式,通过库函数setbuf来实现。如buf是一个合适大小的数组,则

setbuf(stdout,buf);

将通知输入/输出库,所以写入到stdout的输出就用buf作为缓冲区,直到buf缓冲区满了或程序员用fflush,缓冲区的内容才实际定到stdout中。缓冲区的大小由<stdio.h>中的BUFSIZ定义。如下所示:

int main()

{

int c;

char buf[BUFSIZ];

setbuf(stdout,buf);

while((c=getchar())!=EOF)

 putchar(c);

}

然后,程序不对。因为buf缓冲区最后一次清空是在main函数结束后,但此前buf数组已被释放。解决方法:

(1)显示声明为静态:

static char buf[BUFSIZ];//或移到main函数之外。

(2)动态分配缓冲区。

可以作个测试如下:

void hello()

{

int c;

static char buf[3000];

setbuf(stdout,buf);

for(int m=0;m<25;m++)

{

for(int i=0;i<344;i++)

 cout<<i<<" ";

 cout<<m<<" "<<endl;

}//for

}

int main()

{

hello();

cout<<"finish";

}

5)使用errno检测错误

   在调用库函数时,应当首先检测作为错误指示的返回值,确定程序执行已经失败。然后再检查errno来搞清楚原因:

if(返回的错误值)

   检查 errno

6)库函数signal

void ( *signal(int sig, void (*func)(int)))(int)

sig 信号数值,该参数以前由raise函数设置,取值为如下:      

#define SIGABRT 22                                     

#define SIGFPE  8    /* Floating point trap  */        

#define SIGILL  4    /* Illegal instruction  */        

#define SIGINT  2                                      

#define SIGSEGV 11   /* Memory access violation */     

#define SIGTERM 15   

     信号问题棘手,而且具有本质上不可移植性,解决这个问题:让signal处理函数尽可能简单,并将他们组织在一起以便修改。

   常将getchar()实现为宏。

发布了208 篇原创文章 · 获赞 30 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hopegrace/article/details/104168370