2018年9月9日 星期日

sunifdef

今天又來介紹減輕看 code 負擔的小工具,感謝強者我朋友推薦。

就像筆者常講的,現在軟體工程師看 code 的時間要比 coding 多的多。看 C  code 時最怕的就是 code 裡面一堆 #if...#elif...#else:
/* copy from LwIP 1.4.1 */
int lwip_sendto(int s, const void *data, size_t size, int flags,
       const struct sockaddr *to, socklen_t tolen)
{
  struct lwip_sock *sock;
  err_t err;
  u16_t short_size;
  const struct sockaddr_in *to_in;
  u16_t remote_port;
#if !LWIP_TCPIP_CORE_LOCKING
  struct netbuf buf;
#endif

  sock = get_socket(s);
  if (!sock) {
    return -1;
  }

  if (sock->conn->type == NETCONN_TCP) {
#if LWIP_TCP
    return lwip_send(s, data, size, flags);
#else /* LWIP_TCP */
    LWIP_UNUSED_ARG(flags);
    sock_set_errno(sock, err_to_errno(ERR_ARG));
    return -1;
#endif /* LWIP_TCP */
  }

  /* @todo: split into multiple sendto's? */
  LWIP_ASSERT("lwip_sendto: size must fit in u16_t", size <= 0xffff);
  short_size = (u16_t)size;
  LWIP_ERROR("lwip_sendto: invalid address", (((to == NULL) && (tolen == 0)) ||
             ((tolen == sizeof(struct sockaddr_in)) &&
             ((to->sa_family) == AF_INET) && ((((mem_ptr_t)to) % 4) == 0))),
             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
  to_in = (const struct sockaddr_in *)(void*)to;

#if LWIP_TCPIP_CORE_LOCKING
  /* Should only be consider like a sample or a simple way to experiment this option (no check of "to" field...) */
  {
    struct pbuf* p;
    ip_addr_t *remote_addr;

#if LWIP_NETIF_TX_SINGLE_PBUF
    p = pbuf_alloc(PBUF_TRANSPORT, short_size, PBUF_RAM);
    if (p != NULL) {
#if LWIP_CHECKSUM_ON_COPY
      u16_t chksum = 0;
      if (sock->conn->type != NETCONN_RAW) {
        chksum = LWIP_CHKSUM_COPY(p->payload, data, short_size);
      } else
#endif /* LWIP_CHECKSUM_ON_COPY */
      MEMCPY(p->payload, data, size);
#else /* LWIP_NETIF_TX_SINGLE_PBUF */
    p = pbuf_alloc(PBUF_TRANSPORT, short_size, PBUF_REF);
    if (p != NULL) {
      p->payload = (void*)data;
#endif /* LWIP_NETIF_TX_SINGLE_PBUF */

      if (to_in != NULL) {
        inet_addr_to_ipaddr_p(remote_addr, &to_in->sin_addr);
        remote_port = ntohs(to_in->sin_port);
      } else {
        remote_addr = &sock->conn->pcb.ip->remote_ip;
#if LWIP_UDP
        if (NETCONNTYPE_GROUP(sock->conn->type) == NETCONN_UDP) {
          remote_port = sock->conn->pcb.udp->remote_port;
        } else
#endif /* LWIP_UDP */
        {
          remote_port = 0;
        }
      }

      LOCK_TCPIP_CORE();
      if (netconn_type(sock->conn) == NETCONN_RAW) {
#if LWIP_RAW
        err = sock->conn->last_err = raw_sendto(sock->conn->pcb.raw, p, remote_addr);
#else /* LWIP_RAW */
        err = ERR_ARG;
#endif /* LWIP_RAW */
      }
#if LWIP_UDP && LWIP_RAW
      else
#endif /* LWIP_UDP && LWIP_RAW */
      {
#if LWIP_UDP
#if LWIP_CHECKSUM_ON_COPY && LWIP_NETIF_TX_SINGLE_PBUF
        err = sock->conn->last_err = udp_sendto_chksum(sock->conn->pcb.udp, p,
          remote_addr, remote_port, 1, chksum);
#else /* LWIP_CHECKSUM_ON_COPY && LWIP_NETIF_TX_SINGLE_PBUF */
        err = sock->conn->last_err = udp_sendto(sock->conn->pcb.udp, p,
          remote_addr, remote_port);
#endif /* LWIP_CHECKSUM_ON_COPY && LWIP_NETIF_TX_SINGLE_PBUF */
#else /* LWIP_UDP */
        err = ERR_ARG;
#endif /* LWIP_UDP */
      }
      UNLOCK_TCPIP_CORE();
      
      pbuf_free(p);
    } else {
      err = ERR_MEM;
    }
  }
#else /* LWIP_TCPIP_CORE_LOCKING */
  /* initialize a buffer */
  buf.p = buf.ptr = NULL;
#if LWIP_CHECKSUM_ON_COPY
  buf.flags = 0;
#endif /* LWIP_CHECKSUM_ON_COPY */
  if (to) {
    inet_addr_to_ipaddr(&buf.addr, &to_in->sin_addr);
    remote_port           = ntohs(to_in->sin_port);
    netbuf_fromport(&buf) = remote_port;
  } else {
    remote_port           = 0;
    ip_addr_set_any(&buf.addr);
    netbuf_fromport(&buf) = 0;
  }

  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_sendto(%d, data=%p, short_size=%"U16_F", flags=0x%x to=",
              s, data, short_size, flags));
  ip_addr_debug_print(SOCKETS_DEBUG, &buf.addr);
  LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F"\n", remote_port));

  /* make the buffer point to the data that should be sent */
