Posts Tagged ‘C’

Notes of C Programming FAQs (5)

Wednesday, May 13th, 2009

下面是一些通常的检查要点:
² 未初始化的局部变量
² 整数上溢, 特别是在一些16 比特的机器上, 一些中间计算结果可能上溢, 象a * b / c
² 未定义的求值顺序
² 忽略了外部函数的说明, 特别是返回值不是int 的函数, 或是参数“缩小” 或可变的函数
² 复引用空指针
² malloc/free 的不适当使用: 假设malloc 的内存都被清零、已释放的内存还可用、再次释放已释放内存、malloc 的内部被破坏
² 指针类常规问题
² printf() 格式与参数不符, 特别是用%d 输出long int
² 试图分配的内存大小超出一个unsigned int 类型的范围, 特别是在内存有限的机器上
² 数组边界问题, 特别是暂时的小缓冲, 也许用于sprinf() 来构造一个字符串
² 错误的假设了typedef 的映射类型, 特别是size t
² 浮点问题
² 任何你自己认为聪明的在特定机器上的机器代码生成小技巧

“Segmentation violation”, “Bus error” 和“General protectionfault” 意味着什么?
通常, 这意味着你的程序试图访问不该访问的内存地址, 一般是由于堆栈出错或是不正确的使用指针。可能的原因有: 局部数组溢出(用堆栈分配的自动变量);不小心, 用了空指针、未初始化指针、地址未对齐的指针或其它没有适当分配的指针; malloc 内部被破坏; 函数调用参数不匹配, 特别是如果用了指针, 两个可能出错的函数是scanf()和fprintf() (确定他的第一个参数是FILE *)。

“印第安山风格指南” (Indian Hill Style Guide)

用gcc -Wall -pedantic替代lint

怎样显示一个百分比或“转动的短棒” 的进展表示器?
这个简单的事情, 你可以做到相当的可移植。输出字符’\r’ 通常可以得到一个回车而没有换行, 这样你就可以复写当前行。字符’\b’ 代表退格, 通常会使光标左移一格。记住要调用fflush()。

使用数据库来取代平坦文件

怎样抓获或忽略像control-C 这样的键盘中断?
基本步骤是调用signal():
#include <signal.h>
singal(SIGINT, SIG_IGN);
就可以忽略中断信号, 或者:
extern void func(int);
signal(SIGINT, func);
使程序在收到中断信号时, 调用函数func()。

怎样判断机器的字节顺序是高字节在前还是低字节在前?
有个使用指针的方法:


int x = 1;
if(*(char *)&x == 1)
    	printf("little-endian\n");
else
    	printf("big-endian\n");

另外一个可能是用联合。

从其他语言转换到C的工具:p2c, ptoc, f2c

由一个日期, 怎样知道是星期几?
这个由Tomohiko Sakamoto 提供的优雅的代码:


int dayofweek(int y, int m, int d) /* 0 = Sunday */
{
        static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
        y -= m < 3;
        return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}

自打印程序:


char*s="char*s=%c%s%c;main(){printf(s,34,s,34);}";main(){printf(s,34,s,34);}

还有一个有James Hu 发布的改进版:


