rn_xtcxyczjh-3 整理1[小公码(宏 错误包装) Makefile]

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

在y15m9d20目录下的代码虽然只有400余行,但我觉得,凭我的水平,若此时不及时挺下来整理,等代码捎再多一点就可能整理不了了。整理y15m9d20后的代码y15m9d22保存地址:y15m9d22。

整理y15m9d20包括。

  • 编写子函数/宏代替多个函数中的公码。公码的代码量可能比较少,比如对函数指针参数NULL的判断,对库函数/自定义函数返回值的判断。
  • Makefile
  • 【优化。(程序只是创建10来个节点作为练习程序设计的载体,这似乎根本不会让人感觉到关于运行效率和内存空间的压力。重要的是,代码还未完结,所以暂时不做优化)】
    那么,今天的目标要做完前2件事情。

1. 小公码

(1) 宏

将各个函数中的小公码定义在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中的运行结果一致。

(2) 库函数/API的错误包装

库函数/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;
}

(3) 对封装的接口的错误包装

对于调用者,可对调用的函数进行错误封装。将这些函数定义在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中断编译、运行程序。
rn_xtcxyczjh-3 整理1[小公码(宏 错误包装) Makefile]_第1张图片

2. Makefile

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]

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