#if LWIP_NETIF_TX_SINGLE_PBUF
  /* Allocate a new netbuf and copy the data into it. */
  if (netbuf_alloc(&buf, short_size) == NULL) {
    err = ERR_MEM;
  } else {
#if LWIP_CHECKSUM_ON_COPY
    if (sock->conn->type != NETCONN_RAW) {
      u16_t chksum = LWIP_CHKSUM_COPY(buf.p->payload, data, short_size);
      netbuf_set_chksum(&buf, chksum);
      err = ERR_OK;
    } else
#endif /* LWIP_CHECKSUM_ON_COPY */
    {
      err = netbuf_take(&buf, data, short_size);
    }
  }
#else /* LWIP_NETIF_TX_SINGLE_PBUF */
  err = netbuf_ref(&buf, data, short_size);
#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
  if (err == ERR_OK) {
    /* send the data */
    err = netconn_send(sock->conn, &buf);
  }

  /* deallocated the buffer */
  netbuf_free(&buf);
#endif /* LWIP_TCPIP_CORE_LOCKING */
  sock_set_errno(sock, err_to_errno(err));
  return (err == ERR_OK ? short_size : -1);
}
當然可以利用 ctags 之類的工具查詢 symbol,不過根據腦科學指出,人的工作記憶(短期記憶)會隨著年齡遞減,所以年紀越大你越難理解上面這段 code 在說什麼(某些公司也別以為終於找到證據可以趕走那批礙眼的中年工程師了,因為腦科學表示工作記憶 30 歲就開始遞減,除非貴公司只收 30 歲以下的工程師!)

重點是既然查得出 symbol,自然就可以自動化刪除編譯後不存在的 #if...#elif #else 區塊。

直覺上這可以請 C/C++ compiler 代勞,很不幸的,compiler 會連同 header file 等一同展開,結果本想減輕讀 code 負擔變成加重負擔...冏(如果您覺得這樣比較好用也請留言分享,感謝!)

我們需要一個輕量級小工具,可以餵給他一些 symbol 定義幫我們刪掉不需要的分支就好,其他的地方不要亂動!這就是今天要介紹的 - sunifdef。sunifdef 是 son of unifdef 的意思,unifdef 源自 FreeBSD,可見歷史久遠,不過 Google 發現居然沒有人寫中文教學,所以寫這篇跟大家分享一下。

