利用C预编译管理相似结构体

 

Daly(翻译, 有删减)

Lars Wirzenius   http://liw.iki.fi/liw/texts/cpp-trick.html 

介绍
       一般教科书或者编程指南中都建议减少宏的使用,从我的经验看,大量使用宏是错误的来源,经常会使人陷入混乱。 但当我 1997 年参与 HiBase 项目时学习到这个技巧后,使我很震惊。我发现在一些任务里,这个技巧可以避免大量相似结构体的内存分配,释放,初始化等问题。当然他不是万金油,但在特定的程序,这种技巧非常实用。
       ( 译者:通信协议中有大量不同种类但结构相似的数据包,如果为每一种包都定义一个结构体,并且对应不同的初始化 , 释放等函数,程序会变得很复杂庞大,管理也很麻烦。对于面向对象的语言 (Java/C++) 可以为每一种包定义一个类,通过继承,管理各种包的操作。但是 C 就不行 )
      
问题提出
    假设通信协议中有下面两种数据包,一个是鉴权包,一个是确认包
 struct AuthPacket {
    char *username;
    char *password;
};
 
struct AckPacket {
    int status;
    char *error_text;
};
 
两个结构是未经二进制编码的包结构,方便程序员管理,赋值。分别函数去操作是一件痛苦且容易出错的事,单是初始化就要为每一种包写一遍。如果一个新字段加进来,那么对应的初始化,释放,输出全部要改写,任何一项工作都很容易导致bug. (译者:如果不写函数直接对内部成员操作更糟,一是到处有重复的代码,二是难以保持数据的一致性,程序规模大了以后很难跟踪错误)
 
解决方案
 利用预编译定义一种新的”语言”去描述包。
PACKET(Auth,
    STRING(username)
    STRING(password)
)
 
PACKET(Ack,
    INTEGER(status)
    STRING(error_text)
)
  
描述放到头文件,这里用packet-desc.h (在wap网关软件kannel中是放到.def文件)。要定义一个实际的数据类型,可以这样:
struct Packet {
    int type;
    #define INTEGER(name) int name;
    #define STRING(name) char *name;
    #define PACKET(name, fields) /
        struct name { fields } name;
    #include "packet-desc.h"
};
 
编译器展开以后就是这个样子:
struct Packet {
    int type;
    struct Auth {
        char *username;
        char *password;
    } Auth;
    struct Ack {
        int status;
        char *error_text;
    } Ack;
};
 
除了包类型定义,还可以利用这些宏定义enum类型,内存管理,IO操作等。
讨论
在宏定义中,特别注意分号,逗号。这些都要非常小心。比起缺点,这种技巧带来的优点更明显。(译者: 协议软件中的数据包通常用到联合体(union),当要解析或者封装包的时候,如用传统方法,整个程序到处都是switch...case..结构。如果一个字段或者数据包结构改变,所有的switch...case...都得修改。大量地方修改同一项的结果是,容易导致不一致!)
关于本技巧的应用,可参见开源Wap网关Kannel源代码中对PDU的处理部分
 
例子代码
 
cpp-trick.h
#if !defined(PACKET) || !defined(INTEGER) || !defined(STRING)
#error Not all necessary macros were defined.
#endif
 
PACKET(Auth,
        STRING(username)
        STRING(password)
)
 
PACKET(Ack,
        INTEGER(status)
        STRING(error_text)
)
 
#undef PACKET
#undef INTEGER
#undef STRING
cpp-trick.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void panic(char *msg) {
        fprintf(stderr, "%s/n", msg);
        exit(EXIT_FAILURE);
}
 
void *xmalloc(size_t size) {
        void *p;
        
        p = malloc(size);
        if (p == NULL)
                panic("Out of memory");
        return p;
}
 
void *xstrdup(char *str) {
        char *copy;
        
        copy = xmalloc(strlen(str) + 1);
        strcpy(copy, str);
        return copy;
}
 
int read_integer(int *p) {
        if (fread(p, sizeof(int), 1, stdin) == 0)
                return 0;
        return 1;
}
 
