第四篇 从goto 说起 补充后续内容

中山野鬼 发布于 2012/10/28 02:50
阅读 3K+
收藏 38
那么我们也可多此一举的使用C语言提供的跳转命令 goto来模拟上述的if else动作。使用方法其实很简单,如同上述的 b,而且是无条件的跳转。当然和汇编有点相同的是,需要告诉编译器跳转到什么位置。由此,一个新的名词,标号,需要介绍。标号的方法是定义一个标号名,随后跟上一个冒号如下
    LABEL_NAME :
    这句话本身没有逻辑意义。只有当存在明确的 goto后,才被附加了逻辑含义。如同这是一个坑,并没有什么意义,天下之大,到处都是坑。那么当有个厕所指示牌指向它时,就不一样了。当然这个坑仍然是坑,不过是个应急坑了。方法即
    goto LABEL_NAME;
    也即,goto 后面增加莫个标号,随后再增加分号。
    则上述ifelse可以如下写
void model(int flag){
     if (flag != 0){
         goto LABEL_param_exit_called_mode;
     }
LABEL_param_param_called_model :
    param_done();
    goto LABEL_return_model;     
LABEL_param_exit_called_model:
    param_exit();
LABEL_return_model:         
     return;
 }     

     此时对model.c进行编译(记得加上-Wall),你会发现有一个错误和两个警告。
     警告是 LABEL_param_exit_called_model 这个标号没有被使用过。
     错误是 LABEL_param_exit_called_mode 没有被标记,显然没标记,你让编译器goto到哪?
     就是再眼花,现在你也能看出上面的错误,是笔误,model写成了mode。这里强调了warning的价值,关注每个warning对于你发现现在的编译错误的BUG和潜在的逻辑错误有很大的帮助。
 鬼话:原则上,C语言设计,无论工程多大,都不应该有warning。你可以通过 参考文献三查找到我们在gcc编译时,如何使用开关来屏蔽某些warning。但慎用。对于warning应当像对待error一样重视。
     此处给出参考文献 4 ,gcc 的 manual 下载地址为
     http://gcc.gnu.org/onlinedocs/
     你可以找到诸如 GCC 4.7.2 Manual 的链接。下个PDF挺好。哈。
     对应的gcc.pdf,我们是需要查找对warning相关的参数信息,则你在目录中,可以发现有 GCC Command Options,对应该章节,你可以发现有 Options to Request or Suppress Warnings,即对应的3.8 。拜托,诸位的英语应该比我好,所以以后我就不再介绍如何从文献中检索资料的方法了。
     在3.8中,你可以发现有-Wall的错误。
 鬼话:虽然原则上 C代码,很少没有warning。但还是坚持把warning如同error。想办法清楚掉warning。
     例如,LABEL_param_param_called_model这个标号,如果你坚持需要它存在,或许以后的逻辑需要使用,那么你可以做一个无效代码,如下
 
void model(int flag){
     if (flag != 0){
         goto LABEL_param_exit_called_mode;
     }
LABEL_param_done_called_model :
    param_done();
    goto LABEL_return_model;     
LABEL_param_exit_called_model:
    param_exit();
LABEL_return_model:         
     return;
     goto LABEL_param_done_called_model ;// no meaning, only hiding LABEL_param_called_model warning
 }     

     这样写,首先是废话,因为新加的代码,永远不会被执行掉。在O2编译下,还会被抹去。这类代码,我个人的描述为,注释代码,虽然不是注视,但是这种没有意义的代码对理解代码本身,或描述逻辑有帮助,同时这类代码由于没有意义,但可以屏蔽掉一些warning,则还是有存在的价值的。
     例如有一天,你想补充这里的逻辑,想跳转到param_done()位置, 标号的设计,是有统一规范的(谁规范,不是你,就是你的领导),你完全可以提前写上,无需真实使用时再上下找,应该使用什么规则。同时后面的注释也表明,到目前位置,这个标号还空着呢,大爷的,还没对这个标号的价值有所体现,你这个设计师是不是漏了什么?因此后面我的英文注释 no meaning并不是废话。
     展开讨论下标号的命名规则,和存储空间,函数名一样,唯一的根本规则就是没有规则。但从设计习惯上,每个开发团队有自己的风格。就我的风格是 前面一定是 LABEL大写。因为汇编写多了,函数起始也是标号,这种非函数标记的标号要有所区分。
     随后无论小写还是大写,说明清楚这个位置的逻辑含义,如同 call param_done这个函数。最后,再写出当前这个标号所在的函数名。你觉得复杂,无所谓,我说了。没有规则,但我习惯我的规则,而且回避了很多开发问题,此处不一一列举,学不学我的风格,我不介意。
     现在我们反汇编一下,后续不再解释,任何反汇编,实际就是如本文的针对ARM的交叉编译。我们获得汇编如下:
