Chaofan's

Bonvenon al la malpura mondo.

标签: i11r旧文

从qcf.is-programmer.com搬运来的文章

  • 用C写的一个简易JSON Parser

    初来乍到的一学期实在是无聊,于是在百团大战中加了计算机协会,就认识了几个计算机和软件专业的人。看见一软件同学在空间上转发的C语言期末项目,顿时生了兴趣。代码是春节期间写完的,可惜这个博客被搁置了太久,现在把代码贴上来,也不算过分吧。

    作业的具体内容是:老师给定一个头文件 json.h ,包含了 json 数据结构的定义以及一些相关函数的声明,任务就是实现这些函数,最后提交通过学校的自动化测试。我不是软件的学生,自然没有这个权利,不过手工检验了一下,貌似还没有太大问题。只是最近写一个 web 项目的时候,把其间一个比较“庞大”的 json 文件(也才4KB!)当作测试材料,结果程序直接崩溃了;再截取一小部分,又能运行了。难道是字符串读写复制操作太多?可貌似连 fopen 函数都调用失败了,搞不懂。

    老师们给的头文件想必是由 cJSON 的头文件修改而来的,json结构体的定义如下:

    typedef struct JSON {
        int type;   /* The type of the item, as above. */
    
        char *valuestring;  /* The item's string, if type==JSON_STRING */
        int valueint;   /* The item's number, if type==JSON_TRUE||JSON_FLASE;
                          or quantity of members, if type==JSON_ARRAY||JSON_OBJECT */
        double valuedouble; /* The item's number, if type==JSON_NUMBER */
    
        // the following are created by me
        struct JSON *head; /* Head of array, if type==JSON_ARRAY||JSON_OBJECT */
        struct JSON *last; /* Previous member, if this belongs to an array or object */
        struct JSON *next; /* Next member, if this belongs to an array or object */
        char *name; /* Name of member, if this belongs to an object */
    } JSON;

    函数众多就不一一列举了,有几个实现起来相对复杂的:

    void PrintJSON(JSON *item);
    JSON *Duplicate(JSON *item, int recurse);
    JSON *ParseJSON(const char *value);```

    如果有朋友对 json 不熟悉的话,简单介绍一下:

    {
        "name": "ecnelises",
        "school": {
            "primary": "Chenjiawan",
            "junior": "qinghua",
            "senior": "yucai",
            "university": "tongji"
        },
        "randomvalue": [
            1.5,
            18,
            true,
            null
        ]
    }

    json 的类型有 object、array、string、number、boolean(true、false)、null. array 用方括号 [] 括起来,object是“键值对”(类似C++中的 map),用花括号 {} 括起来。json 是一种简化版的数据结构(相比于庞杂的 xml),在 javascript 和其他地方广泛使用。

    第一个要介绍的函数是 Print,顾名思义就是根据既有的 json 打印出规整的内容。这个函数本身的定义很简单:

    void PrintJSON(JSON *item)
    {
        if (item == NULL){
            return;
        }
    
        switch (item -> type) {
            case JSON_FALSE:
                printf("false");
                break;
            case JSON_TRUE:
                printf("true");
                break;
            case JSON_NULL:
                printf("null");
                break;
            case JSON_NUMBER:
                printf("%lf", item -> valuedouble);
                break;
            case JSON_STRING:
                printf("\"%s\"", item -> valuestring);
                break;
            case JSON_ARRAY:
                PrintArray(1, item, 1);
                break;
            case JSON_OBJECT:
                PrintObject(1, item, 1);
                break;
            default:
                break;
        }
    }

    关键在于调用的另外两个函数:PrintArray 和 PrintObject. 引入这两个函数的原因是方便递归。

    void PrintArray(int layers, JSON *array, int newline)
    {
        if (newline) {
            for (int i = 1; i < layers; ++i) {
                printf("    ");
            }
            printf("[\n");
        }
    
        JSON *find = array -> head;
        for (int c = 1; c <= array -> valueint; ++c) {
            if (find -> type == JSON_OBJECT) {
                PrintObject(layers + 1, find, 1);
            } else if (find -> type == JSON_ARRAY) {
                PrintArray(layers + 1, find, 1);
            } else {
                for (int i = 1; i < layers + 1; ++i) {
                    printf("    ");
                }
                PrintJSON(find);
            }
            if (c < array -> valueint) {
                printf(",");
            }
            printf("\n");
            find = find -> next;
        }
    
        for (int c = 1; c < layers; ++c) {
            printf("    ");
        }
        printf("]");
    
        if (layers == 1) {
            printf("\n");
        }
    }
    
    void PrintObject(int layers, JSON *object, int newline)
    {
        if (newline) {
            for (int i = 1; i < layers; ++i) { // Each calling executes all in the same layer
                printf("    ");
            }
            printf("{\n");
        }
    
        JSON *find = object -> head;
        for (int c = 1; c <= object -> valueint; ++c) {
            for (int i = 1; i < layers + 1; ++i) {
                printf("    ");
            }
            printf("\"%s\": ", find -> name);
    
            if (find -> type == JSON_OBJECT) {
                printf("{\n");
                PrintObject(layers + 1, find, 0); // '{' doesn't make a new line
            } else if (find -> type == JSON_ARRAY) {
                printf("[\n");
                PrintArray(layers + 1, find, 0);
            } else {
                PrintJSON(find);
            }
            if (c < object -> valueint) {
                printf(",");
            }
            printf("\n");
            find = find -> next;
        }
    
        for (int i = 1; i < layers; ++i) {
            printf("    ");
        }
    
        printf("}");
    
        if (layers == 1) {
            printf("\n");
        }
    }

    参数 layers 决定了当前要打印的东西在第几“层”,也就是说前面要有多少层缩进。newline 是为了处理冒号后 { 或 [ 不换行的情况。

    下面的函数 Duplicate 是负责复制 json 的,recurse 参数决定是否“深度”复制,我想大概是递归的意思吧。

    JSON *Duplicate(JSON *item, int recurse)
    {
        JSON *source = item;
        JSON *target = NULL;
        JSON *last = NULL;
        JSON *count;
    
        while (source != NULL) {
            switch (source -> type) {
                case JSON_TRUE:
                    target = CreateTrue();
                    break;
                case JSON_FALSE:
                    target = CreateFalse();
                    break;
                case JSON_NUMBER:
                    target = CreateNumber(source -> valuedouble);
                    break;
                case JSON_NULL:
                    target = CreateNULL();
                    break;
                case JSON_STRING:
                    target -> valuestring = CopyString(source -> valuestring);
                    break;
                case JSON_ARRAY:
                    target = CreateArray();
    
                    if (source -> head != NULL && recurse) {
                        target -> head = Duplicate(source -> head, 1);
                    } else if (source -> head != NULL && !recurse) {
                        target -> head = source -> head;
                    }
    
                    count = target -> head;
                    while (count != NULL) {
                        target -> valueint += 1;
                        count = count -> next;
                    }
                    break;
                case JSON_OBJECT:
                    target = CreateObject();
                    target -> name = CopyString(source -> name);
    
                    if (source -> head != NULL && recurse) {
                        target -> head = Duplicate(source -> head, 1);
                    } else if (source -> head != NULL && !recurse) {
                        target -> head = source -> head;
                    }
    
                    count = target -> head;
                    while (count != NULL) {
                        target -> valueint += 1;
                        count = count -> next;
                    }
                    break;
                default:
                    break;
            }
    
            source = source -> next;
            target -> last = last;
            if (last != NULL) {
                last -> next = target;
            } else {
                item = target; // Give the begining position to item
            }
            last = target;
            target = target -> next;
        }
    
        return item;
    }

    函数思路简单,但是算不上太好写……用“同步跟踪”的方式从当前 json 的子节点开始,最后再把头补上。

    相对而言最复杂的要数 ParseJSON 函数了。基本想法就是先搜索配对的括号,然后递归处理括号里边的内容,直到没括号的时候就根据冒号和逗号分隔开各个字符串然后进行解析。由于源字符串可以不是规整的,所以还得跳过其间的空白。没有学过编译原理,所以程序也不带出错功能,错误的输入结果是未知的。

    JSON *ParseJSON(const char *value)
    {
        /* We read the whole string char by char,
           and create a new function parse_object.
           We make two tables to record emergency of {} and [].
         */
        int blank_state = 1; // 是否空白
        int flayerl = 1;
        int flayers = 1;
        int lallb = 0, sallb = 0; // 记录所有出现的括号
        int lbrackets = 0, sbrackets = 0; // 记录“第一层”的括号
        unsigned long vallen = strlen(value);
        int *lbracket_table = NULL;
        int *sbracket_table = malloc(sizeof(int) * sbrackets);
    
        for (int i = 0; i < vallen; ++i) {
            if (value[i] == '{' && flayerl) {
                flayerl = 0;
                ++lbrackets;
                ++lallb;
            } else if (value[i] == '{' && !flayerl) {
                ++lallb;
            } else if (value[i] == '}' && (lallb != lbrackets)) {
                --lallb;
            } else if (value[i] == '}' && (lallb == lbrackets)) {
                flayerl = 1;
                lbracket_table = (int*)realloc(lbracket_table, sizeof(int) * lbrackets);
                lbracket_table[lbrackets - 1] = i;
            } else if (value[i] == '[' && flayers) {
                flayers = 0;
                ++sbrackets;
                ++sallb;
            } else if (value[i] == '[' && !flayers) {
                ++sallb;
            } else if (value[i] == ']' && (sallb != sbrackets)) {
                --sallb;
            } else if (value[i] == ']' && (sallb == sbrackets)) {
                flayers = 1;
                sbracket_table = (int*)realloc(sbracket_table, sizeof(int) * sbrackets);
                sbracket_table[sbrackets - 1] = i;
            }
        }
        lbrackets = 0;
        sbrackets = 0;
        // 在这里,由于函数是递归调用的关系,两个表只存储“第一层”括号的内容
    
        JSON *res = malloc(sizeof(JSON));
        JSON *pre = res;
        JSON *find = res -> next;
        char *middle;
    
        for (int i = 0; i < vallen; ++i) {
            if (blank_state && isspace(value[i])) {
                continue;
            } else if (blank_state && !isspace(value[i])) {
                switch (value[i]) {
                    case '{':
                        find = parse_object(middle = substr(value + i, '{', '}'));
                        free(middle);
                        i = lbracket_table[lbrackets++] + 1;
                        pre -> next = find;
                        find -> last = pre;
                        pre = pre -> next;
                        find = find -> next;
                        break;
                    case '[':
                        find = parse_array(middle = substr(value + i, '[', ']'));
                        free(middle);
                        i = sbracket_table[sbrackets++] + 1;
                        pre -> next = find;
                        find -> last = pre;
                        pre = pre -> next;
                        find = find -> next;
                        break;
                    case '\"':
                        find = parse_string(value + i);
                        pre -> next = find;
                        find -> last = pre;
                        pre = pre -> next;
                        find = find -> next;
                        i += next_mark(value + i + 1, '\"'); // 返回出现第二个参数对应字符的下一个位置
                        break;
                    case 'f':
                    case 't':
                    case 'n': // 猜测是否为 false、true 或者 null,在函数里验证
                        find = parse_bool_null(value + i);
                        pre -> next = find;
                        find -> last = pre;
                        pre = pre -> next;
                        find = find -> next;
                        blank_state = 0;
                        break;
                    case '+':
                    case '-':
                    default:
                        find = parse_number(value + i);
                        pre -> next = find;
                        find -> last = pre;
                        pre = pre -> next;
                        find = find -> next;
                        blank_state = 0;
                        break;
                }
            } else {
                switch (value[i]) {
                    case ',':
                        blank_state = 1;
                        continue;
                        break;
                    default:
                        continue;
                        break;
                }
            }
        }
        pre = res -> next;
        free(res);
        res = pre;
        res -> last = NULL;
    
        free(lbracket_table);
        free(sbracket_table);
    
        return res;
    }

    附上 parse_object 和 parse_array 的代码:

    JSON *parse_object(const char *value)
    {
        JSON *res = CreateObject();
        JSON *tmp;
        int blank_state = 1;
        char *left;
        char *right;
    
        for (int i = 0; i < strlen(value); ++i) {
            if (blank_state && isspace(value[i])) {
                continue;
            } else if (blank_state && !isspace(value[i])) {
                left = divstr(value + i); // 返回第一个在所有括号之外的 “:” 或者 “,” 的位置
                i += next_mark(value + i, ':') + 1;
                AddItemToObject(res, (tmp = parse_string(left)) -> valuestring, ParseJSON(right = divstr(value + i)));
                free(left);
                free(right);
                free(tmp);
                i += next_mark(value + i, ','); // when this loop end, i will be added by 1
                blank_state = 1; // so here we don't have 1 as an addition
            }
        }
        return res;
    }
    
    JSON *parse_array(const char *value)
    {
        JSON *res = CreateArray();
        char *middle;
        int blank_state = 1;
    
        for (int i = 0; i < strlen(value); ++i) {
            if (blank_state && isspace(value[i])) {
                continue;
            } else if (blank_state && !isspace(value[i])) {
                AddItemToArray(res, ParseJSON(middle = divstr(value + i)));
                free(middle);
                i += next_mark(value + i, ',');
                blank_state = 1;
            }
        }
        return res;
    }

    虽然自己接触C语言有些久了,但是真正写出一个达千行的程序,这还是第一次。寒假在家里反复改反复调试,看来自己代码能力还是很不行。虽然在高手们看来可能这个程序还很拙劣,不过成功运行之后还是很有成就感的。加油吧。

  • 丧,得

    半年以前,我以为我右手执笔,左手握着一本三二,就可以上天入地,斩妖除魔,无所畏惧。我说我是文科里的逗比,理科里的奇葩,留给腾贵一个无奈的背影。看着周黄伟便秘的眼神,心里暗暗发誓,六月八号以后,我一定要将你黑过的那些闲书一本一本地翻完。我也没有想到,作为一个男的,如果变得沉默,除了听了煽情的空间故事,也可能是语文课被抽起来回答问题。想当年在寝室里,左长右粗,讲不完的黑话黄段,夜夜笙歌。我说我横扫了育才门外一条街,最后一次还是匆匆献给了重大精英。我着急地飞过天桥,穿越大街,等着一个背影出现。也会走着相似的路,感受一下民工分量的米线。周一中午,我都会阔气地出门,仿佛贝索斯开着他的大货车来迎接我。我手捧纸箱,飞奔回寝,一刀下去以后,又是一个星期的快活。星期天的早上,我睡到自然醒,惬意地走到教室,翻两道题,电话来了,于是又早退。徐怀雄算个鸟!要是厉害,就与我在大富翁棋盘上,杀个昏天黑地。那段时间,回家少了,在公交车上,看到南京青奥会的新闻,窃想要是我去了南大,青奥会还开吗。后来有人不明就里地问我,为啥比状元活活少了五十分,我想,肯定是因为我多长了五十斤肉。你要是看到每天晚上寝室的盛况,就晓得我夜夜笙歌用得一点都不过分。五六个人围在一个人的床上调戏一个女声的场景你见过吗?不用多想,既然她远渡重洋,历经艰险从佛罗里达来到重庆,不好好玩一玩,怎么对得起大哥的淳朴好客?来把大富翁,分分钟数万,生死一线间。上帝不掷骰子,掷骰子的我们更拽。只是可惜了,我们的乌诺。

    一年以前,刺激不输后来。你看我半夜飞跃,拖着笨重的身躯,说时迟那时快,冲向了别人的……不是床,是楼。经历了几个来回,方才明白,西厢记也不是白写的。话说我说了你别生气,我当时真觉得十一楼的女生傻啊,寝室里这么久少个人都不知道,要是哪天有个男的趁你们睡着了潜入进来咋办?男寝里就有个这样的危险人物啊。睡完自习,回去就两回七大奇迹,休整一下又飞去月下幽会,回来要是忘带钥匙也不紧不忙,等着上铺那位起夜的时候给我开个门。我还真没在八楼的楼梯上睡过一夜,倒也遗憾啊。上了床,还不忘逛逛当当网,下个单。要是发现某本书自己的水平不够,那就记着,等着高三毕业再买。没想到高三毕业还真买了,可惜当当网早已被打入冷宫。寝室里喜欢飞来飞去的还不止我一个,那位仁兄逗比一点不输我,周六早上十点给我来个短信问我在哪儿,答曰寝室,没想到他也在寝室。穿好衣服发现去上个英语课还来得及,于是在校外众人返回的目光中,我们也实现了当个学霸的梦想。后来索性学霸也不装了,佯称去教室自习。整个星期六都慵懒得一比,下午漫步去杨家坪,可以唱个歌,可以逛个街,可以喝个茶,就是不回家。第二天懒觉一醒,外婆做的大鱼大肉下肚,抄抄作业,又是一周。回想起来,逼格倒也是蛮高的。那时候就喜欢买点什么旧制度大革命呀,国史大纲呀,社会契约论呀什么的。拿到教室,放在书立顶层,恨不得人人看见。可惜了你们大概也是这么过来的,一眼拆穿我装逼,搞得我现在都想问学校图书馆接不接受学生捐赠书了。那时候周末回家,我都是不坐座位的。我以为我是总统,看着我的人民幸福,我很高兴。真的,我那时候真这么想过。

    两年前?那就别多说了撒,因为说多了丢脸,真的。好厉害哟,他们都拉我上竞赛诶!王伟看他才讲完,我就写好了程序,以为我天资聪颖,脑力过人,心想这孩子能够培养培养。但是我觉得他傻逼,转头去了文科提高班。后来,后来我才晓得到底谁傻逼……还有什么在厕所里几个男的给女生打电话呀,唱唱单身情歌呀,摩擦摩擦我的背包呀。后来看着腾贵,我就想说,阿祖比你不知道高到哪里去了,我还跟他谈笑风生。原来我以为我是数学天才的时候,在办公室问他,级数是什么。阿祖一笑而过,敲了我三下头。我顿悟,“三年后你就懂”。可惜同济高数讲得太慢。再往前倒,我这天才的名号前还得加上高傲二字。你们都是傻逼。你你你,你用个诺基亚侧滑拽个毛,还说苹果用的是安卓系统,简直是学哈了。

    我们都离理想越来越远。你看,如果再怎么努力,都成不了心中那个很厉害的人,为什么不开心一点?

    怕什么幼稚,我还一度觉得用苹果电脑的都是傻逼呢。

    待我一年之后,成了同济大王,再来跟你把酒促膝谈谈哥的故事。

  • 再见

    时间它过去得快吗?好像并不是。当你紧盯着它,妄图成就自己的快乐时,它不紧不慢,坚守着自己的节奏,让挥霍人生的家伙们分外恼火。然而时间虽然公平,但人心并不。有许多被遗忘的事情,当你重新发现的时候,会禁不住地感叹“哇,居然这么久了!”,并以此覆盖心中的那份遗憾。

    这里的博客便是如此。我看着前一次的更新,回忆那时候自己面对时间表,是怎样的一种心情。我惊叹,一个神秘的日期居然能够把人生分为迥然不同的两个段落。原谅我的无词和无思想,面对后一个段落,我实在没有做好启程的准备。为什么我的话变少了呢?仔细想了想,有关自己的话其实一直都少。

    最后的几个月迷上了知乎。的确,知乎是个好地方,有各路大神用完美的逻辑演绎着他们独特的世界观。但是,知乎上待久了发现,我越来越不像我自己了。我慢慢流失独立的思考和勇敢的态度,不敢表明自己的观点。也许是老了吧,懒于与人争论。但,也有可能是破碎的世界观,根本支撑不起实打实的交流。

    最近几天是录取结果出来的日子。看到许多人去了自己想去的大学和专业,我是真的笑不起来。其实几个月前,几年前,我也期盼着有这样的一个时刻——面带笑脸,把自己打扮得像个大学生的模样,毫不隐瞒地向别人提起自己的目标。可惜现实弄人——不,是自己弄人。我做过无数个梦,想着自己踏进大学、赢取机会的方式,想着未来是如何令人喜悦的一番景象。而现在,我也不知道我为什么会做出这样的决定,我也不知道那位老师给我的承诺是不是一句空话。除此之外对现实愈发清晰的了解也加重着内心的那份无力感。

    没有人在关心你,甚至于你自己。

    即使某件事就放在面前,如果我曾怀有幻想,那我还是会保持着距离继续幻想。但是时候不同了,继续幻想只会招来毁灭。无论有没有机会,也要想着向前。我渐渐失去了动力——这是失败者的典型表现。

    获取信息,寻找兴趣,持续行走。

    我别无选择。

  • 后会有期

    好久没有到这个网站看看了,又是一年。

    当初选文科这条路实在是单纯,就觉得能上好大学。然后呢?没想过。

    如今才开始纠结呢。随便一搜哪个IT公司用人要求第一条就是“计算机及相关专业毕业”。

    呵呵。

    这也怪不了谁。

    没办法咯,这种事情,只有等到两个月后再来慢慢思索了。

    后会有期。

  • 2013-2-19

    很久没有到这里来了,上一篇文章还是两年前的。

    其实中间来过几次,还提到过转学的事情,不过也都是一年多以前了。

    可以说认识了新的人,也忘掉了一些旧的人。回到母校去看看的话,一定会有强烈的物是人非之感。

    其实这篇文,颇带点为赋新词强说愁的感觉。也可能是因为,当我真正打开编辑器的时候,大脑就会变得一片空白吧。

    两年前的我一定不可能想到现在我读的是文科,更不会预见到现在的自己是这副模样。

    怎么说,被一群文科生熏陶了几个月,少说也得有点文艺气质啊。结果回头一看,放屁。

    在文科班,不仅要听历史老师整天灌输阶级斗争的理论,更要忍受数学课上一群女生疑惑的哀叹声。

    开始我甚至会后悔。不过慢慢地,还是带了点感情了吧现在。

    我会在这个方向上越走越远吗,又或者只是生命的一段绕路。

    不知道。

    也许,我也不知道什么是“我所理解的生活”。

  • 开源,让金山更美好

    才在百度C语言吧看到有人说金山卫士开源了,所以特地去Google了一下,发现了这个网址:

    http://code.ijinshan.com/

    第一次看到有国内的这方面的软件开源。不管别人怎么说,做好自己吧!界面貌似是MFC写的,看不懂了。

    希望国内越来越多的软件走向开源。也希望用户们早日扔掉360这个垃圾。

  • 我所习惯的C/C++代码格式

    1.函数开始的大括号专起一行。

    int sample()
    {
        return 0xA0246 * 0454;
    }

    2.类定义和内部内联函数定义。

    class foo {
    public:
        foo() : data(0)
        {}
        foo(int d) : data(d)
        {}
        void print()
         {  printf("%d\n", data);  }
    private:
         int data;
    };

    3.for、while、if等关键字后面1空格,且总是使用大括号。

    while (in != 0) {
        s += in;
    }

    4.头文件使用的保护。

    #ifndef FILE_H
    #deinfe FILE_H
    
    // ...
    
    #endif // FILE_H

    5.缩进4空格。

    6.类定义和实现、变量声明和函数定义勤注释。

    7.太多了,最基本的就这些。

  • 符号“∑”和“Π”的用法。

    在数学中,符号“∑”和“Π”分别用来表示求和与求积。

    首先是函数的累积求和,n取[m, k]中的连续整数值。

    Definition of Sigma

    这个变量n可以换成其他任意字母,比如x。我们把下面的“n=m”和上面的“k”称作这个和式的下标。在上下文明确的情况下,下标可以省略。

    求和符号同样可以表示无穷级数。

    Definition of Sigma
    Definition of Sigma

    求和与求积的用法是完全相同的。当下标不是连续整数时,下标也可以有不同的表达方式。

    Example of Pi
    (A表示所有正素数构成的集合)
    Example of Sigma
    (“a|b”表示b能整除a,该和式表示所有10的正因子的和)

    最后附上一些常见的求和公式。

    Example of Sigma
    Example of Sigma
    Example of Sigma
    Example of Sigma
    Example of Sigma
  • Linux下程序创建进程

    进程是操作系统中运行的程序实例。而多进程程序和多线程程序相比,具有更健壮,更简单的特点。

    在GNU/Linux操作系统中,创建一个新进程,可以使用fork,clone函数以及使用exec函数族调用其他程序替换当前进程镜像。

    这里主要讲fork函数。

    fork函数的原型为:

    #include <unistd.h>
    
    pid_t fork(void);

    pid_t是系统定义的类型,一般被定义为short int。

    这里看一个最简单的调用示例。

    #include <stdio.h>
    
    #include <unistd.h>
    
    int main(void)
    {
        pid_t pid;
        pid = fork();
        printf("My process ID is %d.\n", getpid());
        return 0;
    }

    这样就最简单的创建了一个子进程,并且打印出了进程的pid。

    fork函数是分裂执行的,这也就是fork(分叉)命名的原因吧。如何理解这个“分裂”呢,看这段程序。

    #include <stdio.h>
    #include <unistd.h>
    
    int main(void)
    {
        pid_t pid;
        pid = fork();
        if (pid > 0) {
            printf("I'm parent process.\n");
            /* 父进程的pid大于0 */
        } else if (pid == 0) {
            printf("I'm child process.\n");
            /* 子进程的pid等于0 */
        } else {
            printf("Cannot create process.\n");
            /* 如果pid小于0,表示出错 */
        }
        return 0;
    }

    内核在第一次调用fork()时,将当前进程的所有内存空间和文件描述符等资源复制一份给创建的子进程(实际上采用了“Copy-on-write”(写时复制),第一次试图对内存进行写操作的时候才复制,提高了效率)。所以fork调用后有2个进程在同时执行后面的代码。如何区分呢?

    在父进程中,pid变量被标记为一个正整数;而子进程的pid被标记为0;当然,当pid为负数时,系统只有1个进程运行,表示创建进程出错。

    如何证明这一点呢?看下面这个程序:

    #include <stdio.h>
    #include <unistd.h>
    
    int main(void)
    {
        pid_t pid1, pid2, pid3;
        pid1 = fork();
        pid2 = fork();
        pid3 = fork();
        printf("I'm a process.\n");
        return 0;
    }

    这个程序会打印出几个”I’m a process.”字符串?答案是8个(2的3次方)。如果再加一个fork答案就会是16个(2的4次方)。运行结果证明了这一点。第一次fork调用产生一个子进程,第二次两个进程各产生一个,第三次四个进程各又产生一个……所以结果是2*2*2=8个。(fork炸弹?哈哈)

    现在你会发现,调用fork()的程序需要ctrl+c才能退出。这是因为父进程在等待子进程退出。如果父进程在子进程结束之前退出了,那么子进程就会成为所谓的“僵尸进程”。

    要结束子进程可以使用wait函数。

    #include <unistd.h>
    
    pid_t wait(int * status);

    返回退出子进程的pid。调用后可以从status了解到wait的调用状态。

    以下的宏可以用来校验status变量。

    WIFEXITED正常退出,值为true
    WEXITSTATUS返回子进程exit状态,为int
    WIFSIGNALED子进程是否因为信号结束,是则为true
    WTERMSIG返回子进程退出的信号号(上个宏为true时才有意义)
    评估wait状态所用的宏

    如果当前有多个子进程,系统怎么能知道我要结束哪一个呢?所以,有了waitpid函数。

    #include <unistd.h>
    
    pid_t waitpid(pid_t pid, int * status, int options);

    pid参数表示需要等待结束的子进程。几种值的情况如下:

    >0等待pid的进程退出
    0等待任何一个与调用进程组ID相同的子进程退出
    -1等待任何一个子进程退出(相当于wait())
    <-1等待任何一个组ID与pid参数绝对值相同的子进程退出
    waitpid的pid参数值

    options提供了一些额外的选项来控制waitpid,目前只支持WNOHANG和WUNTRACED两个选项,可以用|连接起来。

    WNOHANG,如果没有子进程退出,它也会立即返回,不会一直等下去。

    WUNTRACED,用于跟踪调试。

    如果不想用options,可以传一个参数0。

    waitpid的status多了两个校验的宏,不过仅在设置WUNTRACED后可用。

    WIFSTOPPED如果子进程已经停止,返回true
    WSTOPSIG返回使子进程停止的型号(上个宏为true时才有意义)
    waitpid增加的校验宏

    如果调用出错,返回-1,并且errno被设置成特定的值;如果WNOHANG被设置且没有子进程退出,返回0;否则返回子进程的pid号。

    不过,有时候不需要这么麻烦。

    #include <unistd.h>
    #include <signal.h>
    
    int kill(pid_t pid, int sig_num);

    kill用于向进程发送信号。pid的各种值情况如下表:

    >0信号发送到pid指定的进程
    0信号发送到调用进程同组的所有进程
    -1信号发送到除init外的所有进程
    <0信号发送到pid绝对值指定进程组中所有进程
    kill函数的参数pid的值

    还有函数raise,用于给自己发信号。

    #include <unistd.h>
    #include <signal.h>
    
    int raise(int sig_num);
    /* 等价于 kill(getpid(), sig_num); */

    所以,终止子进程也可以这样:

    #include <unistd.h>
    #include <signal.h>
    
    int main(void)
    {
        pid_t pid = fork();
        kill(pid, SIGKILL);
        return 0;
    }

    这里讲了fork函数创建子进程的一些用法。关于exec函数族和信号,将会在以后的文章里说到。

    希望这篇文章能带给您以收获!

  • C语言中数值和字符串的相互转换

    整数->字符串可以使用stdio.h中的sprintf函数,有的人可能会说到itoa,但其实itoa不是C标准库的函数,是微软自己添加的。

    sprintf的原型是:

    int sprintf ( char * str, const char * format, ... );

    和printf用法相同。当然也可用于其它类型如double。

    例:

    char str[20];
    int s = 1000000;
    sprintf(str, "%d", s);

    字符->整数同样使用的也是stdio.h中的sscanf函数,stdlib.h中也有atoi和strtol可以进行转换。

    int sscanf ( const char * str, const char * format, ... );
    int atoi ( const char * str );
    long int strtol ( const char * nptr, char ** endptr, int base);

    sscanf和atoi的用法都很简单。值得一提的是strtol这个函数。第一个参数是源字符串,第二个参数用于接收非法字符串的首地址,第三个参数是转换后的进制。

    什么叫非法字符串的首地址呢?比如nptr的值是”1234f5eg”,base是10,endptr在调用后的值就是”f5eg”。如果base是16,那么endptr的值就是”g”(f和e是16进制的合法字符,而在10进制中却不是)。可以看出非法字符的类型和base有关。由于要修改指针的值,所以需要用到二重指针。另外,开头和结尾的空格会被忽略,中间的空格会被视为非法字符。

    例:

    char buf[] = "12435 fawr22g"
    char *stop;
    printf("%d\n", (int)strtol(buf,&stop,10));
    printf("%s\n",stop);

    输出结果为

    12435
    
     fawr22g

    另外,给出一个atoi的实现(glibc里的atoi是直接用strtol实现的):

    #include <string.h>
    #include <ctype.h>
    
    int atoi(const char *s)
    {
        int sign = (s[0] == '-') ? -1 : 1;
        int i, j, res = 0;
        int b = 1;
        for (j = strlen(s) - 1; j > i; --j) {
            b *= 10;
        }
        for (i = isdigit(s[0]) ? 0 : 1; i < strlen(s); ++i) {
            res += (s[i] - '0') * b;
            b /= 10;
        }
        return res;
    }