char *read_string(void) {
        int len;
        char *str;
        
        if (read_integer(&len) == 0)
                return NULL;
        str = xmalloc(len + 1);
        if (fread(str, len, 1, stdin) != 1)
                return NULL;
        str[len] = '/0';
        return str;
}
 
void write_integer(int i) {
        if (fwrite(&i, sizeof(i), 1, stdout) != 1)
                panic("Write error.");
}
 
void write_string(char *str) {
        int len;
        
        len = (int) strlen(str);
        write_integer(len);
        if (fwrite(str, len, 1, stdout) != 1)
                panic("Write error.");
}
 
typedef enum {
        #define INTEGER(name)
        #define STRING(name)
        #define PACKET(name, fields) name,
        #include "cpp-trick.h"
} PacketType;
 
char *type_name(PacketType type) {
        #define INTEGER(name)
        #define STRING(name)
        #define PACKET(name, fields) /
                if (type == name) return #name;
        #include "cpp-trick.h"
        return "unknown";
}
 
 
typedef struct Packet {
        PacketType type;
        
        #define INTEGER(name) int name;
        #define STRING(name) char *name;
        #define PACKET(name, fields) struct name { fields } name;
        #include "cpp-trick.h"
} Packet;
 
 
Packet *packet_create(PacketType type) {
        Packet *packet;
        
        packet = xmalloc(sizeof(Packet));
        packet->type = type;
 
        #define INTEGER(name) p->name = 0;
        #define STRING(name) p->name = NULL;
        #define PACKET(name, fields) /
                { struct name *p = &packet->name; fields }
        #include "cpp-trick.h"
 
        return packet;
}
 
 
void packet_destroy(Packet *packet) {
        #define INTEGER(name)
        #define STRING(name) free(p->name); p->name = NULL;
        #define PACKET(name, fields) /
                { struct name *p = &packet->name; fields }
        #include "cpp-trick.h"
        free(packet);
}
 
void packet_dump(Packet *packet) {
        printf("Dumping packet %p:/n", (void *) packet);
        printf(" Type: %s/n", type_name(packet->type));
        #define INTEGER(name) printf(" %s: %d/n", #name, p->name);
        #define STRING(name) printf(" %s: %s/n", #name, p->name);
        #define PACKET(name, fields) /
                if (packet->type == name) /
                        { struct name *p = &packet->name; fields }
        #include "cpp-trick.h"
        printf("End of dump./n");
}
 
 
Packet *packet_read(void) {
        Packet *packet;
        int type;
        
        if (read_integer(&type) == 0)
                return NULL;
        packet = packet_create(type);
        #define INTEGER(name) read_integer(&p->name);
        #define STRING(name) p->name = read_string();
        #define PACKET(name, fields) /
                if (type == name) { struct name *p = &packet->name; fields }
        #include "cpp-trick.h"
        
        return packet;
}
 
 
void packet_write(Packet *packet) {
        write_integer(packet->type);
        #define INTEGER(name) write_integer(p->name);
        #define STRING(name) write_string(p->name);
        #define PACKET(name, fields) /
                if (packet->type == name) /
                        { struct name *p = &packet->name; fields }
        #include "cpp-trick.h"
}
 
 
int main(int argc, char **argv) {
        Packet *packet;
 
        if (argc != 2) {
                fprintf(stderr, "Usage: %s [read|write]/n", argv[0]);
                return EXIT_FAILURE;
        }
        
        if (strcmp(argv[1], "read") == 0) {
                while ((packet = packet_read()) != NULL) {
                        packet_dump(packet);
                        packet_destroy(packet);
                }
        } else {
                packet = packet_create(Auth);
                packet->Auth.username = xstrdup("guest");
                packet->Auth.password = xstrdup("demo");
                packet_write(packet);
                packet_destroy(packet);
 
                packet = packet_create(Ack);
                packet->Ack.status = 123;
                packet->Ack.error_text = xstrdup("Access denied.");
                packet_write(packet);
                packet_destroy(packet);
        }
 
        return 0;
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值