2018年9月9日 星期日

sunifdef

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

就像筆者常講的,現在軟體工程師看 code 的時間要比 coding 多的多。看 C  code 時最怕的就是 code 裡面一堆 #if...#elif...#else:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* 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 參數很多,不過以下範例應該可以解決大部分問題:
1
sunifdef.exe --file argfile --constant eval,del xyz.c
argfile 內容如下,因為預定義 symbol 通常不會只有一個,這樣比較省事:
1
2
3
4
5
6
7
-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 就刪除該區塊:
1
2
3
4
5
6
7
8
9
10
/*
#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 用在一開始的例子就會變成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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 的人。範例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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 看過,不過我沒用過

      刪除