rn_xtcxyczjh-2 通用+回调2 [查找最大值、求和 访问字符串(进程虚拟存储空间.段)]

来源:互联网 发布:端口号 53 编辑:程序博客网 时间:2024/05/29 07:08

2015.09.19 – 09.20
读xtcxyczjh(系统程序员成长计划)—- 学习程序设计方法。

本次笔记基于“rn_xtcxyczjh-I 功能 封装 通用 回调“中y15m914的代码。笔记中的表达极差。笔记基于的源代码保存地址为:pxtcxyczjh-SourceII。

2015.09.19 - 查找 求和

需求简述。
对一个存放整数的双向链表,找出链表中的最大值。
对一个存放整数的双向链表,累加链表中所有整数。

准备。
用回调函数的方法实现需求:双向链表提供遍历链表的功能(分内),找最大值和求和的功能留给用户自己编写(分外)。

代码。
指定(用户编写的)双向链表求和回调函数类型。
参数:双向链表节点中的元素(void );避免使用全局变量的额外参数(void )。
返回值:void。

I 累积和
dlist.h(在dlist.h中定义双向链表值求和回调函数的类型)。

typedef void     (*pCallbackDlistVisitFunT)(void *ctx, void *data);

为使定义含义更明显,使用形参名(ctx为避免使用全局变量的额外参数,data为指向双向链表中数据的指针)。

dlist.c(在dlist.c中编写遍历双向链表节点(时调用双向链表求和的回调函数)的接口)。

/*dlist.c*///...... /* Visit every node of dlist */int dlist_foreach(pDlT pdl, pCallbackDlistVisitFunT pvisit, void *ctx){    unsigned int i, len;    if (NULL == pdl || NULL == pvisit)        return -1;    len = pdl->num;    pnd = pdl->pnd;    for (i = 0; i < len; ++i) {        pvisit(ctx, pnd->pe);        pnd = pnd->pn;    }    if (0 == i) return -1;    return 0;}

在dlist.h中声明dlist_foreach。

/*dlist.h*///...... int dlist_foreach(pDlT pdl, pCallbackDlistVisitFunT pvisit, void *ctx);

用户编写回调函数。
在其它文件如main.c中编写双向链表求和的回调函数。

/* Sum the integer data of dlist */static void sum_data2ctx(void *ctx, void *data){    long int *sum;    sum     = (long int *)ctx;    *sum    += *data;}

用户所有的回调函数暂时都只会在main中被调用,所以可将所有的回调函数都限制为static(避免全局函数名污染全局名字空间,造成重名等问题)。

改写dlist.c中的create_dlist()函数,让双向链表中的数据全为整型。

/*dlist.c*///……static int  itmp_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};//……/* Create len nodes for double linked list */pDlT create_dlist(unsigned int len){        //……    pf->pp  = pn;    pn->pn  = pf;    pn->pe  = &itmp_data[i-1];//原来的语句为pn->pe  = ctmp_data;    //……}

改写main函数,对双向链表求和。

/*main.c*//* Entry of C program */int main(void){    pDlT    pdl;    pNdT    pnd;    int i, len = 10;    long int sum = 0;    pdl     = create_dlist( len );    if (NULL == pdl)        return -1;    dlist_foreach(pdl, sum_data2ctx, &sum);    printf("Sum of dlist-I:%ld\n", sum);    for (i = 1; i <= len; ++i) {        pnd = alloc_node();        if (NULL == pnd) {            free_dlist(pdl);            return -1;        }        insert_node2dlist(pdl, pnd, i);    }    for (i = 1; i <=len; ++i) {        delete_node8dlist(pdl, i);    }    show_dlist(pdl, main_callback_show_dlist);    free_dlist(pdl);    return 0;}

修改dlist.c中显示双向链表元素的回调函数,让其正确显示链表内容。

