第5章 进程环境(5)_非局部跳转

6. 非局部跳转

(1)setjmp和longjmp语句


头文件


#include<setjmp.h>


函数


int* setjmp(jum_buf env);


返回值


直接调用返回0,若从longjmp调用返回则返回非0值


功能


设置非局部跳转的跳转点


 


函数


void longjmp(jmp_buf env, int val);


功能


进行非局部转转,val为返回值


参数


env:一个特殊类型jmp_buf。这一数据类型是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的信息(如,各寄存器的值;但不保存线程栈局部变量等数据)。一般,env变量是个全局变量,因为需要从另一个函数引用它。


备注


(1)C程序缺乏异常处理方法,可使用非局部跳转处理C程序的异常。

(2)goto语句仅限于函数内部跳转,而longjmp不限于

【编程实验】非局部跳转

//process_jmp.c

#include <setjmp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TOK_ADD      5
#define TOK_SUB      6
char* const prompt = "cal: "; //命令行提示符

jmp_buf env;  //保存运行环境,非局部跳转用于函数间的跳转

//命令行格式如:add 3 5  或 sub 5 3
void do_line(char* line); //解析并执行命令行内容
void cmd_add(void);
void cmd_sub(void);
int get_token(char* line); //从命令行参数中获取标识符

int main(int argc, char* argv[])
{
    ssize_t size = strlen(prompt) * sizeof(char);
    char buff[256];
    ssize_t len = 0;

    //设置跳转点
    if(setjmp(env) < 0){ //直接调用返回0,从longjmp跳转来时,返回longjmp中指定的val值
                         //这里我们设定val为正值,小于0表示setjmp调用失败!
        perror("setjmp error");
        exit(1);
    }

    //输出提示符
    write(STDOUT_FILENO, prompt, size);
    while(1)
    {
        len = read(STDIN_FILENO, buff, 256);
        if(len < 0) break;

        buff[len -1] = 0;
        do_line(buff);

        write(STDOUT_FILENO, prompt, size);
    }

    return 0;
}

void do_line(char* line)
{
    int cmd = get_token(line);
    switch(cmd)
    {
    case TOK_ADD:
        cmd_add();
        break;
    case TOK_SUB:
        cmd_sub();
        break;
    default:
        fprintf(stderr, "error command\n");
    }
}

void cmd_add(void)
{
     int i = get_token(NULL);
     int j = get_token(NULL);

     printf("result: %d + %d = %d\n", i, j, i + j);
}

void cmd_sub(void)
{
     int i = get_token(NULL);
     int j = get_token(NULL);

     printf("result: %d - %d = %d\n", i, j, i - j);
}

static int is_number(char* item)
{
    int ret = 1;
    int len = strlen(item);
    int i = 0;

    for(i=0; i<len; i++) {
        ret = ret  && (‘0‘<= item[i]) && (item[i] <= ‘9‘);
        if (ret == 0) break;
    }

    return ret;
}

int get_token(char* line)
{
    //格式add 3 4
    //    sub 7 5
    char* item = strtok(line, " ");
    if(line != NULL)
    {
        if(!strcmp("add", item)) return TOK_ADD;
        if(!strcmp("sub", item)) return TOK_SUB;
    }else{
        if(is_number(item)){
            int i = atoi(item);
            return i;
        }else{
            fprintf(stderr, "arg not number\n");
            longjmp(env, 1);  //1表示跳到跳转点(setjmp)后,setjmp的返回值
        }
    }
}

(2)longjmp对各类变量的影响


变量类型


受影响情况


全局变量、静态变量和volatile变量


不能恢复到原始值


寄存器变量


可以恢复到原始值


自动变量