sunifdef 參數很多,不過以下範例應該可以解決大部分問題:
sunifdef.exe --file argfile --constant eval,del xyz.c
argfile 內容如下,因為預定義 symbol 通常不會只有一個,這樣比較省事:
-D IP_FRAG_USES_STATIC_BUF=0
-D LWIP_NETIF_TX_SINGLE_PBUF=0
-D IP_FRAG=1
-D IP_REASSEMBLY=1
-D LWIP_TCPIP_CORE_LOCKING=0
-D LWIP_TCP=1
-D LWIP_CHECKSUM_ON_COPY=0
--constant eval,del 告訴 sunifdef 碰到 #if... 時要如何處理,eval,del 代表計算出常數值 = 0 就刪除該區塊:
/*
#define IP_FRAG_USES_STATIC_BUF 0
#define IP_REASSEMBLY           1
*/
void foo(void)
{
#if (IP_FRAG_USES_STATIC_BUF==1) && (IP_REASSEMBLY==1)
/* 這裡會被清空...*/
#endif
}
把上述這些參數與 argfile 用在一開始的例子就會變成:
int lwip_sendto(int s, const void *data, size_t size, int flags,
       const struct sockaddr *to, socklen_t tolen)
{
  struct lwip_sock *sock;
  err_t err;
  u16_t short_size;
  const struct sockaddr_in *to_in;
  u16_t remote_port;
  struct netbuf buf;

  sock = get_socket(s);
  if (!sock) {
    return -1;
  }

  if (sock->conn->type == NETCONN_TCP) {
    return lwip_send(s, data, size, flags);
  }

  /* @todo: split into multiple sendto's? */
  LWIP_ASSERT("lwip_sendto: size must fit in u16_t", size <= 0xffff);
  short_size = (u16_t)size;
  LWIP_ERROR("lwip_sendto: invalid address", (((to == NULL) && (tolen == 0)) ||
             ((tolen == sizeof(struct sockaddr_in)) &&
             ((to->sa_family) == AF_INET) && ((((mem_ptr_t)to) % 4) == 0))),
             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
  to_in = (const struct sockaddr_in *)(void*)to;

  /* initialize a buffer */
  buf.p = buf.ptr = NULL;
  if (to) {
    inet_addr_to_ipaddr(&buf.addr, &to_in->sin_addr);
    remote_port           = ntohs(to_in->sin_port);
    netbuf_fromport(&buf) = remote_port;
  } else {
    remote_port           = 0;
    ip_addr_set_any(&buf.addr);
    netbuf_fromport(&buf) = 0;
  }

  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_sendto(%d, data=%p, short_size=%"U16_F", flags=0x%x to=",
              s, data, short_size, flags));
  ip_addr_debug_print(SOCKETS_DEBUG, &buf.addr);
  LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F"\n", remote_port));

  /* make the buffer point to the data that should be sent */
  err = netbuf_ref(&buf, data, short_size);
  if (err == ERR_OK) {
    /* send the data */
    err = netconn_send(sock->conn, &buf);
  }

  /* deallocated the buffer */
  netbuf_free(&buf);
  sock_set_errno(sock, err_to_errno(err));
  return (err == ERR_OK ? short_size : -1);
}
如何?這樣讀起來是不是輕鬆多了,雖然現在 VS Code 之類的編輯器已經處理的比較好了,但 sunifedf 還是有他的存在意義,例如經由 sunifdef 處理完再餵給 cflow2dot 可以得到比較有意義的結果,或是可以節省版面避免不存在的區塊消耗注意力,或是你跟筆者一樣懶得學新 editor XD

補充說明

win32 sunifdef 有個地方很兩光,就是 argfile 不會去搜尋環境變數 PATH,補救方法是使用一門快要失傳的手藝 - batch file,這也是跟強者我朋友學的,他是我見過最會寫 batch file 的人。範例如下:
@echo off

SET arg_filename=lwip.arg

FOR /F "tokens=* USEBACKQ" %%F IN (`where %arg_filename%`) DO (
SET arg_filefullpath=%%F
goto _break0
)

:_break0

For %%A in ("%arg_filefullpath%") do (
    Set arg_folder=%%~dpA
    goto _break1
)

:_break1

SET infile=%1%
SET tmpfile=%infile%.tmp
copy %infile% %infile%.org
sunifdef.exe -f %arg_folder%%arg_filename% --constant eval,del %infile% > %tmpfile%
type %tmpfile% > %infile%
del /F %tmpfile%
前面講了一大堆發現忘了給載點了,雖然 2008 年後 sunifdef 就停止開發了,不過 stackoverflow 上有人表示他用了很多年、用在很複雜刁鑽的案子上都沒問題,其實 C preprocessor 一直到 C++ 都沒有什麼變化,個人覺得可以放心使用 :)

4 則留言:

  1. 已加書籤。感謝分享!我們部門開發的韌體,裡面就有大量的#ifdef用來隔開不同專案的特殊處理需求。這樣寫真的很亂... :(

    回覆刪除
    回覆
    1. 要謝就謝強者我朋友吧!

      很多 open source 也這樣寫,習慣就好,也可以參考小弟其他幾篇文章,說不定雞尾酒療法效果更佳

      刪除
  2. https://sourceforge.net/projects/coan2/files/latest/download

    這個好像是新版的

    回覆刪除
    回覆
    1. 在 stackoverflow 看過,不過我沒用過

      刪除