C 在不使用宏的情况下简化并减少代码量

C 在不使用宏的情况下简化并减少代码量,c,optimization,coding-style,macros,C,Optimization,Coding Style,Macros,有时,在某些情况下,重复简单代码块是不可避免的。为了进行说明,请使用以下示例代码: 注意:此代码仅用于说明,实际代码更大、更复杂。它也可能包含错误,但问题的关键不在于此 开关(cmd){ case CMD_BLOCK_读取: if(当前用户!=密钥){ 错误(“访问冲突-无效密钥!”); res=CR\u访问被拒绝; 打破 } 如果(当前状态new_state; switch(cmd_desc->io_dir) { case IO_READ: if(block_read(id

有时,在某些情况下,重复简单代码块是不可避免的。为了进行说明,请使用以下示例代码:

注意:此代码仅用于说明,实际代码更大、更复杂。它也可能包含错误,但问题的关键不在于此

开关(cmd){
case CMD_BLOCK_读取:
if(当前用户!=密钥){
错误(“访问冲突-无效密钥!”);
res=CR\u访问被拒绝;
打破
}
如果(当前状态<状态忙){
警告(“此状态下不允许使用命令%s!”,cmd_name[cmd]);
res=不允许的CR;
打破
}
如果(ioctl(fd,HPI\u CTL\u BR)!=0){
警告(“握手失败(%s)。已中止!”,strerror(errno));
res=CR\u超时;
转到邮政局;
}
如果(块读取(id)!=0){
错误(“读取%d个块(%s)失败!已中止!”,id,strerror(errno));
res=CR_故障;
goto send_nop;
}
res=成功;
打破
case CMD_BLOCK_WRITE:
if(当前用户!=密钥){
错误(“访问冲突-无效密钥!”);
res=CR\u访问被拒绝;
打破
}
如果(当前状态<状态忙){
警告(“此状态下不允许使用命令%s!”,cmd_name[cmd]);
res=不允许的CR;
打破
}
如果(ioctl(fd,HPI\u CTL\u BR)!=0){
警告(“握手失败(%s)。已中止!”,strerror(errno));
res=CR\u超时;
转到邮政局;
}
如果(块写入(id)!=0){
错误(“未能写入%d个块-%s。命令已中止!”,id,strerror(errno));
res=CR_故障;
goto send_nop;
}
res=成功;
打破
案例CMD\U REQ\U START:
if(当前状态<状态就绪){
警告(“此状态下不允许使用命令%s!”,cmd_name[cmd]);
res=不允许的CR;
打破
}
state=state\u BUSY;
如果(ioctl(fd,HPI\u CTL\u BR)!=0){
警告(“握手失败(%s)。已中止!”,strerror(errno));
res=CR\u超时;
goto send_nop;
}
如果(块读取(id)!=0){
错误(“读取%d个块(%s)失败!已中止!”,id,strerror(errno));
res=CR_故障;
转到邮政局;
}
res=成功;
打破
}
/*其余28个左右类似的命令*/
}
正如您所看到的,由于小的差异和break/goto语句的广泛使用,不可能使用函数或内联线。我通常会定义一些宏:

/* NOTE: DO NOT USE these macros outside of Big Switch */
#define CHECK_KEY(_key) \
   if(current_user != (_key)) \
   { \
      ERROR("Access violation!"); \
      res = CR_ACCESS_DENIED; \
      break; \
   }
#define CHECK_STATE(_state) \
   if(current_state < _state) \
   { \
      WARN("Command %s is not allowed in this state!", cmd_name[cmd]); \
      res = CR_NOT_PERMITTED; \
      break; \
   }

#define HANDSHAKE(_fail) \
   if(ioctl(fd, CTL_BR) != 0) \
   { \
      WARN("Handshake failed (%s). Aborted!", strerror(errno)); \
      res = CR_TIME_OUT; \
      goto _fail; \
   }

#define BLOCK_READ(_id, _fail) \
   if(block_read((int)(_id))!= 0) \
   { \
      ERROR("Failed to read %d block (%s)! Aborted!", (int)_id, strerror(errno)); \
      res = CR_FAIL; \
      goto _fail; \
   }

#define BLOCK_WRITE(_id, _fail) \
   if(block_write((int)(_id)) != 0) \
   { \
      ERROR("Failed to write %d block - %s. Aborted!", (int)_id, strerror(errno)); \
      res = CR_FAIL; \
      goto _fail; \
   }
/*注意:不要在大交换机之外使用这些宏*/
#定义检查键(\u键)\
如果(当前用户!=(\u键))\
{ \
错误(“访问冲突!”)\
res=CR\u访问被拒绝\
中断\
}
#定义检查状态(\u状态)\
如果(当前状态<\u状态)\
{ \
警告(“此状态下不允许使用命令%s!”,cmd_name[cmd])\
res=不允许的CR\
中断\
}
#定义握手(\u失败)\
如果(ioctl(fd,CTL_BR)!=0)\
{ \
警告(“握手失败(%s)。已中止!”,strerror(errno))\
res=CR\u超时\
后进先出\
}
#定义块读取(\u id,\u fail)\
如果(块读取((int)((u id))!=0)\
{ \
错误(“读取%d个块(%s)失败!已中止!”,(int)\u id,strerror(errno))\
res=CR_故障\
后进先出\
}
#定义块写入(\u id,\u失败)\
如果(块写入((int)(_id))!=0)\
{ \
错误(“未能写入%d个块-%s。已中止!”,(int)\u id,strerror(errno))\
res=CR_故障\
后进先出\
}
…并使用它们编写相同的代码。代码变得越来越小,可读性也越来越强:

switch(cmd) 
{
case CMD_BLOCK_READ:
   CHECK_KEY(key);
   CHECK_STATE(STATE_BUSY);
   HANDSHAKE(post_resp);
   BLOCK_READ(id, send_nop);
   res = CR_SUCCESS;
   break;
case CMD_BLOCK_WRITE:
   CHECK_KEY(key);
   CHECK_STATE(STATE_BUSY);
   HANDSHAKE(post_resp);
   BLOCK_WRITE(id, send_nop);
   res = CR_SUCCESS;
   break;
case CMD_REQ_START:
{
   CHECK_STATE(STATE_READY);
   state = STATE_BUSY;
   HANDSHAKE(send_nop);
   BLOCK_READ(id, post_resp);
   res = CR_SUCCESS;
   break;
}
/* The remaining 28 or so similar commands */
<..>
开关(cmd)
{
case CMD_BLOCK_读取:
检查_键(KEY);
检查状态(状态为忙碌);
握手(post_resp);
块读取(id,发送\U nop);
res=成功;
打破
case CMD_BLOCK_WRITE:
检查_键(KEY);
检查状态(状态为忙碌);
握手(post_resp);
块写入(id,send\u nop);
res=成功;
打破
案例CMD\U REQ\U START:
{
检查_状态(状态_就绪);
state=state\u BUSY;
握手(send_nop);
块读取(id、后处理);
res=成功;
打破
}
/*其余28个左右类似的命令*/
代码看起来更像是某种脚本语言,而不是老式的C语言,而且非常难看,但为了可读性,我愿意牺牲它

问题是您如何应对类似情况?有哪些更优雅的解决方案和最佳实践


p.S.我承认,在一般情况下,宏和goto语句是糟糕设计的标志,因此无需大肆宣扬它们有多邪恶或我的编程风格有多糟糕。

我不想说Python源代码是组织的典范,但它包含(IMHO)宏用于简化复杂代码的一个很好的例子

Python主循环实现了一个基于堆栈的字节码执行VM。它包含一个巨大的开关大小写,Python支持的每个操作码都有一个大小写。操作码的分派如下所示:

case STORE_ATTR:
    w = GETITEM(names, oparg);
    v = TOP();
    u = SECOND();
    STACKADJ(-2);
    err = PyObject_SetAttr(v, w, u); /* v.w = u */
    Py_DECREF(v);
    Py_DECREF(u);
    if (err == 0) continue;
    break;
其中,
TOP
SECOND
STACKADJ
都定义为在堆栈对象上操作的宏。一些宏有备用的
#define
用于协助调试。所有操作码都是以这种方式编写的,通过用是一种微型脚本语言

在我看来,谨慎、明智和有限地使用宏可以提高代码可读性并使逻辑更清晰
case STORE_ATTR:
    w = GETITEM(names, oparg);
    v = TOP();
    u = SECOND();
    STACKADJ(-2);
    err = PyObject_SetAttr(v, w, u); /* v.w = u */
    Py_DECREF(v);
    Py_DECREF(u);
    if (err == 0) continue;
    break;

#define IO_NOOP    0
#define IO_READ    1
#define IO_WRITE   2

struct cmd_desc { 
   int check_key;     /* non-zero to do a check */
   int check_state;
   int new_state;
   void* handshake_fail;
   int io_dir;
   void* io_fail;
};

const struct cmd_desc cmd_desc_list[] = {
   { 1, STATE_BUSY,  -1,         &&post_resp, IO_READ,  &&send_nop },  /* CMD_BLOCK_READ */
   { 1, STATE_BUSY,  -1,         &&post_resp, IO_WRITE, &&send_nop },  /* CMD_BLOCK_WRITE */
   { 0, STATE_READY, STATE_BUSY, &&send_nop,  IO_READ,  &&post_rep }   /* CMD_REQ_START */
};

const struct cmd_desc* cmd_desc = cmds[cmd];

if(cmd_desc->check_key) {
   if(current_user != key) {
      ERROR("Access violation - invalid key!");
      return CR_ACCESS_DENIED;
   }
}

if(cmd_desc->check_state != -1) {
   if(current_state check_state) {
      WARN("Command %s is not allowed in this state!", cmd_name[cmd]);
      return CR_NOT_PERMITTED;
   }
}

if(cmd_desc->new_state != -1)
   state = cmd_desc->new_state;

switch(cmd_desc->io_dir) {
   case IO_READ:
      if(block_read(id) != 0) {
         ERROR("Failed to read %d block (%s)! Aborted!", id, strerror(errno));
         res = CR_FAIL;
         goto *cmd_desc->io_fail;
      }
      break;

   case IO_WRITE:
      if(block_write(id) != 0) {
         ERROR("Failed to write %d block (%s)! Aborted!", id, strerror(errno));
         res = CR_FAIL;
         goto *cmd_desc->io_fail;
      }
      break;

   case IO_NOOP:
      break;
}

res = CR_SUCCESS;