• Expert C Programming 阅读笔记(~CH1)


    P4: 好梗!
    There is one other convention—sometimes we repeat a key point to emphasize it. In addition, we sometimes repeat a key point to emphasize it.
     
    P5:UCB的UNIX文档里,tunefs命令的说明有一个叫Bugs的模块,最后一句吐槽道:

        You can tune a file system, but you can't tune a fish.
        (tunefs = tune a fish = tuna fish,来自于REO Speedwagon的专辑You Can Tune a Piano, but You Can't Tuna Fish的冷笑话,在这里相当于:朽木不可雕也……)

    有人加了一句comment威胁不许改掉这句吐槽不然就会受到诅咒:

        Take this out and a UNIX Demon will dog your steps from now until the time_t's wrap around.

    后来Sun在SVr4 UNIX的manpage里不仅把Bugs模块改称为Notes(你以为这样群众就不把它当槽点了嘛?),还斗胆删掉了这句吐槽,接下来发生的事大家都知道了LOL (Line Printer Daemon躺枪)

    下面的练习围绕这句诅咒里的"until the time_t's wrap around"展开:什么时候time_t会wrap around呢?(相当于问从1970年1月1日零点经过time_t能正确表示的最大数的秒数之后是啥时间,比较常见的是long int,大约是2^31-1,也就是到2038年)
    参考阅读:http://en.wikipedia.org/wiki/Year_2038_problem

    查阅time.h,看到了句

    typedef long time_t;

    参考自己以前做过的探究
          http://www.cnblogs.com/joyeecheung/archive/2012/10/12/2720707.html

    为了方便表示,利用二进制转十六进制4位合并一位的原理,我的电脑的time_t可以表示的最大正整数是
        ((2^32))/2-1 = 01111111111111111111111111111111 = 0x7FFFFFFF

    剩下来的部分和书上的代码差不多啦:D
     

    Notes:
      ctime()接受一个time_t的指针,返回这个time_t对应的以地时区为准的可读字符串;
      gmtime()接受一个time_t的指针,将它转化为以UTC为准的struct tm,返回这个struct tm的指针;
      asctime()接受struct tm的指针,返回一个ctime()返回的那种字符串。
      也就是说,要将一个time_t的秒数转化成一个容易看懂的字符串,如果转成本地时间只要用ctime(...),如果转成UTC则需要asctime(gmtime(...))
      当然,C的库函数还没有碉堡到能依据夏令时调整(何况用夏令时的时段也是不固定的),所以那些有夏令时的国家就……呵呵呵呵呵~~

    programming challenge第二个问题的答案

    #include <stdio.h>
    #include <time.h>
    
    int main(void)
    {
        time_t now;
        time(&now);
        
        time_t biggest = 0x7FFFFFFF;
        
        time_t gap;
        gap = difftime(biggest, now);
        
        struct tm *timeptr = gmtime(&gap);
        printf("%d years, %d months, %d days, %d hours, %d minutes and %d seconds
    ",
            timeptr->tm_year - 70, timeptr->tm_mon, timeptr->tm_mday,
            timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
        printf("before time_t's wrapped around
    ");
        return 0;
    }

    结果是还有24年多,期待2038年XD

    P8:
    UNIX比C语言诞生得更早,最初是用PDP-7的汇编写的,所以不存在先有鸡还是先有蛋的问题。
    UNIX和C语言标准库的时间从1970年算起是因为UNIX在1970年诞生。

    作为一个比起看书更喜欢翘脚看纪录片的懒人看到这里立刻想找UNIX的纪录片来看……似乎没有电视台制作的,AT&T的官方youtube上有一个纪录片,因为语速慢+是美音,用youtube自带的听译字幕效果很不错
    http://www.youtube.com/watch?v=tc4ROCJYbm0

    某个程序猿在被PL/I虐过后怒写打油诗一首:
        IBM had a PL/I / Its syntax worse than JOSS / And everywhere this language went / It was a total loss.

    介绍C语言前世时作者的措辞太碉堡了:
        ...After a brief and unsuccessful flirtation with Fortran, Thompson created the language B...

    P9:

    作者抱着务实求真的八卦态度,深刻批评了那些仅凭一条语句的编译效率就臆测C的++与--是为PDP-11而设计的八卦群众。

    P10:

    PDP-7的数据都以同样的大小存储,所以B语言没有数据类型,大家都是一个word。而PDP-11可以用不同大小的内存存储数据,为了帮助写编译器的人区分整数浮点数等等,C语言才加入了数据类型,这个事后加进去的类型系统没有经过认真的思量,所以它也它支持不同数据类型也能直接赋值这种充满陷阱的操作,不像pascal,python之类的语言那样对数据类型的约束那么强,结果就是:

      To this day, many C programmers believe that "strong typing" just means pounding extra hard on the keyboard.

    (其实我第一次看到strong typing这个词也是这么以为的,你是在逗我呢吧~ =____=|||)

    C语言留下来的大量特性(也影响了很多其他的语言)都是“以编译器作者为本”才创造出来的:
        - ​数列从0数起这个大坑是因为编译器作者们喜欢用offset来定位,这样寻址好算
        ​- 数据类型都是看着能直接对应硬件底层才加进来的,所以没有复数,有浮点数也是因为有了PDP-11的新功能
        ​- 对普通程序员看上去很多余的auto是因为编译器作者们弄symbol table的时候有用才存在的
        ​- 数组约等于指针方便了编译器作者,给普通程序员挖了个坑……
        ​- 早期的C语言里float和double没有区别,写了float也照样双精度,因为PDP-11转换精度很方便,加上某些PDP-11的浮点数硬件是切换模式的,要么就全部单精度要么就全部双精度,当时的UNIX又不怎么用得着float所以干脆都扩展成双精度的算了,省得切换来切换去……(真是懒啊……)
        ​- 不能嵌套函数:为什么?因为这样编译器好写(-__-||)
        - register把分配寄存器给常用变量的活丢给程序员干了(不过好在现在的编译器越来越极品已经可以自动把常用变量放进寄存器,不需要程序员特意注明了……)

    P11:
    为什么在其他语言里runtime support会默默调用,但是C语言一定要自己手动管理内存,检查数组越界,干各种各样的琐事捏?答案很简单——因为琐事都丢给程序员,编译器作者就不用操心了!-____-|||
    出于一样的原因,C语言也不自带I/O,要用库函数(还记得大一C语言考试第一题就是问#include<stdio.h>是否必须,哇哈哈)。最早由Mike Lesk写了个臃肿的I/O,性能不太好,后面经过多次优化才变成现在的样子。讽刺的是Mike Lesk 在自己的书里反而写过

        designing the system so that the manual will be as short as possible minimizes learning effort

    作者表示看到这句话万千问候涌上心头,of which "Bwaa ha ha!" is probably the one that minimizes learning effort  XD

    讲C preprocesser的时候提到了String replacement, of the form "change all foo to baz",觉得应该有什么梗,就在维基查了一下foo和baz

      http://en.wikipedia.org/wiki/Foobar

    虽然经常见到foo不过挺少见到其他的。原来播放器的foobar名字是这么来的(也就是……乱起的忘改了……)

    P12
    如何把C语言从表面上改造成另一种语言……IOCCC就是这么来的:D

    P15
    为什么缩写是b+=3而不是b=+3?本来B语言的词法分析器是用=op比较方便的,但是容易搞混,比如b=-3和b= -3,多一个空格照样编译通过,但是差了十万八千里。改过语法之后某些formatter见到了=op就改成op=拿去编译,结果像a=!b就被改成了意思差十万八千里的a!=b……

    教训就是,一个formatter应该乖乖修改whitespace,别没事干去乱动代码……
    为了避免这个bug,一个解决办法是在运算符后面加个空格,可能这也是现在常见的编程风格要求双目运算符两边加空格的由来之一……(不过加上空格本身确实能提高可读性)

    P16
    为了处理8086不规则的芯片架构,微软搞了一个自己的编译器,加入了far和near之类的关键字。在ANSI C制定的时候IBM PC非常流行,所以小组成员对要不要加这些东西争论了很久,最后他们决定it was undesirable to mutate the language to cope with the limitations of one specific architecture.

    P17

    ——到底是ANSI C还是ISO C?

      ANSI弄好标准后,被ISO修改并采用了,然后ANSI又用ISO修改后的版本代替了自己最初的标准,所以现在的标准是ISO确定的,应该叫它ISO C,虽然大家还是叫ANSI C的多,因为早在ISO WG14成立前ANSI C都就已经叫开了,群众都已经习惯了。不过作者表示叫它ANSI C也是appropriate的,因为最后敲定标准的时候ISO和ANSI都有出力。
    标准实体书这个玩意果然无论在什么行业什么国家都是很贵的 = =b 不过作者写书的年代网络盗版还没这么流行,现在ISO/IEC 9899-1990的电子版已经很好找了,尤其是在不知版权为何物的兔子or毛熊国


    作者表示有八卦传闻说要给C加上复数类型,不过今天看来是没实现……

    P19
    按照标准,只要不违反明文规定的constraint,编译器都可以不警告你,即使只是undefined,这时编译器想干啥都可以,也就是所谓的implementation-defined的行为
    Useful rule from Brian Scearce — if you hear a programmer say "shall" he or she is quoting from a standard. :D

    P21
    其他语言都在对语言特性的最大值作规定,但ANSI C却净是对最小值的规定,奇葩的是,这些规定又没有被定性为constraint,所以就算编译器达不到要求,照样可以不给警告……
    A really good implementation won't have any preset limits, just those imposed by external factors.

    P24
    - K&R C中并没有prototype。ANSI C从C++借鉴了这个特性。
    - 对于一个prototype来说,parameter的信息比它的名字和return value type更重要(这也是为什么用parameter和名字组成signature而return value type在其中不起作用)。
    - 有了prototype,编译器可以在编译时就检查参数传递是否正确,而没有prototype就只能等到连接的时候再检查甚至直接被忽略了。同时function header的形式也发生了改变。
    - 最好不要删除prototype中parameter的名字,以便为使用者提供更多的信息
    - 最好给所有新东西都加上个prototype以防万一

    P26
    language lawyer :
        a person who will show you the five sentences scattered through a 200-plus-page man ual that together imply the answer to your question 'if only you had thought to look there.'
                —— The New Hacker's Dictionary

    P27
    从这里开始各种绕晕…………=_____=|||||

    const char * = char *的赋值成立的原因:
        左边指向的是const char,即被指的类型是qualified by const;
        右边指向的是char,即被指的类型是unqualified;
        这时候符合ANSI C 6.3.16.1描述的情形:
            type pointed to by the left has all the qualifiers of the type pointed to by the right
        注意qualified by const = qualified by const + unqualified,1 = 1 + 0, 有包括没有

    const char ** = char **的赋值不成立的原因:
        首先根据ANSI C 6.1.2.5,const char *本身是unqualified的,即使它指向的是qualified by const
        左边指向的是const char *,故被指的类型是unqualified
        右边指向的是char *,同样也是unqualified
        两边的指针均指向unqualified,而两个unqualified的被指内容又不是一个类型,即具有不同的qualifier,所以不符合ANSI C 6.3.16.1 ,不能赋值。

    根本原因是6.3.16.1 对指针之间赋值的合法性只看下一级,再往下的不管关系多亲都没用,也就是Compatibility of pointer types is not transitive
    当然这个混乱的约定并不是所有编译器都在遵守,有的执行得比较灵活。

    嗯,没事干研究这种东西的人确实担得起language lawyer的美名……

    P27
    const并不能把variable变成constant,它还是一个variable,只是限制你不能用它通过赋值来修改变它的值,variable在内存里该放哪它还是放哪。给人缠上防护绷带,不代表他们就变成了木乃伊。

    最常见的用法:const xx *实现安全高效的call-by-reference

    一个const int *可以指向一个普通的int,被指的变量类型不会变(不会进化成const int),指针本身的意思也不会变——你不能通过这个指针去修改它指的东西。
    当然,你不能用这个指针修改它指的东西,不代表你不可以用其他方式(比如用另一个普通指针)去修改它指的东西。假如有个神经病不许你用vim改文件,只许你用vim看文件,你一样可以用emacs改了它,然后用vim阅读修改后的文件。

    const只是一个符号,它不会改变什么实质性的东西,只是在一个特定范围加个约束而已。所以不要真心去指望一个const int *指针dereference后得到的还是原来的内容,靠不住哇。

    P28

    In retrospect, the const keyword would have been better named readonly.

    ANSI委员会再次被嘲笑……

    ANSI C的promotion规则简而言之就是:
        ​Operands with different types get converted when you do arithmetic. Everything is converted to the type of the floatiest, longest operand, signed if possible without losing bits.


    在不同整型的正负数同时出现时,ANSI对较小的整型的处理比K&R更可靠。两者都有一个易见的BUG:只要有一个unsigned int,如果旁边还跟着一个负数,那就要遭殃。解决方案是把那个unsigned int转化成int再来进行运算。而最好的方案是尽量别用任何unsigned类型,世界就清净了……
    一般可以安全用unsigned的场合是用二进制的时候,不然即使你要表达的数全是正数,只要signed的容量够,也没必要硬用unsigned。

    P31
    算数组元素数量:
      用​sizeof(array) / sizeof(array[0])
      不用 sizeof(array) / sizeof(int)
    这样万一改了array的类型,可以少一份工

    P32
    有位gcc的开发者曾经强烈抵制过#pragma,并且将ANSI所谓的implementation-defined发挥到了极致:在gcc 1.34的手册中写明,如果喂进去一段有#pragma的代码,它不但不会编译,反而会试图启动游戏rogue,如果不行就启动游戏hack,再不行就打开emacs放汉诺塔,全都不行了也要报个fatal error,这是多大仇……

    作者还无比充满八卦精神地附上了相应的preprocessor代码……“乃要implementation-defined, 我就define给你看”,这开头的注释是有多幼稚啊摔!

    /*
    * the behavior of the #pragma directive is implementation
    defined.
    * this implementation defines it as follows.
    */


    身为八卦表率的作者又不忘敬业地指出,这版的手册写错了,应该是先试打开hack再试打开rougue……=__=b

  • 相关阅读:
    python迭代器
    初识html
    跨域(jsonp)方法
    闭包
    pycharm软件配置
    插槽slot
    git常用操作
    在mac中使用Charles抓包操作
    防止网页被嵌入框架
    H5唤起APP
  • 原文地址:https://www.cnblogs.com/joyeecheung/p/3244953.html
Copyright © 2020-2023  润新知