《C专家编程》:这不是Bug,而是语言特性

无论什么时候,如果遇到了这样一条语句malloc(strlen(str)),几乎可以断定它是错误的,而malloc(strlen(str + 1))才是正确的,这是因为其他的字符串处理库函数几乎都包含一个额外空间,用于容纳字符串结尾的'\0'字符,所以,人们很容易忽略strlen这个特殊情况。

分析编程语言缺陷的一种方法就是把所有的缺陷归于3类:不该做的做了;该做的没做;该做但做得不合适

多做之过

容易出错的switch语句

switch语句的一般形式如下:

switch(表达式) {
    case 常量表达式 : 零条或多条语句 
    default : 零条或多条语句 
    case 常量表达式 : 零条或多条语句 
}

当表达式的值与case中的常量匹配时,该case后面的语句就会执行。default(如果有的话)可以出现在case列表的任何位置,它在其他的case均无法匹配时被选中执行。如果没有default,而且所有的case均不匹配,那么整个switch语句便什么也不做。

#include 
int main(void)
{
	
	switch(3) {
		default : printf("3\n");
 		case 1 : printf("1\n");
		case 2 : printf("2\n"); 
	}
	return 0;
}
/*
打印
3
1
2
*/

switch对case可能出现的情况太过于放纵了,例如:可以在switch的左花括号之后声明一些变量,从而进行一些局部存储的分配。在switch语句中为这些变量加上初始值是没有什么用处的,因为它就不会被执行——语句从匹配表达式的case开始执行。

#include 
int main(void)
{
	int a = 1; 
	switch(3) {
		a = 2;
 		case 1 : printf("1\n");
		case 2 : printf("2\n"); 
	}
	printf("%d\n",a);
	return 0;
}
/*
a仍然输出1
*/ 

switch的另一个问题是它内部的任何语句都可以加上标签,并在执行时跳转到那里,这就有可能破话程序流的结构化:

switch(i) {	
		case 5 + 3 : do_again:;		
		case 2 : printf("3333333333333333\n"); goto do_again;
		case 3 : ;
	}

switch最大的缺点是它不会再每个case标签后面的语句执行完毕后自动中止。一旦执行某个case语句,程序将会依次执行后面所有的case,除非遇到break语句,这称之为“fall through”,它的意思是:如果case语句后面不加break,就依次执行下去,以满足某些特殊情况的需求。但实际上,这是一个非常不好的特性,因为几乎所有的case都需要以break结尾。

break语句事实上跳出的是最近的那层循环语句或switch语句

ANSI C引入的另一个新特性是相邻的字符串将被自动合并成一个字符串的约定

//旧风格
printf("1111111 \
2222222 \
333333");
//新风格
printf("1111111"
"2222222222"
"33333333");

新风格可以用一连串相邻的字符串常量来代替它,它们会在编译时自动合并,除了最后一个字符串外,其余每个字符串末尾的的'\0'将会去掉。这种合并意味着字符串数组在初始化时,如果不小心漏掉了一个逗号,编译器将不会发出错误信息,而是悄无声息的把两个字符串合并在一起。

误做之过

C语言的符号重载

C语言运算符优先级存在的问题

x = f() + g() * h()

g()和h()的返回值先组成一个意群,执行乘法运算,但g()和h()的调用可能以任何顺序出现(g的调用不一定早于h),类似,f可能在乘法之前也可能在乘法之后调用,也可能在g与h之间调用。唯一确定的就是乘法会在加法之前执行

有些专家建议在C语言中牢记两个优先级就行了:乘法和除法先于加法和减法,在涉及其他的操作符时一律加上括号。

结合性:它是仲裁者,在几个操作符具有相同优先级时决定先执行哪一个

所有赋值符(包括复合赋值符)都具有右结合性,具有左结合性的操作符如& |

结合性只用于表达式中出现两个以上相同优先级的操作符的情况,用于消除歧义,事实上,所有优先级相同的操作员符,它们的结合性也相同。

gets()函数

gets函数原型:char*gets(char*buffer);//读取字符到数组:gets(str);str为数组名。