优化编译后可能会恢复

  ①存放在存储器中的变量将具有longjmp时的值而在cpu和浮点寄存器中的变量则恢复为调用setjmp时的值

  ②不进行优化时自动变量、寄存器变量、全局变量、静态变量和易失变量等都存放在存储器中(亦即忽略了对reg_var变量的register存储类说明)。

  ③而进行了优化后auto_var和reg_var都存放在寄存器中(即使auto_var并未声明为register),volatile变量则仍存放在存储器中

  ④全局、静态和易失变量不受优化的影响,在调用longjmp后,它们的值是最近所呈现的值。

  ⑤如果要编写一个使用非局部跳转的可移植程序,则必须使用volatile属性

【编程实验】longjmp对变量的影响

//longjmp_var.c

#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

int global_var;

jmp_buf env;

int  show(int g_v, int s_v, int a_v, int r_v, int m_v, int v_v);
int  f1(int g_v, int s_v, int a_v, int r_v, int m_v, int v_v);
void f2();

int main(int argc, char* argv[])
{
    static int sta_var;
    int auto_var;
    register reg_var;
    int* heap_var = (int*)malloc(sizeof(int));
    volatile int vola_var;//易失变量

    global_var = 1; sta_var = 2; auto_var = 3;
    reg_var = 4; *heap_var = 5; vola_var = 6;

    int k = 0;
    if((k = setjmp(env)) < 0 ){
        perror("setjmp error");
    }else if(k == 1){
        printf("after longjmp:\n");
        show(global_var, sta_var, auto_var, reg_var, *heap_var, vola_var);
        exit(0);
    }

    global_var = 10; sta_var = 20; auto_var = 30;
    reg_var = 40; *heap_var = 50; vola_var = 60;

    printf("before longjmp:\n");
    f1(global_var, sta_var, auto_var, reg_var, *heap_var, vola_var);

    return 0;
}

int  show(int g_v, int s_v, int a_v, int r_v, int m_v, int v_v)
{
    printf("  global: %d, static: %d, auto: %d, reg: %d, heap: %d vola: %d\n",
            g_v, s_v, a_v, r_v, m_v, v_v);
}

int  f1(int g_v, int s_v, int a_v, int r_v, int m_v, int v_v)
{
    show(g_v, s_v, a_v, r_v, m_v, v_v);
    f2();
}

void f2()
{
    longjmp(env, 1);
}
/*输出结果:
 [[email protected] 5.process]# gcc -o bin/longjmp_var src/longjmp_var.c
 [[email protected] 5.process]# bin/longjmp_var
 before longjmp:
   global: 10, static: 20, auto: 30, reg: 40, heap: 50 vola: 60
 after longjmp:
   global: 10, static: 20, auto: 30, reg: 40, heap: 50 vola: 60
 [[email protected] 5.process]# gcc -O -o bin/longjmp_var src/longjmp_var.c
 [[email protected] 5.process]# bin/longjmp_var
 before longjmp:
   global: 10, static: 20, auto: 30, reg: 40, heap: 50 vola: 60
 after longjmp:
   global: 10, static: 20, auto: 3, reg: 4, heap: 50 vola: 60
 */
时间: 2024-10-06 20:27:44

第5章 进程环境(5)_非局部跳转的相关文章

《Unix环境高级编程》读书笔记 第7章-进程环境

1. main函数 int main( int argc, char *argv[] ); argc是命令行参数的数目,包括程序名在内 argv是指向参数的各个指针所构成的数组,即指针数组 当内核执行C程序时(使用exec函数),在调用main前先调用一个特殊的启动例程.可执行程序文件将此启动例程指定为程序的起始地址——这是由连接器设置的,而连接器则是由C编译器调用.启动例程从内核取得命令行参数和环境变量值,然后按上述方式调用main函数做好安排. 2. 进程终止 有8种方式使进程终止,其中5种

APUE学习笔记:第七章 进程环境

7.1 引言 本章将学习:当执行程序时,其main函数是如何被调用的:命令行参数是如何传送给执行程序的:典型的存储器布局是什么样式:如何分配另外的存储空间:进程如何使用环境变量:各种不同的进程终止方式等:另外还将说明longjmp和setjmp函数以及它们与栈的交互作用:还将介绍研究进程的资源限制 7.2 main函数 C程序总是从main函数开始执行.当内核执行C程序时,在调用main前先调用一个特殊的启动例程.可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编

