《C陷阱与缺陷》Finux_you读书笔记(2)

p52-56<缓冲区的另一个例子>:

需求:程序生成一些可能包括若干页的整数,每页包括NCOLS列,每列包括NROWS个元素。程序生成时按列分布,打印时按行打印。

分析:需要两个函数:

print:生成的整数传给print,它仅在缓冲区满时才打印,未满时将数字送入缓冲区。其中打印任务由三个函数完成:printnum在本页当前位置打印一个数值;printnl则打印一个换行符;printpage打印一个分页符。

flush:最后一个数值生成后,不管缓冲区是否已满,调用flush打印缓冲区中所有元素。

某种形式的缓冲区必不可少。因为只有知道某行的最后一个元素时,才能打印这一行的元素。对于这个元素,只要知道它的值,我们就能将它所对应的行打印出来,所以缓冲区不必是一整页而是不必包括最后一列:

#define BUFSIZE (NROWS * (NCOLS - 1))

static int buffer[BUFSIZE];/*静态数组,防止被程序的其他部分访问*/

要跟踪元素进入缓冲区时所处的位置:

static int *bufptr = buffer;

目前print函数应该是这样:

暂时不能确定的某些操作包括:打印当前行的所有元素,行号增加1;如果一页内所有行都已经打印,另起新的一页。需要一个局部静态变量row保存当前行号。

对于打印当前行元素我们的储备知识有:biffer[row]是第row行的第一个元素,属于第一列。同一行中相邻的元素相隔NROWS个元素。因此,打印缓冲区中属于当前行的元素看起来像这样:

剩下的暂时不能确定的操作(打印当前输入数值;打印换行符;如果是最后一页打印分页符):

因此,print函数看上去是这样:

 

flush函数就简单了:


p67-81<连接>:

  1. 每个外部变量只定义一次,别处引用extern。
  2. 如果一个函数仅仅被同一个源文件中的其他函数调用,就应该声明该函数为static。
  3. 传给函数的参数可以是表达式。
  4. 如果一个参数在定义或声明前被调用,它的返回值类型就默认为整形。
  5. 以下函数判断一个字母是否是元音字母: 
  6. scanf、printf的参数格式不一致会造成错误甚至程序崩溃比如: 
  7. 由于某种奇妙的巧合,错误的程序可能正确地运行。
  8. 保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,是程序员的责任。例如: 

p137<大小端问题>:

在处理器中,像0x12345678这样的数据总是按高位优先(big-endian)方式存放的。但在内存中数据存放顺序因为处理器不同而不同。

big-endian:最低地址存放高位字节,可称为高位优先。

little-endian:最低地址存放地位字节,可称为低位优先。


p84<返回整形的getchar>:

这个故事告诉我们要仔细查看库函数的参数和返回值:

 

有必要整理一下C语言i/o函数,开个小专题。


p139<丢失输出问题>:

问题:一个程序异常终止时,输出的最后几行常常会丢失,原因是什么?该怎样解决?(km2000就曾有这样的问题出现)

分析:原因是该程序没有机会清空输出缓冲区,因此,该程序的输出很可能位于内存中的某个位置永远不会被写出。在某些系统上,这些无法被写出的输出可能长达好几页。

解决方法:在调试时强制不允许对输出进行缓冲,要做到这一点,不同系统有不同的做法,但大概如下:

setbuf(stdout, (char *)0);

该语句必须在任何输出被写入到stdout之前执行,最恰当的位置是main函数的第一个语句。


p139<函数而且是宏>:

下面的程序的作用是吧它的输入复制到输出:

 

如果改为以下:

 

这个程序在许多系统中依然可以运行,但是在某些系统中却慢很多,为什么?

分析:为了消除函数调用花掉的大量时间,getchar函数通常被实现为宏(在stdio.h中定义),如果没有包含stdio.h文件,getchar就会被编译器认为是一个返回类型为整型的函数。实际上,很多C语言实现在库文件中都包括有getchar函数(C标准库中被宏getchar屏蔽),部分是防止编程者粗心大意,部分是为了方便那些需要得到getchar函数地址的编程者(宏不能得到地址)。所以第二个程序中实际上使用的是函数getchar,导致系统开销增多。同样,putchar也是这样。


2011-02-21 19:29:23

p93-101<关于宏>:

1、#define f (x) ((x) - 1) /*f后面有个空格*/

其中f代表的是:

(x) ((x) - 1)

2、宏不是函数:

(1)在宏定义中把每个参数都括起来,包括整体也要括起来,像这样:

#define abs(x) (((x) > 0) ? (x) : (-x))

#define max(a,b) ((a) > (b) ? (a) : (b))

(2)以下做法是错误的:

#define max(a,b) ((a) > (b) ? (a) : (b))

biggest = x[0];

i = 1;

while(i < n)

biggest = max(biggest, x[i++]);

正确的做法是确保宏max中的参数没有副作用:

#define max(a,b) ((a) > (b) ? (a) : (b))

biggest = x[0];

for(i = 1; i < n; i++)

biggest = max(biggest, x[i]);

另一种方法是让max变为函数,或者直接写出比较较大者的代码:

biggest = x[0];

for(i = 1; i < n; i++)

if(x[i] > biggest)

biggest = x[i];

(3)将小写字母转换为大写字母的toupper函数可以这样实现:

toupper(int c)

{

if(c >= 'a' && c <= 'z')

c += 'A' - 'a';

return c;

}

如果实现为宏就有可能发生副作用:

#define toupper(c) ((c >= 'a' && c <= 'z') ? ((c += 'A' - 'a') : (c)))

(4)如果我们要用max宏找到a,b,c,d四个数的最大者我们可能会这样写:

max(a, max(b, max(c, d))) 或者:

max(max(a, b), max(c, d)) 

我们难以想象上面的宏展开以后会有多长,其实,写成下面代码似乎更容易一些:

biggest = a;

if(biggest < b) biggest = b;

if(biggest < c) biggest = c;

if(biggest < d) biggest = d;

3、宏不是语句:

如果把assert宏写成下面这个样子:

#define assert(e) if(!e) assert_error(_FILE_, _LINE_)

那么想象一下如果assert宏像下面这样用会如何:

if(x > 0 && y > 0)

assert(x > y);

else

assert(y > x);

其实,assert宏的正确实现应该是:

#define assert(e) ((e) || assert_error(_FILE_, _LINE_))

4、用宏实现max,参数都是整数,要求这些整形参数在该宏中只被求值一次:

static int max_temp1, max_temp2;

#define max(a, b) (max_temp1 = (a) , max_temp2 = (b),\

max_temp1 > max_temp2 ? max_temp1 : max_temp2)

当然,以上宏在嵌套调用的时候是不正常工作的。


2011-02-22 22:22:22 星期2



posted @ 2011-02-21 11:37  Finux_you  Views(289)  Comments(0Edit  收藏  举报