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

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终端编译、运行程序。
rn_xtcxyczjh-2 通用+回调2 [查找最大值、求和 访问字符串(进程虚拟存储空间.段)]_第1张图片

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终端编译运行程序。
rn_xtcxyczjh-2 通用+回调2 [查找最大值、求和 访问字符串(进程虚拟存储空间.段)]_第2张图片

源代码目录。
../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终端编译、运行程序。
rn_xtcxyczjh-2 通用+回调2 [查找最大值、求和 访问字符串(进程虚拟存储空间.段)]_第3张图片

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

/* 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终端编译、运行程序。
rn_xtcxyczjh-2 通用+回调2 [查找最大值、求和 访问字符串(进程虚拟存储空间.段)]_第4张图片

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

一个Linux进程的虚拟存储空间。
rn_xtcxyczjh-2 通用+回调2 [查找最大值、求和 访问字符串(进程虚拟存储空间.段)]_第5张图片
来自《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]

你可能感兴趣的:(c,程序设计方法)