2015.09.22
读xtcxyczjh(系统程序员成长计划)—- 学习程序设计方法
在y15m9d20目录下的代码虽然只有400余行,但我觉得,凭我的水平,若此时不及时挺下来整理,等代码捎再多一点就可能整理不了了。整理y15m9d20后的代码y15m9d22保存地址:y15m9d22。
整理y15m9d20包括。
将各个函数中的小公码定义在tk.c(toolkit.c)中,声明在tk.h中。将y15m9d20内的所有代码拷贝到新建目录y15m9d22下,在y15m9d22下新建tk.c以及tk.h。
指针参数判断。
在dlist.c中,双向链表的接口在开头都会判断指针参数是否为NULL。有的指针为NULL时,函数就没必要再往下运行。对于这样的指针参数,判断方法都是一样的。
/* xx.c */
#define NULL_P_EXIT_VALUE 1
void NULL_p_exit(void *p)
{
if (NULL == p)
exit(NULL_P_EXIT_VALUE);
}
将函数参数定义为void *类型,就意味着这个函数可以判断任意的一级指针类型。如果p为NULL,那么就结束当前进程,返回(NULL_P_EXIT_VALUE & 0377)给父进程(本程序指调用main的进程)。不过,对于双向链表(库),它没有权利结束调用它接口的进程。
所以就算p为NULL,函数虽无进行下去的必要,但也不至于结束当前进程。此时就应该返回到进程调用函数的地方,让调用此函数的进程做进一步处理。这样的一段小公码可以改为生在tk.h中的宏,这样不仅能够使函数返回还能够省去过程调用开销。
/* tk.h */
/* 自定义宏以及声明tk.c中的函数 */
#include <stdio.h>
#include <stdlib.h>
/* 判定参数的宏定义 */
#define NULL_P_RETURN_VALUE -1
#define NULL_P_RETURN_NULL NULL
#define I_LESS_THAN_K_RETURN_VALUE -1
#define I_EQUAL_K_RETURN_VALUE -1
#define I_EQUAL_K_RETURN_NULL NULL
#define I_LESS_THAN_K_RETURN_NULL NULL
//p为NULL时返回NULL_P_RETURN_VALUE
#define NULL_P_RETURN_V(p) if (NULL == p){ \
fprintf(stderr, "%s:%d-%s\n", __FILE__, __LINE__, "Parameter is null"); \
return NULL_P_RETURN_VALUE; \
}
//p为NULL时返回NULL
#define NULL_P_RETURN_N(p) if (NULL == p){ \
fprintf(stderr, "%s:%d-%s\n", __FILE__, __LINE__, "Parameter is null"); \
return NULL_P_RETURN_NULL; \
}
//i < k时返回I_LESS_THAN_K_RETURN_VALUE
#define I_LESS_THAN_K_RETURN_V(i, k) if (i < k){ \
fprintf(stderr, "%s:%d-%s\n", __FILE__, __LINE__, "Parameter is null"); \
return I_LESS_THAN_K_RETURN_VALUE; \
}
//i < k时返回I_LESS_THAN_K_RETURN_NULL
#define I_LESS_THAN_K_RETURN_N(i, k) if (i < k){ \
fprintf(stderr, "%s:%d-%s\n", __FILE__, __LINE__, "Parameter is null"); \
return I_LESS_THAN_K_RETURN_NULL; \
}
//i == k时返回I_LESS_THAN_K_RETURN_VALUE
#define I_EQUAL_K_RETURN_V(i, k) if (i == k){ \
fprintf(stderr, "%s:%d-%s\n", __FILE__, __LINE__, "Parameter is null"); \
return I_EQUAL_K_RETURN_VALUE; \
}
//i == k时返回I_LESS_THAN_K_RETURN_NULL
#define I_EQUAL_K_RETURN_N(i, k) if (i == k){ \
fprintf(stderr, "%s:%d-%s\n", __FILE__, __LINE__, "Parameter is null"); \
return I_EQUAL_K_RETURN_NULL; \
}
现在来看看,哪些函数中的”参数判断代码“可以换成带参数的宏定义。
dlist.c
/* dlist.c */
#include "tk.h"
#include "dlist.h"
//……
/* Show doubly linked list node */
static int dlist_callback_show_dlist(pDlT pdl)
{
pNdT pnd;
unsigned int i, len;
//参数检查
NULL_P_RETURN_V(pdl);
pnd = pdl->pnd;
NULL_P_RETURN_V(pnd);
//……
return 0;
}
/* Create len nodes for double linked list */
pDlT create_dlist(unsigned int len)
{
int i;
pNdT pf, pp, pn, tmp;
pDlT pdl;
//For doubly linked list node default
static int itmp_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int ID_SIZE = 10;
//参数检查
I_LESS_THAN_K_RETURN_N(len, 0);
/* Fisrt node */
pf = (pNdT)malloc( sizeof(NDT) );
NULL_P_RETURN_N(pf);
//…...
}
/* Free len nodes for double linked list */
int free_dlist(pDlT pdl)
{
pNdT tpp, tpn;
unsigned int i, len;
//参数检查
NULL_P_RETURN_V(pdl);
tpp = pdl->pnd;
NULL_P_RETURN_V(tpp);
//Free nodes
tpn = pdl->pnd->pn;
len = pdl->num;
//……
return 0;
}
/* Insert node into doubly linked list */
int insert_node2dlist(pDlT pdl, pNdT pnd, unsigned int lc)
{
int i, len;
pNdT tpnd;
//参数检查
NULL_P_RETURN_V(pdl);
NULL_P_RETURN_V(pnd);
len = pdl->num;
I_LESS_THAN_K_RETURN_V(lc, 1);
I_LESS_THAN_K_RETURN_V(len + 1, lc);
I_EQUAL_K_RETURN_V(len, 0);
tpnd = pdl->pnd;
NULL_P_RETURN_V(tpnd);
//…...
}
/* Delete node form doubly linked list */
int delete_node8dlist(pDlT pdl, unsigned int lc)
{
int i, len;
pNdT tpnd;
//参数检查
NULL_P_RETURN_V(pdl);
len = pdl->num;
I_LESS_THAN_K_RETURN_V(lc, 1);
I_LESS_THAN_K_RETURN_V(len, lc);
tpnd = pdl->pnd;
NULL_P_RETURN_V(tpnd);
//…...
}
/* Show doubly linked list, called in dlist.c only */
static int dlist_show_dlist(pDlT pdl, pdlistCallerShowDlistFun pcallback_fun_show_dlist)
{
//参数检查
NULL_P_RETURN_V(pcallback_fun_show_dlist);
pcallback_fun_show_dlist(pdl);
return 0;
}
/* Show doubly linked list by call callback function which writed by caller */
int show_dlist(pDlT pdl, pCallbackShowDlistFunT pcallback_show_dlist_fun)
{
pNdT pnd;
unsigned int i, len;
//参数检查
NULL_P_RETURN_V(pdl);
pnd = pdl->pnd;
NULL_P_RETURN_V(pnd);
//…….
return 0;
}
/* Visit every node of dlist */
int dlist_foreach(pDlT pdl, pCallbackDlistVisitFunT pvisit, void *ctx)
{
pNdT pnd;
unsigned int i, len;
//参数检查
NULL_P_RETURN_V(pdl);
NULL_P_RETURN_V(pvisit);
//……
I_EQUAL_K_RETURN_V(i, 0);
return 0;
}
/* Get the i-th element of dlist */
void * get_dlist_ith_elmt(pDlT pdl, unsigned int i)
{
int len;
pNdT pnd;
//参数检查
NULL_P_RETURN_N(pdl);
len = pdl->num;
I_EQUAL_K_RETURN_N(0, len);
//……
}
/* Assign i-th node */
int assign_ith_node_value(pDlT pdl, void *data, unsigned int i)
{
pNdT pnd;
unsigned int k, len;
//参数检查
NULL_P_RETURN_V(pdl);
NULL_P_RETURN_V(data);
len = pdl->num;
I_LESS_THAN_K_RETURN_V(len, 1);
I_LESS_THAN_K_RETURN_V(i, 1);
I_LESS_THAN_K_RETURN_V(len, i);
pnd = pdl->pnd;
NULL_P_RETURN_V(pnd);
//……
}
更新对应函数、类型的定义、声明。
main.c中的函数(主函数main待其它小公码完成后再更新)。
/* main.c */
//…..
#include "tk.h"
//……
/* Callback function for show_dlist() */
static int main_callback_show_dlist(void *data, unsigned int len)
{
//参数检查
NULL_P_RETURN_V(data);
printf("%s ", ((char *)data) );
return 0;
}
/* Sum the integer data of dlist */
static int sum_data2ctx(void *ctx, void *data)
{
long int *sum;
//参数检查
NULL_P_RETURN_V(ctx);
NULL_P_RETURN_V(data);
//……
return 0;
}
/* Get the max number from dlist */
static int max_data8dlist(void *ctx, void *data)
{
int *max, a, b;
//参数检查
NULL_P_RETURN_V(ctx);
NULL_P_RETURN_V(data);
//……
return 0;
}
/* Translate lower string to upper string */
static int lstr2ustr(void *ctx, void *data)
{
char ch, *str;
//参数检查
NULL_P_RETURN_V(data);
//……
return 0;
}
更新对应函数、类型的定义、声明。在linux终端重新编译、运行程序。结果跟y15m9d20中的运行结果一致。
库函数/API的错误包装是指根据库函数/API的返回值判定它执行错误时的处理。到目前为止,程序调用的库函数有malloc/free,islower,toupper,printf,strlen。在本程序中,malloc返回值为NULL时往往要释放之前分配的空间,同时也不能断定此时进程就应该退出,故而可(或暂时)不对malloc进行错误包装。至于islower和toupper,它们的返回值单一,所以也不必进行错误包装。对printf和strlen也没什么好包装的。所以,库函数的错误包装可等满足包装条件的库函数或API出现时再对其进行包装。
如对创建进程的API fork()可对其进行如下错误包装(来自《CSAPP》 2E)。
void unix_error(char *msg) /* unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
/* $begin forkwrapper */
pid_t Fork(void)
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
}
对于调用者,可对调用的函数进行错误封装。将这些函数定义在tk.c中。
/* tk.c */
/* “库函数、自定义函数的错误包装函数”以及“各个函数中小公码函数”在tk.c中定义 */
#include "tk.h"
#include "dlist.h"
/* 输出msg信息后当前进程,返回0给父进程 */
void fun_error_exit(char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(0);
}
/* 创建链表函数的错误包装 */
pDlT Create_dlist(unsigned int len)
{
pDlT pdl;
pdl = create_dlist(len);
if(NULL == pdl)
fun_error_exit("When create list, Create doubly linked list failed");
return pdl;
}
/* 释放双向链表函数的错误包装 */
void Free_dlist(pDlT pdl)
{
int rv;
if ( (rv = free_dlist(pdl)) == NULL_P_RETURN_VALUE )
fprintf(stderr, "%s\n", "When free list, The doubly linked list is NULL, not permit free");
}
/* 向双向链表中插入节点函数的错误包装 */
void Insert_node2dlist(pDlT pdl, pNdT pnd, unsigned int lc)
{
int rv;
rv = insert_node2dlist(pdl, pnd, lc);
if (rv == NULL_P_RETURN_VALUE || rv == I_EQUAL_K_RETURN_VALUE)
fprintf(stderr, "%s\n", "When insert node, The doubly linked list or inserted node is NULL");
if (rv == I_LESS_THAN_K_RETURN_VALUE)
fprintf(stderr, "%s\n", "When insert node, Insert out of doubly linked list");
}
/* 删除双向链表某个节点函数的错误包装 */
void Delete_node8dlist(pDlT pdl, unsigned int lc)
{
int rv;
rv = delete_node8dlist(pdl, lc);
if (rv == I_LESS_THAN_K_RETURN_VALUE)
fprintf(stderr, "%s\n", "When delete node, Delete node out of doubly linked list");
if (rv == NULL_P_RETURN_VALUE)
fprintf(stderr, "%s\n", "When delete node, The doubly linked list is NULL");
}
/* 分配双向链表节点函数的错误包装 */
pNdT Alloc_node(void *data)
{
pNdT pnd;
pnd = alloc_node(data);
if (NULL == pnd)
fprintf(stderr, "%s\n", "When allocate node, allocate a node failed");
return pnd;
}
/* 遍历双向链表函数的错误包装 */
void Foreach_dlist(pDlT pdl, pCallbackDlistVisitFunT pvisit, void *ctx)
{
int rv;
if ( (rv = dlist_foreach(pdl, pvisit, ctx)) == NULL_P_RETURN_VALUE)
fprintf(stderr, "%s\n", "When foreach list, list or callback function is null");
}
/* 获取双向链表第i个节点的值的函数的错误包装 */
void * Get_dlist_ith_elmt(pDlT pdl, unsigned int i)
{
pNdT *p;
p = get_dlist_ith_elmt(pdl, i);
if (NULL_P_RETURN_NULL == p || I_EQUAL_K_RETURN_NULL)
fprintf(stderr, "%s\n", "When get list element, list is null");
return p;
}
/* 给双向链表第i个节点赋值的函数的错误包装 */
void Assign_ith_node_value(pDlT pdl, void *data, unsigned int i)
{
int rv;
rv = assign_ith_node_value(pdl, data, i);
if (NULL_P_RETURN_VALUE == rv)
fprintf(stderr, "%s\n", "When assign node value, list or data is null");
if (I_LESS_THAN_K_RETURN_VALUE == rv)
fprintf(stderr, "%s\n", "Whene assign node value, out of list");
}
将这些错误包装函数声明在tk.h文件中。如果函数参数有问题,在tk.h中定义的宏及在tk.c中的包装函数都会向标准错误输出流打印,宏打印文件名和行号[能作为一个检查参数错误的索引],包装函数打印具体的参数错误。
修改main.c中的主函数main。
/* main.c */
/* C语言程序入口main()函数所在文件 */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "dlist.h"
#include "tk.h"
//……
static int callback_show_int(void *data, unsigned int len);
/* Entry of C program */
int main(void)
{
pDlT pdl;
char *pstr[DLIST_SIZE];
int i, j, max, len, slen;
long int sum;
char *str[DLIST_SIZE] = {"i", "love", "you", "once", "i", "love", "you", "twice", "i", "love"};
max = 0;
sum = 0;
len = DLIST_SIZE;
// 创建双向链表
pdl = Create_dlist( len );
// 获取双向链表中所有元素的和
Foreach_dlist(pdl, sum_data2ctx, &sum);
show_dlist(pdl, callback_show_int);
printf("Sum of dlist-I:%ld\n", sum);
// 获取双向链表的第一个元素给max,然后找双向链表中的最大值
max = *(int*)Get_dlist_ith_elmt(pdl, 1);
Foreach_dlist(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);
// 将双向链表中的字符串转换为大写
Foreach_dlist(pdl, lstr2ustr, NULL);
show_dlist(pdl, main_callback_show_dlist);
for (i = 0; i < DLIST_SIZE; i++)
free(pstr[i]);
free_dlist(pdl);
return 0;
}
/* Callback function to print int element */
static int callback_show_int(void *data, unsigned int len)
{
NULL_P_RETURN_V(data);
printf("%d ", *((int *)data));
return 0;
}
//......
将dlist.c中打印双向链表元素值的语句(以及函数定义和声明)去(注释)掉,将显示用户等权利全部交给调用者。在linux中断编译、运行程序。
y15m9d22目录下共5个程序文件(dlist.c/dlist.h,tk.c/tk.h,main.c)。在编译器的预处理阶段,所有文件中包含的.h语句(预处理)将会被相应的.h文件中的内容替换;C/汇编编译器进而根据经过预处理的各个.c或者经过C编译器的.s文件生成独立的.obj(目标)文件;最后连接器按照C/汇编编译器输出的所有的.obj文件连接成最终的可执行文件(main)。“预处理器”、“C/汇编编译器”、“连接器”被集成在如今简说如gcc这样的编译器中。gcc *.c -o main -Wall这个命令一口气完成了“预处理”、“编译”、“连接”所有工作。
由.c文件直接生成可执行文件时,中间过程被隐蔽了。不够可以给gcc编译器相应的参数让gcc只开启“预处理器”或“C/汇编编译器”或“连接器”的功能。
如果需要gcc输出的这些中间结果,在生成可执行文件前需要在linux终端输入多个跟gcc相关的命令。道听途说在linux下准备好make环境后,将与编译器相关的命令写在一个名为Makefile的文件中,执行一个make [option]命令就可以执行在Makefile中的相应的编译命令。这样似乎可以避免在linux终端直接输入编译命令方式下重复输入的特点(还听说,Makefile对于管理多个文件的程序很是便利,不知道是真是假。当然如果只是需要像gcc *.c -main -Wall这样的简单命令,不写Makefile又何妨)。
以这种让人难以目睹表达继续说下去还不如直接按照Makefile的规则先为y15m9d22下的程序写一个简单的Makefile。在y15m9d22目录下新建一个Makefile文件。翻查先前渣记“Makefile基础”编写规则部分。
#Makefile-2015.09.22
#Default execute order
dlist: main.o dlist.o tk.o
gcc main.o dlist.o tk.o -o dlist
main.o: main.c dlist.h tk.h
gcc -c main.c
dlist.o: dlist.c dlist.h tk.h
gcc -c dlist.c
tk.o: tk.c dlist.h tk.h
gcc -c tk.c
clean:
-rm *.o
在linux终端运行make命令,生成可执行文件dlist。这是对y15m9d22目录下程序较为直接的一个版本,以前也写过类似的。根据这个版本逐之改为较为通用的版本。使用具有特殊含义的符号。
#Makefile-2015.09.22
#Default execute order
dlist: main.o dlist.o tk.o
gcc main.o dlist.o tk.o -o $@
main.o: main.c dlist.h tk.h
gcc -c $<
dlist.o: dlist.c dlist.h tk.h
gcc -c $<
tk.o: tk.c dlist.h tk.h
gcc -c $<
clean:
-rm *.o
规则中的目标用 @获取,规则中的第一个条件用 <获取。在linux终端执行make可生产dlist可执行文件。通过添加变量和进一步的利用具有特殊含义的符号再次改版。
#Makefile-2015.09.22
#Default execute order
dlist: main.o dlist.o tk.o
$(CC) $^ $(CFLAGS) $@
main.o: main.c dlist.h tk.h
$(CC) $(CFLAG_OBJ) $<
dlist.o: dlist.c dlist.h tk.h
$(CC) $(CFLAG_OBJ) $<
tk.o: tk.c dlist.h tk.h
$(CC) $(CFLAG_OBJ) $<
clean:
-rm *.o
CC=gcc
CFLAGS=-o
CFLAG_OBJ=-c
在linux终端运行make命令可得到执行正常的dlist可执行文件。
将今天Makefile的关键点汇总到这里。
• 欲更新(规则中的命令列表)目标必须先更新条件。
• 使用make命令时,如果指定一个目标,则只更新这个目标。如果不指定目标则更新Makefile中的第一个规则中的目标。
• @取当前规则中的目标; <取当前规则中的第一个条件;$^取当前规则中的所有条件。
• Makefile变量像C的宏定义一样,代表一串字符。在取值的地方展开(=)。
欧克,y15m9d22到此为止。
读《xtcxyczjh》-(Part-III) pnote over.
[2015.09.23-16:14]