00000028 <model>:
  28:    b580          push    {r7, lr}
  2a:    b082          sub    sp, #8
  2c:    af00          add    r7, sp, #0
  2e:    6078          str    r0, [r7, #4]
  30:    687b          ldr    r3, [r7, #4]
  32:    2b00          cmp    r3, #0
  34:    d102          bne.n    3c <model+0x14>
  36:    f7ff ffe3     bl    0 <param_done>
  3a:    e002          b.n    42 <model+0x1a>
  3c:    bf00          nop
  3e:    f7ff ffe9     bl    14 <param_exit>
  42:    f107 0708     add.w    r7, r7, #8
  46:    46bd          mov    sp, r7
  48:    bd80          pop    {r7, pc}
  4a:    bf00          nop     
    对比一下,有啥结论?无非多了两个nop,nop汇编表示什么事情不干。因此结论就是C代码设计,多次一举。正常,此处只是说明if语句的跳转方式。     
     
    那么对于for呢?我们在回顾一下摸一下的故事。
    for (黑白写0; 老板判断黑板是否小于100;老板在黑板上加1){
摸狗一次;
}
    为了简化for摸狗一次我们修改model.c文件如下
#include <stdio.h>
static void param_done(void){
    printf("param_done func !\n");
    return;
}
static void param_exit(void){
    int i;
    printf("param_exit func !\n");
    for (i = 0; i < 100 ; i++){
        printf("touch the dog! \n");
    }
    return;
}

void model(int flag){


     if (flag){
         param_done();
     }else{
         param_exit();//printf("model func flag == 0\n");//此处增加一个判断测试点
     }
         
     return;

 } 

 修改attr.c如下
 
#include <stdio.h>
#include <string.h>


#include "view.h"
#include "control.h"
#include "model.h"
int main(int argc ,char *argv[]){

    FILE *f = 0;
    int view_mode = 0;
    int control_mode = 0;
    if (argc < 2){
        printf("please enter the pathname !\n");
        return 1;
    }
    control_mode = ((f = fopen(argv[1],"rt")) != 0);
    if (control_mode){
        strcpy(filename,argv[1]);
        fclose(f);
        view_mode = 0;
    }else{
        filename[0] = 0;
        view_mode = 1;
        strcpy(v_param,argv[1]);
    }

    control(control_mode);
    
        model(0);
        view(view_mode);
    
    return 0;
} 

    使用gcc编译,链接。运行
    ./attr ./attr.c1 (确保rm 掉了attr.c1 ,使得该文件不存在)
    此时,你会发现有很多printf信息,touch the dog!。证明目前我们for确实运转良好。
    那么反汇编,param_exit函数如下    
00000014 <param_exit>:
  14:    b580          push    {r7, lr}
  16:    b082          sub    sp, #8
  18:    af00          add    r7, sp, #0
  1a:    f240 0000     movw    r0, #0
  1e:    f2c0 0000     movt    r0, #0
  22:    f7ff fffe     bl    0 <puts>   //到此为止,是打印函数测试点
  26:    f04f 0300     mov.w    r3, #0 //for 中的 i = 0,i的存储位置为r3这个寄存器
  2a:    607b          str    r3, [r7, #4]  //把i这个值保存到堆栈中,
  2c:    e009          b.n    42 <param_exit+0x2e> //跳一下到 42(16进制)位置
  2e:    f240 0000     movw    r0, #0   //循环在这里开始 哦 。。。
  32:    f2c0 0000     movt    r0, #0
  36:    f7ff fffe     bl    0 <puts>
  3a:    687b          ldr    r3, [r7, #4]  //取出i
  3c:    f103 0301     add.w    r3, r3, #1 //加一下
  40:    607b          str    r3, [r7, #4]  //保存起来
  42:    687b          ldr    r3, [r7, #4]  //2c位置会跳过来的,哦。取出i准备比较
  44:    2b63          cmp    r3, #99    ; 0x63 //和99做比较啦。
  46:    ddf2          ble.n    2e <param_exit+0x1a> //如果小于等于则跳进循环运行
  48:    f107 0708     add.w    r7, r7, #8
  4c:    46bd          mov    sp, r7
  4e:    bd80          pop    {r7, pc}    
    现在对for 语言有了理解吧。for语句,有三个部门 i = 0是个初始化, i < 100是比较判断跳转,i++是每次循环完毕后,再下一次比较前进行的操作。你完全可以从上述汇编中理解。
    别以为 i++放在 for()里,就会先执行。这里说个非常“奇怪的例子”,希望强调两点
    1、for()里面不是一定什么都要写的,语法规则,可以完全空着。
    2、用goto来描述一下for 怎么实现。
static void param_exit(void){
    int i;
    printf("param_exit func !\n");
LABEL_for_init_param_exit:    
    i = 0;
    goto LABEL_for_check_param_exit;
    for ( ; ; ){
LABEL_for_begin_param_exit:
    printf("touch the dog! \n");
LABEL_for_adjust_param_exit:
    i++;
LABEL_for_end_param_exit:        
LABEL_for_check_param_exit:
        if (i < 100){
            goto LABEL_for_begin_param_exit;
        }else{
        
        }    
    }
    
    return;
    goto LABEL_for_init_param_exit;
    goto LABEL_for_end_param_exit;
    goto LABEL_for_adjust_param_exit;
}     

    使用gcc 编译,链接。
    运行
    ./attr ./attr.c1 (rm attr.c1)
    不好意思,上面的代码,有错误。你会发现你的程序有死循环。不用紧张。电脑不值钱,即便程序运行导致你的电脑主板一阵青烟后再也无法启动,也没关系。重要的是数据,如我对上述所有代码均上传到perforce上(一种版本管理器)一样,你要保持良好习惯,每做一小步的开发,就要做次测试验证,验证正确,立刻备份保存。
    出错的原因,先不谈。至少编译通过了。也即 for (;;)没有什么大不了的。语法支持什么都不写。当然意思也很确定,就是死循环。
鬼话:死循环绝对不是错误,仅是一种逻辑表现。你所使用的电脑操作系统里,到处都有死循环。只有当配合死循环的逻辑缺失时,整体才是错误。很多设计原理,都依赖死循环的存在。
    那么这里缺失了一个明显的逻辑,也即 i >= 100 时,我们需要跳转。因此代码修改如下:
static void param_exit(void){
    int i;
    printf("param_exit func !\n");
LABEL_for_init_param_exit:    
    i = 0;
    goto LABEL_for_check_param_exit;
    for ( ; ; ){
LABEL_for_begin_param_exit:
    printf("touch the dog! \n");
LABEL_for_adjust_param_exit:
    i++;
LABEL_for_end_param_exit:        
LABEL_for_check_param_exit:
        if (i < 100){
            goto LABEL_for_begin_param_exit;
        }else{
            goto LABEL_return_param_exit;
        }    
    }
LABEL_return_param_exit:    
    return;
    goto LABEL_for_init_param_exit;
    goto LABEL_for_end_param_exit;
    goto LABEL_for_adjust_param_exit;
}     

    再次编译链接运行,对比上次输出,看是否一致了。    
    而当你注释掉 ,for(;;),余下的代码才对应
 
   for (i = 0; i < 100 ;i++){
        printf("touch the dog!\n");
    } 

    的逻辑。
    我们去掉for(;;)后,获取反汇编如下:
00000014 <param_exit>:
  14:    b580          push    {r7, lr}
  16:    b082          sub    sp, #8
  18:    af00          add    r7, sp, #0
  1a:    f240 0000     movw    r0, #0
  1e:    f2c0 0000     movt    r0, #0
  22:    f7ff fffe     bl    0 <puts>
  26:    f04f 0300     mov.w    r3, #0
  2a:    607b          str    r3, [r7, #4]
  2c:    e00a          b.n    44 <param_exit+0x30>
  2e:    bf00          nop
  30:    f240 0000     movw    r0, #0
  34:    f2c0 0000     movt    r0, #0
  38:    f7ff fffe     bl    0 <puts>
  3c:    687b          ldr    r3, [r7, #4]
  3e:    f103 0301     add.w    r3, r3, #1
  42:    607b          str    r3, [r7, #4]
  44:    687b          ldr    r3, [r7, #4]
  46:    2b63          cmp    r3, #99    ; 0x63
  48:    ddf1          ble.n    2e <param_exit+0x1a>
  4a:    bf00          nop
  4c:    f107 0708     add.w    r7, r7, #8
  50:    46bd          mov    sp, r7
  52:    bd80          pop    {r7, pc}    
 
    和if else的类似,只是多了点nop。因此,你需要注意我对LABEL_for_XXX的定义。实际的每次循环结束前有了i++的工作。而任何循环没有开始前,已经做了初始化和检测条件的工作。
    同时,我特意将循环的外部写为LABEL_return_param_exit,此处更多的是想强调,这个位置,已经和for循环没有关系。算for循环的真正的外部。因为跳出循环,有两种,一种是跳出本次内循环(注意有个内字),C语言对应的是continue。一种是跳出当前for循环,C语言对应的是break。
    为了更好的说明continue ,和break,以及我非常希望你喜欢上goto(虽然很少用,但保持goto的思维)下面给出continue和break对应使用goto实现的方式。
static int test = 0;
static void param_exit(void){
    int i;
    printf("param_exit func !\n");
LABEL_for_init_param_exit:    
    i = 0;
    goto LABEL_for_check_param_exit;
    //for (i = 0; i < 100 ; i++){
    //for ( ; ; )
    {
LABEL_for_begin_param_exit:
    printf("touch the dog! \n");
LABEL_for_break_param_exit:    
    if (test == 0){
        goto LABEL_return_param_exit;
    }
LABEL_for_adjust_param_exit:
    i++;
LABEL_for_end_param_exit:        
LABEL_for_check_param_exit:
        if (i < 100){
            goto LABEL_for_begin_param_exit;
        }else{
            goto LABEL_return_param_exit;
        }    
    }
LABEL_return_param_exit:
    
    return;
    goto LABEL_for_break_param_exit;
    goto LABEL_for_init_param_exit;
    goto LABEL_for_end_param_exit;
    goto LABEL_for_adjust_param_exit;
}     

    编译,链接,运行(记得是输入参数文件名不存在的情况),你会发现只执行了一次。对应的break的代码如下:
static void param_exit(void){
    int i;
    printf("param_exit func !\n");
    for (i = 0 ; i < 100; i++){
        printf("touch the dog! \n");
        if (test ==0){
            break;
        }
    }
    return;
}         

    你可以通过反汇编发现,仍然是只多了点nop。在没有写出continue做对比前,希望你关注下LABEL_for_break_param_exit的位置。
    下面给出conitnue的用法。为了跟好的说明,我们增加了test存储区。model.c的整体内容如下:
#include <stdio.h>
static void param_done(void){
    printf("param_done func !\n");
    return;
}
static int test = 0;
#if 1
static void param_exit(void){
    int i;
    printf("param_exit func !\n");
LABEL_for_init_param_exit:    
    i = 0;
    goto LABEL_for_check_param_exit;
    //for (i = 0; i < 100 ; i++){
    //for ( ; ; )
    {
LABEL_for_begin_param_exit:
    printf("touch the dog! \n");
LABEL_for_continue_param_exit:    
    if (test == 0){
        goto LABEL_for_adjust_param_exit;
    }
    test += 1;
LABEL_for_adjust_param_exit:
    i++;
LABEL_for_end_param_exit:        
LABEL_for_check_param_exit:
        if (i < 100){
            goto LABEL_for_begin_param_exit;
        }else{
            goto LABEL_return_param_exit;
        }    
    }
LABEL_return_param_exit:
    
    return;
    goto LABEL_for_continue_param_exit;
    goto LABEL_for_init_param_exit;
    goto LABEL_for_end_param_exit;
    goto LABEL_for_adjust_param_exit;
}    

#else
static void param_exit(void){
    int i;
    printf("param_exit func !\n");
    for (i = 0 ; i < 100; i++){
        printf("touch the dog! \n");
        if (test == 0){
            continue;
        }
        test += 1;
    }
    return;
}
#endif
void model(int flag){


     if (flag){
         param_done();
     }else{
         param_exit();//printf("model func flag == 0\n");//此处增加一个判断测试点
     }
         
     return;

 } 

     你可以发现,除了多出的test相关内容,以及LABEL_for_break_XXX调整为 LABEL_for_continue_XXX等,还有个#if 1, #else #endif。
     #if 和前面介绍的 #ifdef #ifndef 很像。 后面的表达式如入成立,则其到#else或#endif的内容,被预处理程序保留,否则被删除,不进行编译。因此上面 #if 1 实际是对第一个  param_exit函数进行处理,而你可以通过修改 #if 0来实现,对第二个函数的处理。否则,你要跟随我的例子,进行同一个函数,两个版本的切换,并生成对应的反汇编,你得不停的ctrl +c 和ctrl+v。
 鬼话:不要忽视这些宏操作的价值。当然我希望至少在你发现其某一个价值时,介绍给你。
    扩展一下,     参考#ifndef ,对应有个#ifdef。和#ifndef类似,就是解释相反。对于预处理程序发现如果定义了什么,则下面的需要进行编译处理。因此我们可以将上面的#if 1 修改如下
    #ifdef LABEL_VER
    则表示,如果定义了LABEL_VER则LABEL版本的代码被编译。那么我们怎么保证这个能运行呢?有两种方法。
    1、静态的。你可以在这之前,增加
    #define LABEL_VER
    没错,什么也没有,就是个噱头。
    2、动态的。我们在gcc 的参数里增加。如下
    gcc -DLABEL_VER .....,现在你可以如下处理了。
    arm-linux-gnueabi-gcc -DLABEL_VER -c model.c -o model.o 此时生成了什么,你可以反汇编,并对照你前面的生成信息比较一下就可以。而此时,仅使用 arm-linux-gnueabi-gcc  -c model.c -o model.o ,也可以在看一下,生成了。
鬼话:并非我玩你,我一开始就这么用了,无非是希望你能发现这些用法的价值,因为他可以省去很多代码文本的操作。
    -D的使用方法就不多说了,根据参考文献4可以获得全面的信息。不要觉得其他高级语言有多牛,也不要觉得命令行下的gcc有多弱,其实都一样,当你在IDE(集成开发环境里),对配置点来点去,还不知道为什么点,仅是书本说应该这样时,为什么不考虑通过参考文献4来明确知道每个动作的含义呢?
鬼话:为什么C语言的非IDE的开发方式要学这么多?很简单,知其所以然,则可以不求人,只知其然,出了问题,自然不知其所以然。
    回到continue的讨论,需要注意,continue是跳过当前内循环,意味着,i++这种adjust的操作,仍然需要执行。这是和break最大的区别。因为break直接跳出一个循环,而不会追加adjust部分的执行。利用这个特性,因此也经常在代码中通过检测调整量来判断是否循环检测到你想要的东西。一个C代码的模板如下:
   
for (i = 0 ; i < 100 ; i++){
        
        if (官府来抓虐待动物者){
            break;
        }
        摸狗一下;
    }
    if (i >= 100){
        很爽,摸了100下。
    }else{
        城管来了,没摸够100下。
    } 

    但需要注意,下面写法的区别
   
for (i = 0 ; i < 100 ; i++){
        摸狗一下;
        if (官府来抓虐待动物者){
            break;
        }
        
    }
    if (i >= 100){
        很爽,摸了100下。
    }else{
        if (i == 99){
            嘿嘿,还好,摸完了100下,差点被抓
        }else{
            城管来了,没摸够100下。
        }
    }     

    特别是我们对一个连续逻辑空间内(不代表实际连续存储在一起)进行查找,一旦找到就OK的逻辑,用上面的方法就可以判断到是否找到了。你可以尝试写一些真实的代码。
    跳转,在C里面,其实还有一个标准的动作。就是我们一早介绍的return。说return和goto的区别,不妨你自己参考文献1。需要注意的是6.8.6.2.2,这个三个比较需要好好看看,特别是我介绍完while 和do{}while之后。
    先说goto。你可以在标准中注意到,是局部跳转。也必须注意到一个小句子 , A goto statement shall not jump from outside the scope of  an identifer having a variably modified type to inside  the scope of that identifier。
    我就不翻译了。如果看不懂,可以尝试做做国际标准里的范例。资料上有的,我尽量不重复,无非希望告诉你写重点。对应的一个C语言的重点概念是 scope,作用域,而在标准第一章就提到这个概念,可想而知他的重要性,而传统的教科书对这块有所欠缺,因此如果你想深入的学习标准而提升你的C编程水平,scope需要重点理解。
    但一个典型的推广就是,goto无法跳转到其他函数里。不过C的强大,不会另你无法作为。在C的标准库中提供了setjmp和longjmp,由此可以另你为所欲为的到处goto。
鬼话:可能有些C语言老师要鞭笞我了。让初学者到处跳,甚至函数里面跳出去?你是误导人还是教育人。我只能说,拜托了传统教育,读书阶段我有几个想法用C都无法实现,至少弱智的处处加判断,处处做return。
    我们调整一下model.c代码如下:
#include <stdio.h>
#include <setjmp.h>
jmp_buf context_buf;
static void param_done(void){
    printf("param_done func !\n");

    return;
}
static int test = 0;
static void param_exit(void){
    int i;
    
    printf("param_exit func !\n");
    for (i = 0 ; i < 100; i++){
        printf("touch the dog! \n");
        if (test >= 10){
            printf("the officers come ! run away!\n");
            longjmp(context_buf,test);
        }
        test += 1;
    }
    return;
}

void model(int flag){
    

     if (flag){
         param_done();
     }else{
         if (setjmp(context_buf) >= 10){
             printf("my god ,i escape!\n");
         }else{
             param_exit();//printf("model func flag == 0\n");//此处增加一个判断测试点
         }
     }
         
     return;

 } 

     先不要考虑我printf中英语的水平。先着重看一下context_buf。我相信这个单词我并没有用错。意思是上下文的BUF。这里的上下文是什么意思呢?不得不从函数使用方式来描述。
     我们可以把CPU里的ALU单元,计算逻辑单元看做是个舞台上的灯光、音响等设备。那么对应很多寄存器和堆栈等等看作舞台的空间。一个函数的逻辑,看作一幕话剧。如果一个函数调用另一个函数,可以看作一幕场景剧情切换到另一幕场景。但是毕竟子函数处理完,原先的场景还要继续。怎么办?恢复啊。可以假设,model的函数落到flag == 0时,是你在向别人说过故事“想当年,你大哥我,在街上。。”,于是开始调用param_exit()函数。这个,舞台工作人员开始把现在的场景布置记录下来,随后开始布置param_exit的场景,还记得汇编里有一堆“编译套路”吗?例如
      14:    b580          push    {r7, lr}
  16:    b082          sub    sp, #8
  18:    af00          add    r7, sp, #0
  等等,其实还包括其他工作,比如 bl    0 <param_done> 这个动作,也就是调用当前函数的动作。
  等场景置换完毕,那么就开始演出你摸狗的桥段了。那么
      longjmp(context_buf,test); 这是什么意思呢?很简单,穿越了。通常正常的函数,需要通过return  或执行到函数尾部,才会退出这个函数,于是场景再次重新恢复到你讲故事的环境。 return区别于 goto的一个重要作用就在于,可以返回到原先调用的指令位置的下一条指令,也就是说, return 是继续 “想当年,你大哥我,在街上 (此处省略XXX字param_exit函数),返回(场景重新回来),你看怎么样我牛吧“。而穿越呢?通常是  "想当年,你大哥我,在街上(此处省略XXX字param_exit函数),好悬啊,好悬啊,唉,你看城管追来没有?哦,我回来了。。吓死我了。。。“
           但无论那种方式,你都需要重新布置场景。无非,穿越剧我们无法使用正常的return,则需要手工的保留上下文的内容,是哪些,这个和硬件以及编译器有关系,你目前只需要记得,用 setjmp(context_buf)来记录。但每次记录时,setjmp会返回0.因此,第一次运行时,会去讲解故事,调用 param_exit();
           而在城管大喊一声“小子!你望哪里逃!”你的longjmp就会把context_buf的内容,迅速恢复到舞台上,也就是回到了一开是setjmp处的状况。于是,你身未动,已穿越。当然,longjmp的第二个参数是用于穿越时顺点东西回去,比如狗毛?或次数。而此时顺回去的东西,会作为setjmp的返回值。切记,切记,如果你为了向别人证明真的穿越了,多少顺点东西啊,无论什么,也都算古董啊。就怕你什么都没顺到,返回个0,完蛋了。继续穿吧。
      在不展开longjmp ,setjmp的利用介绍前,我们说下计算机执行指令的一个方式。很简单,有个寄存器,你完全可以看做是场记人员的本子。本子上是什么地址,就执行所指向的空间里的动作么。通常每执行一条指令,会对这个寄存器自动增加,以顺序执行下一条语句。而对于goto,则是表明,向那翻翻多少页,则自动就跳过去了。但是这可返回不了。而调用函数的跳转,除了写明向拿翻翻多少页,会额外在边上的一个小本子上写着,此处跳的。于是,return的作用啥?无非先找到这个小本子,再跳一下,这样自然就跳回原先的函数了。当然该场景切换的,还是要切换。这是call 调用函数,return 返回跳转,与goto的主要区别。
      展开讨论一下,longjmp ,setjmp的使用。太有用了。无非你做老师的习题用不上。因为考试,不是对,就是错。而工程上我们要能有包容错误的能力。如同我们发现有些逻辑有问题,则怎么办?丢弃掉,但不能导致整体模块退出。此时就在初始位置,setjmp一下,而发现出错,则longjmp到初始位置,继续该干什么干什么。但不代表循环重复啊。至少大话西游里,吴孟达多加了个“又”字。
     

以下是话题补充:

@中山野鬼:有错别字,还有参考文献4写成了参考文献3,OSC我仅发原始内容。排版,校正,以后处理。希望大家多包涵。 (2012/10/28 03:03)
加载中
0
lanybass
lanybass

感谢野鬼的连载.

我谨代表大家包涵野鬼~

0
中山野鬼
中山野鬼
我不介意现在的C语言老师,或其他C高手对我的怀疑,新手是否应该从汇编和goto讲解,甚至现在的阶段说longjmp 等。我只想说,我希望新手学习C语言是用的,不是研究和记忆知识点的。goto来说明break,continue 等等可以加深这些语句的理解。从而避免逻辑实现错误。 而我更关心,作为新手,我上面的描述方式是否真的能理解,如果不能,我会修改描述内容,而不是删除这段讨论。
0
lanybass
lanybass

引用来自“中山野鬼”的答案

我不介意现在的C语言老师,或其他C高手对我的怀疑,新手是否应该从汇编和goto讲解,甚至现在的阶段说longjmp 等。我只想说,我希望新手学习C语言是用的,不是研究和记忆知识点的。goto来说明break,continue 等等可以加深这些语句的理解。从而避免逻辑实现错误。 而我更关心,作为新手,我上面的描述方式是否真的能理解,如果不能,我会修改描述内容,而不是删除这段讨论。
对汇编了解点皮毛的人(比如我)来说,你这种从起源开始追溯的方法还是比较受用。如果看懂了你这个,那自己猜都知道for循环改怎么用了,甚至可以自己“发明”for循环(这么多的goto,我看得头晕,为什么不用一种简单的语句来代替他们?对,这就是for语句)。不过在我看来,不是每个人都能在一行行汇编代码里跟着你的思路,因为看见太多陌生的东西就会迷惑和畏惧。我还好是勉强知道几个add mov cmp jmp。否则我就直接跳过,往下继续看了。
0
杨同学
杨同学
语法+方法,碉堡了
0
景德真人
景德真人
搬个板凳,细细品尝。
0
中山野鬼
中山野鬼

引用来自“齐天大肾”的答案

引用来自“中山野鬼”的答案

我不介意现在的C语言老师,或其他C高手对我的怀疑,新手是否应该从汇编和goto讲解,甚至现在的阶段说longjmp 等。我只想说,我希望新手学习C语言是用的,不是研究和记忆知识点的。goto来说明break,continue 等等可以加深这些语句的理解。从而避免逻辑实现错误。 而我更关心,作为新手,我上面的描述方式是否真的能理解,如果不能,我会修改描述内容,而不是删除这段讨论。
对汇编了解点皮毛的人(比如我)来说,你这种从起源开始追溯的方法还是比较受用。如果看懂了你这个,那自己猜都知道for循环改怎么用了,甚至可以自己“发明”for循环(这么多的goto,我看得头晕,为什么不用一种简单的语句来代替他们?对,这就是for语句)。不过在我看来,不是每个人都能在一行行汇编代码里跟着你的思路,因为看见太多陌生的东西就会迷惑和畏惧。我还好是勉强知道几个add mov cmp jmp。否则我就直接跳过,往下继续看了。
总得有所新的学习。而且我涉及到汇编的,反复的基本就是那几个命令,且这个只是汇编的一些指令展示,和汇编设计还相去甚远。。。我倒希望新手能硬着头皮看看,看几次,后面的反汇编就舒服多了。
0
中山野鬼
中山野鬼

引用来自“齐天大肾”的答案

引用来自“中山野鬼”的答案

我不介意现在的C语言老师,或其他C高手对我的怀疑,新手是否应该从汇编和goto讲解,甚至现在的阶段说longjmp 等。我只想说,我希望新手学习C语言是用的,不是研究和记忆知识点的。goto来说明break,continue 等等可以加深这些语句的理解。从而避免逻辑实现错误。 而我更关心,作为新手,我上面的描述方式是否真的能理解,如果不能,我会修改描述内容,而不是删除这段讨论。
对汇编了解点皮毛的人(比如我)来说,你这种从起源开始追溯的方法还是比较受用。如果看懂了你这个,那自己猜都知道for循环改怎么用了,甚至可以自己“发明”for循环(这么多的goto,我看得头晕,为什么不用一种简单的语句来代替他们?对,这就是for语句)。不过在我看来,不是每个人都能在一行行汇编代码里跟着你的思路,因为看见太多陌生的东西就会迷惑和畏惧。我还好是勉强知道几个add mov cmp jmp。否则我就直接跳过,往下继续看了。
总得有所新的学习。而且我涉及到汇编的,反复的基本就是那几个命令,且这个只是汇编的一些指令展示,和汇编设计还相去甚远。。。我倒希望新手能硬着头皮看看,看几次,后面的反汇编就舒服多了。
0
中山野鬼
中山野鬼

引用来自“齐天大肾”的答案

引用来自“中山野鬼”的答案

我不介意现在的C语言老师,或其他C高手对我的怀疑,新手是否应该从汇编和goto讲解,甚至现在的阶段说longjmp 等。我只想说,我希望新手学习C语言是用的,不是研究和记忆知识点的。goto来说明break,continue 等等可以加深这些语句的理解。从而避免逻辑实现错误。 而我更关心,作为新手,我上面的描述方式是否真的能理解,如果不能,我会修改描述内容,而不是删除这段讨论。
对汇编了解点皮毛的人(比如我)来说,你这种从起源开始追溯的方法还是比较受用。如果看懂了你这个,那自己猜都知道for循环改怎么用了,甚至可以自己“发明”for循环(这么多的goto,我看得头晕,为什么不用一种简单的语句来代替他们?对,这就是for语句)。不过在我看来,不是每个人都能在一行行汇编代码里跟着你的思路,因为看见太多陌生的东西就会迷惑和畏惧。我还好是勉强知道几个add mov cmp jmp。否则我就直接跳过,往下继续看了。
总得有所新的学习。而且我涉及到汇编的,反复的基本就是那几个命令,且这个只是汇编的一些指令展示,和汇编设计还相去甚远。。。我倒希望新手能硬着头皮看看,看几次,后面的反汇编就舒服多了。
0
贾珣
贾珣
这个连载很重要=)感谢楼主=)
返回顶部
顶部