【转】浅析C语言的非局部跳转:setjmp和longjmp

转自 http://www.cnblogs.com/lienhua34/archive/2012/04/22/2464859.html C语言中有一个goto语句,其可以结合标号实现函数内部的任意跳转(通常情况下,很多人都建议不要使用goto语句,因为采用goto语句后,代码维护工作量加大).另外,C语言标准中还提供一种非局部跳转“no-local goto",其通过标准库<setjmp.h>中的两个标准函数setjmp和longjmp来实现. C标准库<setjmp.h>

Unix编程第7章 进程环境

准备雄心勃勃的看完APUE,但是总感觉看着看着就像进入一本字典,很多地方都是介绍函数的用法的,但是给出例子远不及函数介绍的多.而且这本书还是个大部头呢.第7章的讲的进程环境,进程是程序设计中一个比较重要的概念,知道倒是知道它的大概意思,但是其实还是有很多的细节其实都没有深究,这章呢APUE就带着我们逛了一下如下的几个主题(尼玛,学C语言的话,学那点语法其实不是很重要,反而经常把时间浪费在语法的蹩脚处): 1.程序执行的时候main函数是如何被调用的 2.命令行参数是如何传递给新程序的: 3.典型

第5章 进程环境(3)_环境表和环境变量

3. 环境表 (1)每个进程都有一个独立的环境表(字符指针数组) (2)初始的环境表继承自父进程 (3)两种访问方式: ①int main(int argc, char* argv[], char* envp[]);  //第3个参数 ②extern char** environ;   //全局变量 4. 环境变量操作函数 (1)getenv:获取环境变量值 头文件 #include<stdlib.h> 函数 char* getenv(const char* name); 返回值 指向与nam

第5章 进程环境(2)_进程的启动和终止

2. 进程启动和终止 2.1 C程序启动过程 (1)启动例程 ①是一段程序代码,放置在/lib/libc.so.***中.编译器在编译时会将启动例程的代码编译进可执行文件中. ②可执行程序将这段嵌入的启动例代码指代为程序的起始地址. ③当内核执行C程序时(使用exec函数),在调用main前先执行启动例程代码. (2)启动例程的作用 ①搜集命令行的参数传递给main函数中的argc和argv ②搜集环境信息构建环境表并传递给main函数 ③登记进程的终止函数 2.2 进程终止   进程终止 主要

第5章 进程环境(4)_进程资源限制

5. 进程资源限制 (1)getrlimit.setrlimit函数 头文件 #include<sys/resource.h> 函数 int* getrlimit(int resource, struct rlimit* rlptr); 返回值 成功返回0,出错返回非0 功能 获得资源限制,存放在rlptr指向的结构体中 参数 (1)rlimit结构体 struct rlimit{ rlim_t rlim_cur; //软限制:当前限制 rlim_t rlim_max; //硬限制:rlim_

第5章 进程环境(1)_进程结构(task_struct)

1. 进程的概念和进程结构 1.1 进程 (1)程序(program):是一些保存在磁盘上有序指令的集合,是存放在磁盘文件中的可执行文件.但没有任何执行的概念,它是静态的. (2)进程(process) ①程序的执行实例被称为进程,是一个动态的概念,它是程序执行的过程,包括创建.调度和消亡. ②进程具有独立的权限与职责,如果系统中某个进程崩溃,它不会影响其余的进程. ③每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通信. (3)进程ID:每个linux进程都有一个唯一

APUE学习笔记(第七章 进程环境)

本章将了解进程的环境. main函数 C程序总是从main函数开始执行,main函数的原型是: int main(int argc,char *argv[]); 其中,argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组. 进程终止 3个函数用于正常终止一个程序:_exit和_Exit立即进入内核,exit则先执行一些清理处理(对于所有打开流调用fclose函数),然后返回内核. #include <stdlib.h> void exit(int status); void _