2015.09.19 – 09.20
读xtcxyczjh(系统程序员成长计划)—- 学习程序设计方法。
本次笔记基于“rn_xtcxyczjh-I 功能 封装 通用 回调“中y15m914的代码。笔记中的表达极差。笔记基于的源代码保存地址为:pxtcxyczjh-SourceII。
需求简述。
对一个存放整数的双向链表,找出链表中的最大值。
对一个存放整数的双向链表,累加链表中所有整数。
准备。
用回调函数的方法实现需求:双向链表提供遍历链表的功能(分内),找最大值和求和的功能留给用户自己编写(分外)。
代码。
指定(用户编写的)双向链表求和回调函数类型。
参数:双向链表节点中的元素(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");
}
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;
}
源代码目录。
../xtcxyczjh/y15m9d19/
需求简述。
对一个存放字符串的双向链表,把存放在其中的字符串转换成大写字母。
准备。
字符串转换的功能不属于双向链表分内之事 + 处理双向链表的通用性,采取用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中。
另外,在创建新的节点时,插入节点的值可由调用者传入。
/* 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;
}
源代码目录。
../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
堆是最灵活的一种内存,它的生命周期完全由使用者控制。使用堆内存时请注意两个问题:
valgrind
读《xtcxyczjh》-Part-II pnote over.
[2015.09.21-15:31]