关于C语言12个问题,我在帖子上回答了部分朋友。现在又看到了一些朋友的评论 ,在此一起答复。
[Quote=引用 15 楼 heguodong 的回复:]
8.
if ( ch == 1 ) {
..........
}
if ( ch == 2 ) {
...........
}
if ( ch == 3 ) {
..........
}
这类问题最需要处理的以多态取代条件式子,或者用状态取代这种魔法数
[/Quote]
答复:这位朋友是说是用宏名称代替程序中的常量魔数吧?!这当然是个很好的建议。不过,这个问题的核心是流程方面的。至于多态......? 我们这里是C语言啊,不是C++。
[Quote=引用 18 楼 mymtom 的回复:]
1.
if ( strlen(s) == 0 )
if ( strlen(s) )
if ( strcmp(s, “”) == 0 )
改善:
# define isNotEmpeyStr(s) ( (s)[0] != ‘/0’ )
# define isEmptyStr(s) ( (s)[0] == ‘/0’ )
这个,根本不是问题,21世纪的编译器编译出来的代码不会有问题。
在不打开优化的情况都都没有去调用strlen,strcmp
2.
memcpy(s1, s2, 1);
对于一个字节的拷贝,竟然也调用memcpy。看来,C语言可以删除赋值的语法功能了。
改善:
s1[0] = s2[0];
gcc 4.x.x 在-O1的情况下,不会调用memcpy,直接赋值。
[/Quote]
答复mymtom朋友: 你那么迷信编译器的优化?! 不优化的程序,编译器都能优化了,那么优化的程序是不是会更优化了呢? 比如上面那个strlen就算是被编译器优化成指令而没有调用实际的函数,那也应该是 repn及scans这样的循环及判断测试的指令吧?! 而我改善后的只是一句判断指令,你说那个更优化啊? 我们自己可以不费什么力,只需要有一点儿概念,就可以优化,为什么要指望着编译器的优化.
C语言是程序员的语言。它的特点就是将很大的一部分的决定权交给了程序员。而有些自做聪明的优化显然违背了这个宗旨。C语言的语法特别的简单,而它面向函数的特点,以及大量标准库函数的支撑,使得它的功能仍像其他高级语言一样强大。但它的最大特点就是,它认为程序员是聪明的。有些傻瓜一样的优化,大概是给不想聪明的程序员使用的。专业程序员不应该指望着编译的优化,应该在任何编译器和条件下,都写出优雅高效的代码。
C语言的标准库,理论上来说,是不属于C语言的语法部分的。而自做聪明的优化编译,却生生将标准库函数优化掉了,是对语言本身的违背。我曾经有特殊要求,另外改写了一部份标准库的内容,好像就是这个strlen。结果,总是不会调用我写的函数。使用汇编一看,发现被优化掉了,感觉这样的行为太流氓了。我和你相反,讨厌这样的优化。
至于你说的21世纪的编译器都会那么优化,我不知道是哪里的信息。据我所知,一些优秀的编译器都没有这样的优化。比如: HP aCC, AIX xlC等。当然,也许有优化的选项,我没有仔细研究过。我只发现:gcc优化的东西是最多的。看优秀的代码是可以增加代码的美的感觉的。我看遍了标准C库的源代码,从没有发现会有上面的 strlen(s) == 0 类似的语句的。所谓根本不是问题,当然,如果你不追求完美和专业的话,确实根本不是问题。
[Quote=引用 19 楼 mymtom 的回复:]
3.
for ( i = 0; i < strlen(s); i++ )
每次循环的循环判断中都有strlen的函数调用,显然降低效率。
改善如下:
len = strlen(s);
for ( i = 0; i < len; i++ )
这个很更有意思了,编译器不会把strlen(s)的值放在寄存器里?
当然前提是你在循环中没有改变s,
[/Quote]
答复:这个更有意思?我看这位朋友更有意思。一面埋怨没有将strlen的返回值优化到寄存器里,一面又提出了循环中s不能改变的前提。那你到底是想让编译器怎么做啊?难道是让它扫描整个循环体,看s是否变化,再做优化。还是上面的回答:专业程序员会写出本身就优化的代码,而不会指望着编译器的优化。尤其是专业的C程序员。
[Quote=引用 20 楼 mymtom 的回复:]
7.
sprintf(curtime, "%02d:%02d:%02d:%03d", tt->tm_hour, tt->tm_min, tt->tm_sec, tb.millitm);
curtime[12] = 0;
写这个程序段的人很小心,已经习惯了。这个没有坏处,如果不信,请看:
cat mymtom.c&& cc mymtom.c-o mymtom&& ./mymtom
#include<stdio.h>
int
main(void)
{
char buf[16];
(void)sprintf(buf,"%02d:%02d:%02d:%03d",1,2,999,888);
(void)printf("%s/n", buf);
return 0;
}
01:02:999:888
[/Quote]
答复:你这叫欲加之罪,何患无辞。为什么一定定义 buf为16个字节啊? 在这个问题上,我没有给出curtime的空间的长度,因为我提出的这个问题的核心不是空间溢出的问题。实际的 curtime 定义为1024个字节。假如,如你所说,输出的内容超出了12个字节,那么 curtime[12] = 0 岂不是截掉了内容更加错误了。当然,也许你认为缓冲区溢出是最大的错误。凭我的经验,并非如此。缓冲区溢出,会让程序异常退出,并且可以留下内存映像信息供调试,而那种逻辑错误可常常是隐藏的定时炸弹。至少你的例子太没有说服力了。当然,我理解你的写程序的小心。在这之前,也有朋友提出前面的有个问题memset是必须的。我曾经经历过一个项目,被要求在所有的函数前进行变量空间的初始化。于是一大堆的memset出现在每个函数的最前面。而我这个混了十几年的老家伙被判定为不合格的程序员,理由之一,就是不遵守初始化的规定。我为此专门写了一篇文章: 庸余的语句和初始化 ---- 不成熟的C程序员的标志。
一个int val;
我根据程序的含义,设 val = DEFAULT_VALUE; 我认为就是初始化了,但有人就是一定要写:
val = 0;
val = DEFAULT_VALUE;
尤其争议的是字符串问题,
char sbuf[128];
我认为,sbuf[0] = '/0' 或者直接写上 strcpy(sbuf, DEFAULT_STR); 就是初始化了,但一定却要在这之前多些一个:
memset(sbuf, 0, sizeof sbuf);
我把这个现象归纳为幼稚程序员字符串恐惧病。在他们来看,C语言的字符串并非仅仅是以'/0'来结尾的,紧接它后面的内存空间应该都是'/0'。我在那篇文章里,还指出了由于过度初始化,导致的程序的隐患和错误。等我找到那篇文章,我会再贴出来。
看优秀的代码可以增加我们的程序的美感。你可以看一下标准C库的代码,虽然由于它的平台的通用性和一些效率的需要,显得代码有些复杂,但你绝不会发现它有任何庸余的代码和初始化。每个语句都显得简练而有效,绝不拖泥带水。
另外,在上面的例子里,我发现你写的语句:
(void)sprintf(......);
前面的(void)加的好,我比较欣赏。在实际程序中,我都很难做到。从这方面来说,这位朋友倒是显得很专业。
[Quote=引用 22 楼 udsking 的回复:]
亲爱的udsking朋友比较谦虚:发表一下个人的拙见!欢迎楼主指导
本楼主就不谦虚了,给你一下指导。由于你的内容太长了,咱们一段一段来:
1.
# define isNotEmpeyStr(s) ( (s)[0] != ‘/0’ )
# define isEmptyStr(s) ( (s)[0] == ‘/0’ )
这样可以用上面两个宏来判断字符串是否为空,或者字符串是否不为空。
对于
char *str = (char*)malloc(3);
str[0] = 0x00;
str[1] = 'a';
str[2] = 'b';
我想你这个宏是判断不出来的吧?我只想说,并不是起始字符是0的就一定是空字符串。
答复:这个问题有些幽默了。我都快不知道怎么指导了。我们的问题是改善代码。当然,改善后的功能不能改变。现在就假设有你那个str。
if ( strlen(str) == 0 ) 的判断是否成立呢?
if ( str[0] == '/0' ) 的判断是否成立呢?
你把上面两个语句写个程序试一下吧,看看结果是否相同。要是不同的话,恐怕得麻烦你把你的代码发给我,我才能解答了。
您不会像我前面说的,认为C字符串的末尾后面不仅是一个末尾'/0', 而是其后面的内存空间全都是'/0'吧?!
我也只想说:起始字符是0的就是空字符串。
2.
memcpy(s1, s2, 1);
对于一个字节的拷贝,竟然也调用memcpy。看来,C语言可以删除赋值的语法功能了。
改善:
s1[0] = s2[0];
不同编译器有自己不同的优化方法。
3.应该和2是相同的问题,编译器可能会对其进行优化的,要不也不可能会有volatile变量了吧。
答复:又是编译优化的问题,请见上面的有关答复。再重复一遍:专业的优秀程序员会写在任何条件和编译环境下的优雅高效的代码,绝不指望着编译器的优化。
4.
printf("OK/n");
对于没有格式控制的输出,最好使用puts,fputs等替代。这样有更高的效率。
puts("OK");
我没跟puts和printf里面具体汇编指令是什么,但这两个语句汇编代码是一样的,
printf("OK/n");
0040F538 push offset string "OK/n" (00420020)
0040F53D call printf (004011a0)
0040F542 add esp,4
puts("OK");
0040F545 push offset string "OK" (0042001c)
0040F54A call puts (004010b0)
0040F54F add esp,4
答复:幸亏你把汇编代码贴出来了,要不还真不知道怎么个一样法。我可看到了前面有call printf 而后面的是call puts。这怎么是一样的呢? 你所说一样就是说它们都一样是三个汇编语句吗?假如有一个死循环的函数: dead(const str* ), 然后,你编译一个对它调用的语句:
dead("OK");
汇编代码也一定是类似上面的三个语句。但是,运行的结果是一样的吗?这个语句运行起来可就是死循环了啊。
[/Quote]
[Quote=引用 25 楼 tkminigame 的回复:]
觉得最后一个这样写更高效
char* ltrim(char *str)
{
while( *str == ' ' )
str++;
return str;
}
[/Quote]
答复tkminigame朋友:你这个语句想法当然很好。但是,我们改善原代码的前提是不应该更改原来的功能。原来是修改str的内容而删除前置的空白。而你这个代码却是返回第一个非空白的字符的位置,所以应该说是更改了原来的功能了。如果是如你这样的用法,最好参数以const修饰:
const char * ltrim(const char *str);
当然,这样返回的字符串是常量的字符串,不能修改。但往往这样用法都是将返回的字符串拷贝到其他的空间来使用的。所以,使用常量更好一些。
答复 kingfox:
一路看下来,到了kingfox这里。发现,已经替我回答了上面朋友的三个问题。感谢kingfox朋友,这个名字就够可爱的。不过,我不能同意你说编译器没有优化,就嫌它笨的态度。那个strlen语句是很难优化的,连十分崇尚优化的上面的mymtom朋友都为难了,不知道该怎么解决strlen中的s的内容如果在循环体内变化的情况。如果编译器太聪明了,那你就会变笨了。还是让自己聪明一些更好。多有知情权和主动权不是更自由吗? 通常编译优化才会让人一头雾水的.
总的来说,评论的很好,感谢亲爱的kingfox朋友.
[Quote=引用 28 楼 mu_yang 的回复:]
把代码写的乱七八糟
然后把一起交给编译器打理
指望编译器优化出一个好的程序
我不能认同这种程序员
[/Quote]
答复:最后看到了mu_yang朋友的评论,不需要再说了,一声感叹:知己啊!