np_xtcxyczjh-II 通用+回调II [查找最大值、求和 访问字符串(进程虚拟存储空间.段)]
2015-09-21 15:31
465 查看
读xtcxyczjh(系统程序员成长计划)—- 学习软件设计方法,非算法。2015.09.19 – 09.20
本次笔记基于“np_xtcxyczjh-I 功能 封装 通用 回调“中y15m914的代码。笔记中的表达极差。笔记基于的源代码保存地址为:pxtcxyczjh-SourceII。
对一个存放整数的双向链表,找出链表中的最大值。
对一个存放整数的双向链表,累加链表中所有整数。
准备。
用回调函数的方法实现需求:双向链表提供遍历链表的功能(分内),找最大值和求和的功能留给用户自己编写(分外)。
代码。
指定(用户编写的)双向链表求和回调函数类型。
参数:双向链表节点中的元素(void );避免使用全局变量的额外参数(void )。
返回值:void。
I 累积和
dlist.h(在dlist.h中定义双向链表值求和回调函数的类型)。
为使定义含义更明显,使用形参名(ctx为避免使用全局变量的额外参数,data为指向双向链表中数据的指针)。
dlist.c(在dlist.c中编写遍历双向链表节点(时调用双向链表求和的回调函数)的接口)。
在dlist.h中声明dlist_foreach。
用户编写回调函数。
在其它文件如main.c中编写双向链表求和的回调函数。
用户所有的回调函数暂时都只会在main中被调用,所以可将所有的回调函数都限制为static(避免全局函数名污染全局名字空间,造成重名等问题)。
改写dlist.c中的create_dlist()函数,让双向链表中的数据全为整型。
改写main函数,对双向链表求和。
修改dlist.c中显示双向链表元素的回调函数,让其正确显示链表内容。
在linux终端编译、运行程序。
II 查找最大值回调函数
到了此时,只需要在main.c中定义一个pCallbackDlistVisitFunT类型的函数,然后在函数内完成查找链表最大值功能即可。
*ctx的初始值为双向链表的第一个元素。在main.c中访问不到双向链表的元素,故而在dlist.c中添加一个返回双向链表第i个元素的函数。
并将get_dlist_ith_elmt函数声明在dlist.h中。
在main.c中测试求双向链表最大值的回调函数。
在linux终端编译运行程序。
源代码目录。
../xtcxyczjh/y15m9d19/
对一个存放字符串的双向链表,把存放在其中的字符串转换成大写字母。
准备。
字符串转换的功能不属于双向链表分内之事 + 处理双向链表的通用性,采取用dlist_foreach()函数回调用户编写的将字符串转换成大写字母的函数lstr2ustr()。在站在调用者的调度来编写回调函数lstr2ustr()。之前先修改一个问题:双向链表的值只能在dlist.c中的create_dlist()函数中指定,调用者并不能决定每个链表中的值。现在修改双向链表的相关接口,然调用者决定往链表存什么值。
在创建双向链表时将双向链表中的所有元素都循环初始化为1,2, …, 0。
将双向链表默认初值放在函数内部,避免全局变量。编译、运行程序,跟先前一样的结果。
然后提供一个可以初始化双向链表中任意一个节点的函数。
在dlist.h中声明此函数。
在main函数中将双向链表初始化为字符串序列。
在main程序中,str[i]指向的的内容.rodata段中,保存在.rodata中的字符串不允许被修改。所以要将str中的内容拷贝到RAM中。
在linux终端编译、运行程序。
另外,在创建新的节点时,插入节点的值可由调用者传入。
在dlist.h中更新alloc_node函数的声明。
现在可以来实现将双向链表中的字符串转换为大写的了。在main.c中编写回调函数lstr2ustr()。
‘a’是一个字符常量,它的值在任何时候都 是97,但在不同语言中,97却不一定表’a’。我们不能简单的认为在97(‘a’)-122(‘z’)之间的字符就是小写字母,而是应该调用标准C函 数islower来判断,同样转换为大写应该调用toupper而不是减去一个常量。
回调函数lstr2ustr()的类型为pCallbackDlistVisitFunT。调用库函数来转换字符而不是用常量,这些函数在
在linux终端编译、运行程序。
源代码目录。
../xtcxyczjh/y15m9d20/
一个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),这是非常严重的,大部分安全问题都是由缓冲区溢出引起的。
读《xtcxyczjh》-Part-II pnote over.
[2015.09.21-15:31]
本次笔记基于“np_xtcxyczjh-I 功能 封装 通用 回调“中y15m914的代码。笔记中的表达极差。笔记基于的源代码保存地址为:pxtcxyczjh-SourceII。
2015.09.19 - 查找 求和
需求简述。对一个存放整数的双向链表,找出链表中的最大值。
对一个存放整数的双向链表,累加链表中所有整数。
准备。
用回调函数的方法实现需求:双向链表提供遍历链表的功能(分内),找最大值和求和的功能留给用户自己编写(分外)。
代码。
指定(用户编写的)双向链表求和回调函数类型。
参数:双向链表节点中的元素(void );避免使用全局变量的额外参数(void )。
返回值:void。
I 累积和
dlist.h(在dlist.h中定义双向链表值求和回调函数的类型)。
[code]typedef void (*pCallbackDlistVisitFunT)(void *ctx, void *data);
为使定义含义更明显,使用形参名(ctx为避免使用全局变量的额外参数,data为指向双向链表中数据的指针)。
dlist.c(在dlist.c中编写遍历双向链表节点(时调用双向链表求和的回调函数)的接口)。
[code]/*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。
[code]/*dlist.h*/ //...... int dlist_foreach(pDlT pdl, pCallbackDlistVisitFunT pvisit, void *ctx);
用户编写回调函数。
在其它文件如main.c中编写双向链表求和的回调函数。
[code]/* 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()函数,让双向链表中的数据全为整型。
[code]/*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函数,对双向链表求和。
[code]/*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中显示双向链表元素的回调函数,让其正确显示链表内容。
[code]/* 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类型的函数,然后在函数内完成查找链表最大值功能即可。
[code]/* 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个元素的函数。
[code]/* 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中。
[code]/*dlist.h*/ void * get_dlist_ith_elmt(pDlT pdl, unsigned int i);
在main.c中测试求双向链表最大值的回调函数。
[code]/* 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。
[code]/* 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; }
将双向链表默认初值放在函数内部,避免全局变量。编译、运行程序,跟先前一样的结果。
然后提供一个可以初始化双向链表中任意一个节点的函数。
[code]/* 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函数中将双向链表初始化为字符串序列。
[code]/* 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终端编译、运行程序。
另外,在创建新的节点时,插入节点的值可由调用者传入。
[code]/* 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()。
[code]/* 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。调用库函数来转换字符而不是用常量,这些函数在
[code]/* 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进程的虚拟存储空间。
来自《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]
相关文章推荐
- 20150921之前的信息泄露搜集-国内搜索引擎
- 低效程序员的7个坏习惯
- [转] 使用Spring Boot和Gradle创建项目
- 监听列表ListVIew的滑动状态
- 事件流机制
- Xcode报The identity used to sign the executable is no longer valid
- android ListView详解
- 中国 省市区县 数据库SQL 脚本
- 暑假第9周周报
- 相亲时如何表现自己
- RAID5服务器磁盘阵列,两块盘坏了数据恢复
- Cornerstone 破解的简单方法
- Maven3 安装使用(一)
- Android中同一个button按钮每次点击响应不同的事件
- 在Darwin层建立Notification监听锁屏/解屏事件
- Centos6.5搭建svn服务器
- PostgresQL中的NUlls first/last功能
- 【干货】前方高能!如何保障Python应用的高性能
- 数据结构之树
- 面向对象