#define q(k)main(){return!puts(#k"\nq("#k")");}
q(#define q(k)main(){return!puts(#k"\nq("#k")");})

什么是“达夫设备” (Duff’s Device)?
这是个很棒的迂回循环展开法, 由Tom Duff 在Lucasfilm 时所设计。它的“传统” 形态, 是用来复制多个字节:


register n = (count + 7) / 8; /* count > 0 assumed */
switch (count % 8)
{
case 0: do { *to = *from++;
case 7:      *to = *from++;
case 6:      *to = *from++;
case 5:      *to = *from++;
case 4:      *to = *from++;
case 3:      *to = *from++;
case 2:      *to = *from++;
case 1:      *to = *from++;
            	} while (--n > 0);
}

这里count 个字节从from 指向的数组复制到to 指向的内存地址(这是个内存映射的输出寄存器, 这也是为什么它没有被增加)。它把swtich 语句和复制8 个字节的循环交织在一起, 从而解决了剩余字节的处理问题(当count 不是8 的倍数时)。相信不相信, 象这样的把case 标志放在嵌套在swtich 语句内的模块中是合法的。当他公布这个技巧给C 的开发者和世界时, Duff 注意到C 的swtich 语法, 特别是“跌落” 行为, 一直是被争议的, 而“这段代码在争论中形成了某种论据, 但我不清楚是赞成还是反对”。

Notes of C Programming FAQs (4)

Wednesday, May 13th, 2009

\n 在scanf 格式串中不表示等待换行符, 而是读取并放弃所有的空白字符。

在scanf() 转换数字的时候, 它遇到的任何非数字字符都会终止转换并被保留在输入流中。因此, 除非采用了其它的步骤, 那么未预料到的非数字输入会不断“阻塞” scanf(): scanf() 永远都不能越过错误的非数字字符而处理后边的合法数字字符。

通常先用类似fgets() 的函数读入整行, 然后再用sscanf() 或其它技术解释。

要避免溢出问题, 你可以使用限制长度的sprintf() 版本, 即snprintf()。这样使用:snprintf(buf, bufsize, “You typed \”%s\”", answer);
snprintf() 在几个stdio 库中已经提供好几年了, 包括GNU 和4.4bsd。在C99中已经被标准化了。作为一个额外的好处, C99 的snprintf() 提供了预测任意sprintf() 调用所需的缓冲区大小的方法。C99 的snprintf() 返回它可能放到缓冲区的字符数, 而它又可以用0 作为缓冲区大小进行调用。因此nch = snprintf(NULL, 0, fmtstring, /* 其它参数*/ );这样的调用就可以预测出格式串扩展后所需要的字符数。

没有什么标准的办法可以丢弃标准输入流的未读取字符, 即使有, 那也不够,因为未读取字符也可能来自其它的操作系统级的输入缓冲区。

读取二进制数据文件的时候你应该用“rb” 调用fopen(), 确保不会发生文本文件的解释。类似的, 写二进制文件时, 使用“wb”。注意文本/二进制区别只是发生在文件打开时: 一旦文件打开之后, 在其上调用何种I/O 函数无关紧要。

怎样产生标准分布或高斯分布的随机数?
这里有一个由Marsaglia 首创Knuth 推荐的方法:


#include <stdlib.h>
#include <math.h>
double gaussrand()
{
    	static double V1, V2, S;
    	static int phase = 0;
    	double X;
    	if(phase == 0) {
    	    	do {
            	double U1 = (double)rand() / RAND_MAX;
            	double U2 = (double)rand() / RAND_MAX;
            	V1 = 2 * U1 - 1;
            	V2 = 2 * U2 - 1;
            	S = V1 * V1 + V2 * V2;
    	    	} while(S >= 1 || S == 0);
    	    	X = V1 * sqrt(-2 * log(S) / S);
    	} else
        	X = V2 * sqrt(-2 * log(S) / S);
    	phase = 1 - phase;
    	return X;
}

大多数电脑都是用二进制来表示浮点和整数的。在十进制里, 0.1 是个简单、精确的小数, 但是用二进制表示起来却是个循环小数0.0001100110011 . . . 。

“参数默认晋级” 规则适用于在可变参数中的可变动部分: 参数类型为float 的总是晋级(扩展) 到double, char 和short int 晋级到int。所以va arg(arpg, float)是错误的用法。应该总是用va arg(arpg, double)。

程序在执行用之前就崩溃了, 用调试器单步跟进, 在main() 之前就死了。
也许你定义了一个或多个非常大的局部数组(超过上千字节)。许多系统只有固定大小的堆栈, 即使那些自动动态堆栈分配的系统也会因为一次性要分配大段堆栈而失败。
一般对大规模数组, 定义为静态的数组会更好。如果由于递归的原因, 每次都需要一组新的数组, 可以用malloc() 动态申请内存。

Notes of C Programming FAQs (3)

Wednesday, May 13th, 2009

一般地说, 使用指针的时候, 你必须总是考虑内存分配, 除非明确知道编译器替你做了此事。

那么返回字符串或其它集合的争取方法是什么呢?
返回指针必须是静态分配的缓冲区, 或者调用者传入的缓冲区, 或者用malloc() 获得的内存, 但不能是局部(自动) 数组。

sizeof(char) 严格为1。

一个常见的bug 是用malloc(strlen(s)) 而不是strlen(s) + 1。

当你调用free() 的时候, 传入指针指向的内存被释放, 但调用函数的指针值可能保持不变, 因为C 的按值传参语义意味着被调函数永远不会永久改变参数的值。

calloc(m, n) 本质上等价于 p = malloc(m * n); memset(p, 0, m * n); 填充的零是全零, 因此不能确保生成有用的空指针值或浮点零值。free() 可以安全地用来释放calloc() 分配的内存。

C语言中的字符常数是int 型, 因此sizeof(’a’) 是sizeof(int),这是另一个与C++ 不同的地方。

如果你认为“if((a == b) == TRUE)” 比“if(a == b)” 好, 为什么就此打住呢?为什么不使用“if(((a == b) == TRUE) == TRUE)” 呢?

如果宏体内的语句都是简单语句, 没有声明或循环, 那么还有一种技术, 就是写一个单独的, 用一个或多个逗号操作符分隔的表达式。这种技术还可以“返回” 一个值。

作为一般规则, 你应该把这些东西放入头(.h) 文件中:
² 宏定义(预处理#defines)
² 结构、联合和枚举声明
² typedef 声明
² 外部函数声明
² 全局变量声明
当声明或定义需要在多个文件中共享时, 尤其需要把它们放入头文件中。特别是, 永远不要把外部函数原型放到.c 文件中。
另一方面, 如果定义或声明为一个.c 文件私有, 则最好留在.c 文件中。

我在文件的第一个声明就遇到奇怪的语法错误, 但是看上去没什么问题。可能你包含的最后一个头文件的最后一行缺一个分号。

C99 引入了对参数个数可变的函数式宏的正式支持。在宏“原型” 的末尾加上符号… (就像在参数可变的函数定义中), 宏定义中的伪宏__VA_ARGS__ 就会在调用时替换成可变参数。

“const char *p” (也可以写成“char const *p”) 声明了一个指向字符常量的指针, 因此不能改变它所指向的字符; “char * const p” 声明一个指向(可变) 字符的指针常量, 就是说, 你不能修改指针。

在使用符号粘接操作符## 连接两个宏的值(而不是名字) 时也要采用同样的“迂回战术”。

当你希望把宏参数转成字符串时,你可以使用新的预处理操作符# 和字符串常量连接(ANSI 的另一个新功能)

如果源和目的参数有重叠, memmove() 提供有保证的行为。而memcpy()则不能提供这样的保证, 因此可以实现得更加有效率。

保存getchar 的返回值的变量必须是int 型。getchar() 可能返回任何字符值, 包括EOF。如果把getchar 的返回值截为char 型, 则正常的字符可能会被错误的解释为EOF, 或者EOF 可能会被修改(尤其是char 型为无符号的时候), 从而永不出现。

在C 语言中, 只有输入例程试图读并失败以后才能得到文件结束符。换言之,C 的I/O 和Pascal 的不一样。通常你只需要检查输入例程的返回值, 例如, fgets()在遇到文件结束符的时候返回NULL。实际上, 在任何情况下, 都完全没有必要使用feof()。

我如何在printf 的格式串中输出一个’%’?我试过\%, 但是不行。
只需要重复百分号: %%。
\%不行, 因为反斜杠\ 是编译器的转义字符, 而这里我们的问题最终是printf的转义字符。

在printf 中使用%lf 不正确。scanf() 需要%lf,printf 的%f 标识符的确既可以输出浮点数又可以输出双精度数。printf() 的确接受%Lf, 用于输出长双精度数。

对于size t 那样的类型定义, 当我不知道它到底是long 还是其它类型的时候, 我应该使用什么样的printf 格式呢?
把那个值转换为一个已知的长度够大的类型, 然后使用与之对应的printf 格式。例如, 输出某种类型的长度, 你可以使用
printf(“%lu”, (unsigned long)sizeof(thetype));

我如何用printf 实现可变的域宽度?就是说, 我想在运行时确定宽度而不是使用%8d?
printf(“%*d”, width, x) 就能达到你的要求。

Notes of C Programming FAQs (2)

Wednesday, May 13th, 2009

*((condition) ? &a : &b) = complicated_expression;

*p++ 和*(p++) 等价,如果副作用的顺序无关紧要也可以使用++*p。

对函数extern int f(int *);用引用方式传入一个常数。在C99 中, 可以使用“复合常量”:f((int[]){5});

最初, 一个函数指针必须用* 操作符(和一对额外的括弧) “转换为” 一个“真正的” 函数才能调用:
int r, func(), (*fp)() = func;
r = (*fp)();
而函数总是通过指针进行调用的, 所有“真正的” 函数名总是隐式的退化为指针(在表达式中, 正如在初始化时一样。)。这个推论表明无论fp 是函数名和函数的指针
r = fp();
ANSI C 标准实际上接受后边的解释, 这意味着* 操作符不再需要, 尽管依然允许。

execl(“/bin/sh”, “sh”, “-c”, “date”, (char *)0);
如果省略最后一个参数的(char *) 转换, 则编译器无从知道这是一个空指针,从而当作一个0 传入。(注意很多Unix 手册在这个例子上都弄错了。)

有两条简单规则你必须遵循:
1. 当你在源码中需要空指针常数时, 用“0” 或“NULL”。
2. 如果在函数调用中“0” 或“NULL” 用作参数, 把它转换成被调函数需要的指针类型

一个图形胜过千言万语。声明
char a[] = “hello”;
char *p = “world”;
将会初始化下图所示的数据结果:


   +---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
   +---+---+---+---+---+---+
   +-----+     +---+---+---+---+---+---+
p: |  *======> | w | o | r | l | d |\0 |
   +-----+     +---+---+---+---+---+---+

说数组和指针“等价”不表示它们相同, 甚至也不能互换。它的意思是说数组和指针的算法定义可以用指针方便的访问数组或者模拟数组。

一个T 的数组类型的左值如果出现在表达式中会蜕变为一个指向数组第一个成员的指针(除了三种例外情况); 结果指针的类型是T 的指针。

一旦数组出现在表达式中, 编译器会隐式地生成一个指向数组第一个成员地指针, 就像程序员写出了&a[0] 一样。例外的情况是, 数组为sizeof 或&操作符的操作数, 或者为字符数组的字符串初始值。

C99引入了变长数组(VLA),局部数组的大小可以用变量或其它表达式设置, 可能也包括函数参数。

如果你向函数传递二位数组:
int array[NROWS][NCOLUMNS];
f(array);
那么函数的声明必须匹配:
void f(int a[][NCOLUMNS])
{ … }
或者
void f(int (*ap)[NCOLUMNS]) /* ap 是个数组指针*/
{ … }

Notes of C Programming FAQs (1)

Wednesday, May 13th, 2009

尽管字符类型(尤其是无符号字符型) 可以当成“小” 整型使用, 但由于不可预知的符号扩展和代码增大有时这样做可能得不偿失。

定义是分配空间并赋初值(如果有) 的声明。

永远不要把外部函数的原型放到.c 文件中: 通常它与定义的一致性不能得到检查, 而矛盾的原型比不用还糟糕。

在extern int f();和int f();之间并没有实质的区别。

使用cdecl 程序, 它可以把英文翻译成C 或者把C 翻译成英文

一本好的C 语言书都会解释如何“从内到外” 解释和理解这样复杂的C 语言声明(“模拟声明使用”)。

在范围内没有声明就调用(可能是第一次调用在函数的定义之前) 的函数被认为返回整型(int) (且没有任何参数类型信息), 如果函数在后边声明或定义成其它类型就会导致矛盾。所有函数(非整型函数一定要) 必须在调用之前声明。

calloc() 获得的内存为全零, 但这对指针和浮点值不一定有用

以下的初始化有什么区别?char a[] = “string literal”; char *p = “string literal”; 当我向p[i] 赋值的时候, 我的程序崩溃了。
字符串常量有两种稍有区别的用法。用作数组初始值(如同在char a[] 的声明中), 它指明该数组中字符的初始值。其它情况下, 它会转化为一个无名的静态字符数组, 可能会存储在只读内存中, 这就是造成它不一定能被修改。在表达式环境中, 数组通常被立即转化为一个指针, 因此第二个声明把p 初始化成指向无名数组的第一个元素。

int (*fp)() = func;

这样声明结构的代码: struct name { int namelen; charnamestr[1];}; 然后又使用一些内存分配技巧使namestr 数组用起来好像有多个元素。这种技术十分普遍。这些“亲密” 结构都必须小心使用, 因为只有程序员知道它的大小, 而编译器却一无所知。
C99 引入了“灵活数组域” 概念, 允许结构的最后一个域省略数组大小。这为类似问题提供了一个圆满的解决方案。

如何向接受结构参数的函数传入常数值?
传统的C 没有办法生成匿名结构值; 你必须使用临时结构变量或一个小的结构生成函数。
C99 标准引入了“复合常量” (compound literals); 复合常量的一种形式就可以允许结构常量。例如, 向假想plotpoint() 函数传入一个坐标对常数, 可以调用plotpoint((struct point){1, 2});
与“指定初始值” (designated initializers) (C99 的另一个功能) 结合, 也可以用成员名称确定成员值:plotpoint((struct point){.x=1, .y=2});

如何确定域在结构中的字节偏移?
ANSI C 在<stddef.h> 中定义了offsetof() 宏, 用offsetof(struct s, f) 可以计算出域f 在结构s 中的偏移量。如果出于某种原因, 你需要自己实现这个功能, 可以使用下边这样的代码:
#define offsetof(type, f) ((size_t)
((char *)&((type *)0)->f – (char *)(type *)0))
这种实现不是100% 的可移植; 某些编译器可能会合法地拒绝接受。

a[i] = i++; 不能工作

表达式: a ˆ= b ˆ= a ˆ= b不具有可移植性。它试图在序列点之间两次修改变量a, 而这是无定义的。

如果一个表达式和程序变得未定义, 则它的所有方面都会变成未定义。