《C专家编程》Finux_you读书笔记(1)
p22-27<ANSI C的Bug>:
你认为下面程序会打印出什么?为什么?
2 #include <conio.h>
3 int a[] = {1,3,4,5,6,7,8};
4 /*(sizeof(a[0]))而不是sizeof(int)*/
5 #define MAX_ELEMENT ((sizeof(a)) / (sizeof(a[0])))
6 int main(void)
7 {
8 int d = -1;
9 if(d <= MAX_ELEMENT - 2)
10 {
11 printf("There is no bug.\n");
12 }
13 else
14 {
15 printf("OH! FUCK! BUG!\n");
16 }
17 getch();
18 return 0;
19 }
是的,打印出:OH! FUCK! BUG! 这是C语言规则的一个小小的Bug。
(1)如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算数转换(usual arithmetic conversion):
long double
double
float
unsigned long int
long int
unsigned int
int ——摘自《C和指针》p80
上述规则可以分析为:当执行算术运算时,操作数的类型如果不同,就会发生转换。数据类型一般朝着浮点精度更高、长度更长的方向转换。整形数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。
sizeof运算符的结果是无符号数,所以MAX_ELEMENT是无符号数。d就被升级为unsigned int,结果将是一个非常大的正数。
解决办法:将MAX_ELEMENTS强制转换为int型。:
if(d <= (int)MAX_ELEMENT - 2)
(2)值得补充的一点:C的整形算术运算总是至少以整形类型的精度进行的。所以表达式中的char型和short型会在使用之前被转换为整型,这种转换称为为整型提升(integral Promotion):
char a, b, c;
a = b + c;
b和c被提升为普通整型,然后相加,结果将被截断存储于a中。注意溢出的情况!
(3)对无符号类型的建议:
尽量不要使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在负值而用它表示数量。
尽量使用int那样的有符号类型。这样在设计升级混合类型的复杂细节时,不必担心边界情况(如-1被翻译为非常大的正数)。
只有在使用位段和二进制掩码时,才可以用无符号数。在表达式中使用强制类型转换,是操作数均为有符号数或者无符号数,这样就不必由编译器来选择结果的类型
(4)#define MAX_ELEMENT ((sizeof(a)) / (sizeof(a[0])))中的sizeof(a[0])可以在不改写宏的情况下改变数组a元素类型。
(5)%的操作数必须属于整型,否则会引发编译错误。
2011-02-24 17:23:3
p27<这不是bug,而是语言特性>:
Bug是迄今为止地球上最庞大最成功的实体类型,有近百万已知的品种。在这个方面,它比其他任何已知的生物种类的总和还要多,而且至少多出4倍。 ——Snope,《Encyclopedia of Animal Life》
在本书的第二章“这不是bug,而是语言特性”中讲解了很多C语言中不好的特性,包括:多做之过、少做之过和误做之过。值得仔细研读。
p30<局部变量>:
int main()
{
char temp[1] = {'a'};
if(a[0])
{char temp[1] = {'b'};}
return 0;
}
上面代码中的两个temp互不可见。if下的temp只在大括号中有用。
p33<break中断了什么>:
break中断的是最进的那层循环语句或switch语句,而不是从最进的大括号跳出去。
p34<字符串合并>:
ANSI C引入的一个特性:相邻的字符串常量将会自动合并为一个字符串。如下面代码:
char *a[] = {
"ab"
"out",
"idear",
}
a[0]就成了“about”;因为"ab"后面遗漏了一个逗号。注意最后一个逗号,可以通过编译。
p47<编译器日期破坏>:
Sun的Pascal编译器中有一段程序打印日期,却时常出现乱码,最后查出错误在如下代码:
cahr * localized_time(char *filename)
{
char buffer[120];
......
return buffer;
}
原因你是知道的。解决的办法有如下几种
(1)返回一个指向字符串常量的指针:
char *fuc()
{
reutrn "Only works for simple strings";
}
但是需要改写字符串的内容时会有麻烦。
(2)使用全局声明的数组:
char buffer[120];
char *fuc();
{
buffer[i] = ".......";
......
reutrn buffer;
}
缺点就是全局变量的可见性。
(3)使用静态数组:
char *fuc()
{
static char buffer[120];
......
return buffer;
}
这就可以防止任何人修改这个数组,只有拥有指向该数组的指针的函数(通过参数传递给它)才能修改这个静态数组。当然,和全局变量一样,大型缓冲区如果限制不用是非常浪费空间的。
(4)显示分配一些内存,保存返回的值:
char *fuc()
{
char *buffer = malloc(120);
......
return buffer;
}
缺点是程序员必须承担内存管理责任。根据程序的复杂度,可能很容易也可能很复杂。容易产生内存泄露。
(5)也许最好的办法是要求调用者分配内存来保存函数的返回值。为了提高安全性,调用者应该同时制定缓冲区大小(就像fgets)()):
void fuc(char *result, int size)
{
strncpy(result, "That'd be in the data segment, Bob", size);
}
buffer = malloc(size);
fuc(buffer, size);
......
free(buffer);
如果程序员能在同一代码块中同时进行malloc和free操作,内存管理最为轻松。
2011-02-25 16:33:42
p72<cdecl.c>:
编程要对解决问题的步骤十分清楚,然后再对其进行抽象,使之适合编程语言表达。这样才能写出好程序,比如下面是分析C语言声明的程序,很难想象如果不会分析声明怎么编出来:
2 #include<stdio.h>
3 #include<string.h>
4 #include<ctype.h>
5 #include<stdlib.h>
6 #define MAXTOKENS 100
7 #define MAXTOKENLEN 64
8 enum type_tag
9 {
10 IDENTIFIER,QUALIFIER,TYPE
11 };
12 struct token
13 {
14 char type;
15 char string[MAXTOKENLEN];
16 };
17
18 struct token stack[MAXTOKENS];
19 struct token this;
20 #define pop stack[top--]
21 #define push(s) stack[++top] = s;
22 int top=-1;
23 enum type_tag classify_string(void)
24 /*推断标识符的类型*/
25 {
26 char *s = this.string;
27 if(!strcmp(s,"const"))
28 {
29 strcpy(s,"read-only");
30 return QUALIFIER;
31 }
32 if(!strcmp(s,"volatile")) return QUALIFIER;
33 if(!strcmp(s,"void")) return TYPE;
34 if(!strcmp(s,"char")) return TYPE;
35 if(!strcmp(s,"signed")) return TYPE;
36 if(!strcmp(s,"unsigned")) return TYPE;
37 if(!strcmp(s,"short")) return TYPE;
38 if(!strcmp(s,"int")) return TYPE;
39 if(!strcmp(s,"long")) return TYPE;
40 if(!strcmp(s,"float")) return TYPE;
41 if(!strcmp(s,"double")) return TYPE;
42 if(!strcmp(s,"struct")) return TYPE;
43 if(!strcmp(s,"union")) return TYPE;
44 if(!strcmp(s,"enum")) return TYPE;
45 return IDENTIFIER;
46 }
47 void gettoken(void) /* 读取下一个标记到"this" */
48 {
49 char *p = this.string;
50 /* 略过空白字符 */
51 while((*p = getchar()) == ' ')
52 ;
53 if(isalnum(*p))
54 {
55 /* 读入的标识符以A-Z,0-9开头 */
56 while(isalnum(*++p = getchar()))
57 ;
58 ungetc(*p,stdin);
59 *p = '\0';
60 this.type = classify_string();
61 return;
62 }
63 if(*p == '*')
64 {
65 strcpy(this.string,"pointer to")
66 ;
67 this.type = '*';
68 return;
69 }
70 this.string[1] = '\0';
71 this.type = *p;
72 return;
73 }
74 /* 理解所有分析过程的代码段 */
75 void read_to_first_identifier()
76 {
77 gettoken();
78 while(this.type != IDENTIFIER)
79 {
80 push(this);
81 gettoken();
82 }
83 printf("%s is ",this.string);
84 gettoken();
85 }
86 void deal_with_arrays()
87 {
88 while(this.type == '[')
89 {
90 printf("array");
91 gettoken(); /*数字或']'*/
92 if(isdigit(this.string[0]))
93 {
94 printf("0..%d",atoi(this.string)-1);
95 gettoken();
96 }
97 gettoken();
98 printf("of ");
99 }
100 }
101 void deal_with_function_args()
102 {
103 while(this.type != ')')
104 {
105 gettoken();
106 }
107 gettoken();
108 printf("function returning");
109 }
110 void deal_with_pointers()
111 {
112 while(stack[top].type == '*')
113 {
114 printf("%s ",pop.string);
115 }
116 }
117 void deal_with_declarator()
118 {
119 /* 处理标识符之后可能存在的数组/函数*/
120 switch(this.type)
121 {
122 case '[':deal_with_arrays(); break;
123 case '(':deal_with_function_args();
124 }
125 deal_with_pointers();
126 /* 处理在读入到标识符之前压入到堆栈中的符号 */
127 while(top>=0)
128 {
129 if(stack[top].type == '(')
130 {
131 pop;
132 gettoken();
133 deal_with_declarator();
134 }
135 else
136 printf("%s",pop.string);
137 }
138 }
139 int main()
140 {
141 /* 将标记压入堆栈中,直到遇见标识符*/
142 read_to_first_identifier();
143 deal_with_declarator();
144 printf("\n");
145 getch();
146 return 0 ;
147 }
2011-02-28 20:21:41