gets函数功能:从键盘上输入字符,直至接受到换行符或EOF时停止,并将读取的结果存放在buffer指针所指向的字符数组中。读取的换行符被转换为null值,做为字符数组的最后一个字符,来结束字符串。

注意:gets函数由于没有指定输入字符大小,所以会无限读取,一旦输入的字符大于数组长度,就会发生内存越界,从而造成程序崩溃或其他数据的错误。

fgets函数原型:char *fgets(char *s, int n, FILE *stream);//我们平时可以这么使用:fgets(str, sizeof(str), stdin);其中str为数组首地址,sizeof(str)为数组大小,stdin表示我们从键盘输入数据。

fgets函数功能:从文件指针stream中读取字符,存到以s为起始地址的空间里,知道读完N-1个字符,或者读完一行。

注意:调用fgets函数时,最多只能读入n-1个字符。读入结束后,系统将自动在最后加'\0',并以str作为函数值返回。

          看看这个函数的官方说明:

                       /*** 
                    *char *fgets(string, count, stream) - input string from a stream 
                    * 
                    *Purpose:  
                    * get a string, up to count-1 chars or '\n', whichever comes first, 
                    * append '\0' and put the whole thing into string. the '\n' IS included 
                    * in the string. if count<=1 no input is requested. if EOF is found 
                    * immediately, return NULL. if EOF found after chars read, let EOF 
                    * finish the string as '\n' would. 
                    * 
                    *Entry: 
                    * char *string - pointer to place to store string 
                    * int count - max characters to place at string (include \0) 
                    * FILE *stream - stream to read from 
                    * 
                    *Exit: 
                    * returns string with text read from file in it. 
                    * if count <= 0 return NULL 
                    * if count == 1 put null string in string 
                    * returns NULL if error or end-of-file found immediately 
                    * 
                    *Exceptions: 
                    * 
                    *******************************************************************************/ 

char *fgets(char *s, int n,  FILE *stream)
   {
     register int c;
     register char *cs;
     cs=s;
     while(--n>0 &&(c = getc(stream))!=EOF)
     if ((*cs++=  c) =='\n')
           break;
     *cs ='\0';
     return (c == EOF && cs == s) ?NULL :s ;
    }

少做之过

z = y+++x

程序员的意图可能是z = y + ++x,但也可能是z = y++ + x。ANSI C规定了一种逐渐为人熟知的“maximal munch strategy(最大一口策略)”。这种策略表示如果下一个标记有超过一种的解释方案,编译器将选取能组成最长字符序列的方案,以上这个例子被解析为z = y++ + x

编译器日期被破坏

char * localized_time(char * filename)

{

    struct tm *tm_ptr;

    struct stat stat_block;

    char buffer[120];

    stat(filename,&stat_block);

    tm_ptr = localtime(&stat_block.st_mtime);

    strftime(buffer,sizeof(buffer),"%a %b %e %T %Y",tm_ptr);

    return buffer;

}

  问题就出在函数的最后一行,也就是返回buffer的那行。buffer是一个自动分配内存的数组,是该函数的局部变量。当控制流 离开申明自动变量的范围时,自动变量便自动失效。这就意味着即使返回一个指向局部变量的指针,当函数结束时,由于该变量已被销毁,谁也不知道这个指针所指向的地址的内容是什么。

    在C语言中,自动变量在堆栈中分配内存。当包含自动变量的函数或代码块退出时,他们所占用的内存便被回收,他们的内容肯定会被下一个所调用的函数覆盖。这一切取决于堆栈中先前自动变量位于何处,活动函数申明了什么变量,写入了什么内容等。原先自动变量地址的内容可能被立即覆盖,也可能稍后才被覆盖。

    解决这个问题有几种方案:

    1、返回一个指向字符串常亮的指针;

    2、使用全局申明的数组;

    3、使用静态数组;

    4、显示分配一些内存,保存返回的值(注意内存的释放)

    5、调用者分配内存来保存函数的返回值。为了提高安全性,调用者应该同时指定缓冲区的大小。

    如果可以在同一代码块中同时进行“malloc”和“free”操作,内存管理是最为轻松的
 

你可能感兴趣的:(C语言)