关于C语言12个问题的回复

关于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朋友的评论,不需要再说了,一声感叹:知己啊!

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值