/* dlist.c*/static void dlist_callback_show_dlist(pDlT pdl){    pNdT        pnd;    unsigned int    i, len;    len = pdl->num;    pnd = pdl->pnd;    for (i = 0; i < len; ++i) {        printf("%d ", *((int *)(pnd->pe)) );        pnd = pnd->pn;    }    //printf("%s", ((char *)(pnd->pe)) );    printf("\n");}

在linux终端编译、运行程序。
双向链表求和

II 查找最大值回调函数
到了此时,只需要在main.c中定义一个pCallbackDlistVisitFunT类型的函数,然后在函数内完成查找链表最大值功能即可。

/* Get the max number from dlist */static void max_data8dlist(void *ctx, void *data){    int *max, a, b;    max     = (int *)ctx;    a       = *max;    b       = *((int *)data);    *max    = a > b ? a : b;}

*ctx的初始值为双向链表的第一个元素。在main.c中访问不到双向链表的元素,故而在dlist.c中添加一个返回双向链表第i个元素的函数。

/* Get the i-th element of dlist */void * get_dlist_ith_elmt(pDlT pdl, unsigned int i){    int     len;    pNdT    pnd;    if (NULL == pdl)        return NULL;    len = pdl->num;    if (0 == len)        return NULL;    pnd = pdl->pnd;    for (i = 1; i < len; ++i)        pnd = pnd->pn;    return pnd->pe;}

并将get_dlist_ith_elmt函数声明在dlist.h中。

/*dlist.h*/void * get_dlist_ith_elmt(pDlT pdl, unsigned int i);

在main.c中测试求双向链表最大值的回调函数。

/* main.c *///……/* Entry of C program */int main(void){    pDlT    pdl;    pNdT    pnd;    int i, max, len = 10;    long int sum = 0;    pdl     = create_dlist( len );    if (NULL == pdl)        return -1;    // 获取双向链表中所有元素的和    dlist_foreach(pdl, sum_data2ctx, &sum);    printf("Sum of dlist-I:%ld\n", sum);    // 获取双向链表的第一个元素给max,然后找双向链表中的最大值    max = *(int*)get_dlist_ith_elmt(pdl, 1);    dlist_foreach(pdl, max_data8dlist, &max);    printf("The max value-I:%d\n", max);    //……    return 0;}

在linux终端编译运行程序。
寻找最大值

源代码目录。
../xtcxyczjh/y15m9d19/

2015.09.20 – 字符串大小写转换

需求简述。
对一个存放字符串的双向链表,把存放在其中的字符串转换成大写字母。

准备。
字符串转换的功能不属于双向链表分内之事 + 处理双向链表的通用性,采取用dlist_foreach()函数回调用户编写的将字符串转换成大写字母的函数lstr2ustr()。在站在调用者的调度来编写回调函数lstr2ustr()。之前先修改一个问题:双向链表的值只能在dlist.c中的create_dlist()函数中指定,调用者并不能决定每个链表中的值。现在修改双向链表的相关接口,然调用者决定往链表存什么值。

在创建双向链表时将双向链表中的所有元素都循环初始化为1,2, …, 0。

/* dlist.c *///……pDlT create_dlist(unsigned int len){    //…    //For doubly linked list node default    static int  itmp_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};    int ID_SIZE = 10;    //……    for (i = 1; i < len; ++i){        //…...        pp->pe  = &itmp_data[(i - 1) % ID_SIZE];        //…...    }    //……    pn->pe  = &itmp_data[(i - 1) % ID_SIZE];    //……    return pdl;}

将双向链表默认初值放在函数内部,避免全局变量。编译、运行程序,跟先前一样的结果。

然后提供一个可以初始化双向链表中任意一个节点的函数。

/* dlist.c *///….../* Assign i-th node */int assign_ith_node_value(pDlT pdl, void *data, unsigned int i){    pNdT        pnd;    unsigned int k, len;    if (NULL == data)        return -1;    len = pdl->num;    if (len < 1 || i < 1 || i > len)        return -1;    pnd = pdl->pnd;    for (k = 1; k < i; ++k)            pnd = pnd->pn;    pnd->pe = data;}   

在dlist.h中声明此函数。

在main函数中将双向链表初始化为字符串序列。

/* main.c */#include "dlist.h"#include <stdio.h>#include <ctype.h>#include <string.h>#include <stdlib.h>static void main_callback_show_dlist(void *data, unsigned int len);static void sum_data2ctx(void *ctx, void *data);static void max_data8dlist(void *ctx, void *data);#define DLIST_SIZE 10/* Entry of C program */int main(void){    pDlT    pdl;    //pNdT  pnd;    char    *pstr[DLIST_SIZE];    int i, j, max, slen, len = DLIST_SIZE;    long int sum = 0;    char *str[DLIST_SIZE] = {"i", "love", "you", "once", "i", "love", "you", "twice", "i", "love"};    pdl     = create_dlist( len );    if (NULL == pdl)        return -1;    // 获取双向链表中所有元素的和    dlist_foreach(pdl, sum_data2ctx, &sum);    printf("Sum of dlist-I:%ld\n", sum);    // 获取双向链表的第一个元素给max,然后找双向链表中的最大值    max = *(int*)get_dlist_ith_elmt(pdl, 1);    dlist_foreach(pdl, max_data8dlist, &max);    printf("The max value-I:%d\n", max);    // 将字符串载入RAM中    for (i = 0; i < DLIST_SIZE; ++i){        slen    = strlen(str[i]);        pstr[i] = (char *)malloc(slen + 1);        if (NULL == pstr[i]) {            for (j = i - 1; j >= 0; --j)                free(pstr[j]);            free_dlist(pdl);            return -1;        }        memcpy(pstr[i], str[i], slen + 1);    }    // 将RAM中的字符串一次赋值给双向链表的节点    for (i = 0; i < len; ++i)        assign_ith_node_value(pdl, pstr[i], i + 1);    show_dlist(pdl, main_callback_show_dlist);    for (i = 0; i < DLIST_SIZE; i++)        free(pstr[i]);    free_dlist(pdl);    return 0;}/* Callback function for show_dlist() */static void main_callback_show_dlist(void *data, unsigned int len){    printf("%s ", ((char *)data) );}/* Sum the integer data of dlist */static void sum_data2ctx(void *ctx, void *data){    long int *sum;    sum     = (long int *)ctx;    *sum    += *((int *)data);}/* Get the max number from dlist */static void max_data8dlist(void *ctx, void *data){    int *max, a, b;    max     = (int *)ctx;    a       = *max;    b       = *((int *)data);    *max    = a > b ? a : b;}

在main程序中,str[i]指向的的内容.rodata段中,保存在.rodata中的字符串不允许被修改。所以要将str中的内容拷贝到RAM中。

在linux终端编译、运行程序。
用户初始化双向链表为字符串

另外,在创建新的节点时,插入节点的值可由调用者传入。

/* dlist.c */ //….../* Allocate one node of doubly linked list */pNdT alloc_node(void *data){    pNdT    pnd;    pnd     = (pNdT)malloc( sizeof(NDT) );    if (NULL != pnd) {        pnd->pe = data;    }    return pnd;}

在dlist.h中更新alloc_node函数的声明。

现在可以来实现将双向链表中的字符串转换为大写的了。在main.c中编写回调函数lstr2ustr()。

/* main.c *///......#include <stdlib.h>//……/* Translate lower string to upper string */static void lstr2ustr(void *ctx, void *data){    char ch, *str;    str = (char *)data;    ch  = *str;    while (ch) {        if (islower(ch))            *str    = toupper(ch);        ++str;        ch  = *str;    }}

‘a’是一个字符常量,它的值在任何时候都 是97,但在不同语言中,97却不一定表’a’。我们不能简单的认为在97(‘a’)-122(‘z’)之间的字符就是小写字母,而是应该调用标准C函 数islower来判断,同样转换为大写应该调用toupper而不是减去一个常量

回调函数lstr2ustr()的类型为pCallbackDlistVisitFunT。调用库函数来转换字符而不是用常量,这些函数在

/* main.c *///……int main(void){    //……    show_dlist(pdl, main_callback_show_dlist);        // 将双向链表中的字符串转换为大写        dlist_foreach(pdl, lstr2ustr, NULL);        show_dlist(pdl, main_callback_show_dlist);    //…..    Return 0;}

在linux终端编译、运行程序。
双向链表字符串转大写运行结果

源代码目录。
../xtcxyczjh/y15m9d20/

一个Linux进程的虚拟存储空间。
一个Linux进程的虚拟存储器
来自《CSAPP》 2E-9.7.2

.bss
未初始化的全局变量(.bss段)用来存放那些没有初始化的和初始化为0的全局变量的。bss类型的全局变量只占运行时的内存空间,而不占用文件空间(可执行文件中记录了.bss段的大小)。

.data
初始化过的全局变量 (.data段)用来存放那些初始化为非零的全局变量。data类型的全局变量是即占文件空间,又占用运行时内存空间的。

.text
text段存放代码(如函数)和部分整数常量。

.rodata
rodata的意义同样明显,ro代表read only,rodata就是用来存放常量数据的。关rodata类型的数据,要注意以下几点:
(1)常量不一定就放在rodata里,有的立即数直接和指令编码在一起,存放在代码(.text)中。
(2)对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份拷贝。
(3)rodata是在多个进程间是共享的,这样可以提高运行空间利用率。
(4) rodata是在多个进程间是共享的,这样可以提高运行空间利用率。
(5) 在有的嵌入式系统中,rodata放在ROM(或者norflash)里,运行时直接读取,无需加载到RAM内存中。
(6) 在嵌入式linux系统中,也可以通过一种叫作XIP(就地执行)的技术,也可以直接读取,而无需加载到RAM内存中。
(7) 常量是不能修改的,修改常量在linux下会出现段错误。
这些机制由编译器(和操作系统)共定。

把在运行过程中不会改变的数据设为rodata类型的是有好处的:在多个进程间共享,可以大大提高空间利用率,甚至不占用RAM空间。同 时由于rodata在只读的内存页面(page)中,是受保护的,任何试图对它的修改都会被及时发现,这可以提高程序的稳定性。字符串会被编译器自动放到rodata中,其它数据要放到rodata中,只需要加const关键字修饰就好了

Stack
栈用于存放临时变量和函数参数。根栈相关的笔记有“ 一个C源文件到可执行文件 [反汇编-函数栈帧 编译 链接]”、“ [Hb-XVII] 计算机的抽象层次-简 使用寄存器 使用内存空间 程序执行过程 使用main函数规定 不定参数函数机制 C”、“ [CSAPP-I] 过程(函数栈帧) C语句的机器级表示(gcc -S)”。

Heap
堆是最灵活的一种内存,它的生命周期完全由使用者控制。使用堆内存时请注意两个问题:

  • malloc/free要配对使用。内存分配了不释放我们称为内存泄露(memory leak),内存泄露多了迟
    早会出现Out of memory的错误,再分配内存就会失败。当然释放时也只能释放分配出来的
    内存,释放无效的内存,或者重复free都是不行的,会造成程序crash。
  • 分配多少用多少。分配了100字节就只能用100字节,不管是读还是写,都只能在这个范围
    内,读多了会读到随机的数据,写多了会造成的随机的破坏。这种情况我们称为缓冲区溢出
    (buffer overflow),这是非常严重的,大部分安全问题都是由缓冲区溢出引起的。

linux下检查内存泄露或缓冲区溢出的工具

valgrind

读《xtcxyczjh》-Part-II pnote over.
[2015.09.21-15:31]

0 0