• C专家编程 第2章 这不是Bug,而是语言特性 2.2 多做之过


    /*多做之过就是指语言中存在某些不应该存在的特性 
     *例如容易出错的switch语句,相邻字符串常量的自动连接,缺省全局作用域 
     */
    /*由于存在fall through, switch语句会带来麻烦 
    //1.
    switch(表达式) {
        case 常量表达式:零条或多条语句
        default: 零条或多条语句
        case 常量表达式:零条或多条语句 

    /*每个case结构由3个部分组成:关键字case;紧随其后的常量值或常量表达式;在紧接
     *一个冒号。当表达式的值与case中的常量匹配时,该case后面的语句就会执行。default(如
     *果有的话)可以出现在case列表的任何位置,它在其他的case均无法匹配时被选中执行。如
     *过没有default,而且所有的case均不匹配,则整条switch语句便什么也不做。
     */ 
    /*在C语言中,几乎从来不进行运行时错误检查---对进行解除引用操作的指针进行有效
     *性检查大概是唯一的例外,而且在MS-DOS系统里甚至连这点很有限的检查都无法保证。 
     */
    /*MS-DOS的运行时检查*/ 
    /*在所有的虚拟内存体系结构中,一旦一个指针进行解除引用操作时所引用的内存地址
     *超出了虚拟内存的地址空间,操作系统会中止这个进程。
     *MS-DOS并不支持虚拟内存,即使内存访问失败,它也无法立即捕捉到这种情况
     *MS-DOS可以在程序结束之后检测解除引用空指针的情况
     *具体方法是在进入程序前,保存内存地址为零时存储的内容。在程序结束时,系统
     *检查这个地址与原先的是否相同。如果不同,基本可以肯定你的程序使用了空指针来访问
     *内存,运行时系统会打印一条“null pointer assignment"(空指针赋值)信息
     */
    /*运行时检查与C语言的设计理念相违背。按照C语言的理念,程序员应该知道自己正在干什么,
     *而且保证自己的所作所为是正确的
     */ 
    /*各个case和default的顺序可以是任意的,但习惯上总是把default放在最后。 
     *一个遵循标准的C编译器至少允许一条switch语句中有257个case标签,
     *这是为了允许switch满足一个8比特字符的所有情况(256个可能的值加上EOF)
     */
    /*switch存在一些问题,其中之一就是它对case可能出现的值太过于放纵了。 
     *例如,可以在switch的左花括号之后可以声明一些变量,从而进行一些局部存储的分配
     *在最初的编译器里,这是一个技巧---绝大多数用于处理任何复合语句的代码
     *都可以被复用,可以用于处理switch语句中由花括号包住的那部分代码。
     *所以在这个位置上声明一些变量会被编译器很自然地接受,尽管在switch语句中
     *为这些变量加上初始值没有什么用处,因为它绝不会被执行---语句从匹配表达式
     *的case开始执行
     */ 
    //2.
    /*小启发*/
    /*需要一些临时变量吗?把它放在块的开始处*/
    /*在C语言中,当建立一个块时,一般总是这样开始的:
     *{
           语句 
     *你总是可以在两者之间增加一些声明,如:
     *{
         声明
         语句
     *当分配动态内存代价较高时,你可能会采用这种局部存储的方法,但有可能的话要
     *尽量避免。编译器可以自由地忽略它,它可以通过函数调用来分配所有局部块需要
     *的内存空间。
     *另一种用法是声明一些完全局部于当前块的变量
     */    
    /*声明一些完全局部于当前块的变量 */
    if (a > b) 
    /*交换a, b*/ 
    {
        int temp = a;
        a = b; 
        b = temp;
    }
    //C++在这方面又进了一步,允许语句和声明以任意的顺序交叉出现,甚至允许变量的声明出现在for表达式的内部 
    for (int i = 0; i < 100; i++) {
        ...
    }

    /*switch的另一个问题是它内部的任何语句都可以加上标签,并在执行时跳转到那里,这就有可能
     *破坏程序流的结构化
     */ 
    switch(i) {
        case 5 + 3: do_again;
        case 2: printf("I loop unremittingly\n"); goto do_again;
        default: i++;
        case 3: ;
    }
    //3.
    /*顺便提一句,在C语言中,const关键字并不真正表示常量*/ 
    const int two = 2;
    switch(i) {
        case 1: printf("case 1\n");
        case two: printf("case 2\n"); 
        //**error** ^^^integral constant expression expected 
        case 3: printf("case 3\n");
        default: ;
    }

    /*switch语句最大的缺点是它不会在每个case标签后面的语句执行完毕后自动中止。
     *一旦执行某个case语句,程序将会执行后面所有的case,除非遇到break语句
     *"fall through"意思是:如果case语句后面不加break,就依次执行下去,以满足某些
     *特殊情况的要求。 
     *实际上,这是一个非常不好的特性,因为几乎所有的case都需要以break结尾。
     *大部分lint程序在发现“fall through”情况时会发出警告信息。 
     */ 
    switch(2) {
        case 1: printf("case 1\n");
        case 2: printf("case 2\n");
        case 3: printf("case 3\n");
        case 4: printf("case 4\n");
        default: printf("default\n");
    }

    /*软件信条*/
    /*缺省采用“fall through”,在97%的情况下都是错误的*/
    /*在编译可能具有一个或两个操作数的操作符时:*/ 
    switch(operator->num_of_operands) {
        case 2: process_operand(operator->operand_2);
            /*fall through*/
        case 1: process_operand(operator0->operand_1);
        break;
    }

    //4.
    /*switch的另一个问题---break中断了什么*/
    /*它证明了在C语言中,人们太容易低估break语句对控制结构的影响*/ 
    network code() {
        switch(line) {
            case THING1:
                doit1();
            break;
            case THING2:
                if (x == STUFF) {
                    do_first_stuff();
                    if (y == OTHER_STUFF) {
                        break;
                    }
                    do_latter_stuff();
                } /*代码的意图是跳到这里...*/ 
                initialize_modes_pointer();
                break;
            default:
                processing();
        } /*...但事实上跳到了这里*/
        use_modes_pointer(); /*致使modes_pointer未初始化*/ 
    }
    /*break语句事实上跳出最近的那层循环或switch语句*/ 

    //5.
    /*粉笔也成了可用的硬件*/
    /*ANSI引入的另一个新特性是“相邻的字符串常量将被自动合并成一个字符串。
     *这就省掉了过去在书写多行信息时必须在行末加“\”的做法,后续的字符串可以
     *出现在每行的开头
     */
    /*旧风格*/ 
    printf("A favorite children's book \
    is 'muffy Gets It: the hilarious table of a cat, \
    a boy, and his machine gun");
    /*现在可以用一连串相邻的字符串常量来代替它,它们会在编译时自动合并。除了最后
     *一个字符串外,其余每个字符串末尾的'\0'字符会被自动删除
     */
    /*新风格*/ 
    printf("A favorite children's book "
    "is 'muffy Gets It: the hilarious table of a cat, "
    "a boy, and his machine gun");

    /*Crayon 粉笔
     *automated generation 自动生成
     */ 
    /*然而,这种合并意味着字符串在初始化时,如果不小心漏掉了一个逗号,编译器
     *将不会发出错误信息,而是悄无声息地把两个字符串合并在一起。
     */ 
    char *available_resources[] = {
        "color monitor",
        "big disk",
        
        "Cray" /*哇!少了个逗号*/
        "on-line drawing routhiness",
        
        "mouse",
        "keyboard",
        "power cables", /*这个多余的逗号会引起什么问题吗?*/ 
    };
    /*顺便提一句,最后那个字符末尾的逗号并不是打字错误,而是从最早的C语法中继承下来的东西
     *,不管存在与否都没有什么意义。ANSI C Rationale对它进行了辩护,称它使C语言在自动生成
     *(automated generation)时更容易一些。我想,这种拖尾巴如果在其他由逗号分割的列表(如
     *枚举声明、单行多变量声明等)中也允许使用,那还说得过去,可惜事实并非如此
     */ 
    //6.
    /*小启发*/ 
    /*使一段代码在第一次执行时的行为与以后执行时不同
     *这种方法能使分支和条件测试减少到最小程度
     */ 
    void generate_initializer(char *string)
    {
        static char separator = ' ';
        printf("%c %s\n", separator, string);
        separator = ',';
    }

    //7.
    /*太多的缺省可见性*/ 
    /*定义C函数是,在缺省情况下函数的名字全局可见的,跟加一个冗余的
     *extern关键字效果是一样的。这个函数对于链接到它所在的目标文件的任何东西都是可见的。
     *如果想限制对这个函数的访问,就必须加static关键字 
     */ 
    /*interpositioning就是用户编写和库函数同名的函数并取而代之的行为*/
     
    function apple(); {/*在任何地方均可见*/}
    extern function pear(); {/*在任何地方均可见*/}
    static function trunip() {
        /*在这个文件之外不可见*/ 
    }
    /*根据实际经验,这种缺省的全局可见性多次证明是个错误,这已是盖棺定论。软件对象在
     *大多数情况下应该缺省地采用有限可见性。当程序员需要让它全局可见时,应该采用显式的手段
     */
    /*这种太大范围的全局可见性会与C语言的另一个特性相互产生影响,那就是interpositioning。
     *interpositioning就是用户编写和库函数同名的函数并取而代之的行为。
     */ 
    /*作用域过宽的问题常见于库中:一个库需要让一个对象在另一个库中可见。
     *唯一的办法是让它变得全局可见。但这样一来,它对于链接到该库的所有对象
     *都是可见的了。这就是all_or_nothing---一个符号要么全局可见,要么对其他
     *文件都不可见
     *Pascal中那样在一个函数内部嵌套另一个函数的定义
     *Ada和Modula-2就是在各个程序单元中明确说明那些符号是引入的,那些是引出的 
     */ 
     

  • 相关阅读:
    Docker学习笔记
    前端数字计算精度问题
    产品经理如何独立从0-1着手甲方项目,或者负责一个产品?
    [从零开始学习FPGA编程-56]:视野篇-常见概念:chip(芯片)、chipset(芯片组)、chiplet(芯粒)、die(裸片)的区别
    角色扮演?一款跨平台可移植开源游戏
    史上最全的mysql数据类型汇总-(上)
    图像分割 - 分水岭算法
    RabbitMQ------延迟队列(整合SpringBoot以及使用延迟插件实现真正延时)(七)
    【CF780G】Andryusha and Nervous Barriers 题解
    EasyCVR及智能分析网关在校园视频融合及明厨亮灶项目中的应用方案设计
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126070937