這周 refactor 一段 code 之後,被同事說:「為什麼 log 的來源 IP 跟目的地 IP 總是一樣呢?」
char *szDestIp = inet_ntoa(destAddr); char *szSrcIp = inet_ntoa(srcAddr);
後來仔細想了一下 inet_ntoa 的 prototype,我就明白了!如果這個 function 不必特意去 free 回傳的 pointer,那可能在內部有一個 static buffer 去保存這個值。果不期然,FreeBSD 的原始碼是這樣實作的
/*const*/ char * inet_ntoa(struct in_addr in) { static char ret[18]; strcpy(ret, "[inet_ntoa error]"); (void) inet_ntop(AF_INET, &in, ret, sizeof ret); return (ret); } char * inet_ntoa_r(struct in_addr in, char *buf, socklen_t size) { (void) inet_ntop(AF_INET, &in, buf, size); return (buf); }
到這邊我們大概就知道,inet_ntoa 並不是一個 thread safe 的 function,你可以用下面這段程式測一下會發生什麼事情。
void* thread_func (void *param) { char * addr= (char *)param; struct in_addr val; inet_aton(addr, &val); while (1) printf("%s => %s\n", addr, inet_ntoa(val)); pthread_exit(NULL); } int main() { pthread_t thr1, thr2; pthread_create(&thr1, NULL, thread_func, (void *)"127.0.0.1"); pthread_create(&thr2, NULL, thread_func, (void *)"127.0.0.2"); while(1) { sleep (1); } return 0; }
輸出大概會像
127.0.0.2 => 127.0.0.1
127.0.0.1 => 127.0.0.2
127.0.0.1 => [inet_ntoa error]
127.0.0.2 => 127.0.0.2
所以,直接使用 inet_ntoa 在 reentrant 上可能會發生問題,如果你必須面對這些問題,最好的方式就是使用 inet_ntoa_r 這個 reentrant 的版本,或者直接使用 inet_ntop。
而這難道非得要踩到地雷,或者從 API 的細節才能窺知這一切嗎?其實並不完全是,當你發現回傳的是一個 char *,又沒有要求你特別 free 他,你是不是就該猜測他是回傳 function 內部的一個 static buffer 了呢?所以,舉凡 ctime, asctime 都不保證是 thread safe 的,這是我們 programmer 該留意的細節。
所以,也許我們以後需要更小心的去寫程式,在選擇 API 時也必須留意更多細節,以免未來踩到更多地雷……。
2 responses to “從 inet_ntoa 看 thread safe 的 API”
最機的是, 這 API, MSDN 說它是 thread-safe: http://msdn.microsoft.com/en-us/library/ms738564(VS.85).aspx
不只這樣, ctime asctime 這類的也是 thread-safe
Microsoft C-runtime 把很多 C-API thread-safe 問題都給搞定…
無形中增加了 Windows program porting 到其他平台的難度啊 (誤)
Winsock 的 inet_ntoa 是threadsafe的,却不是可重入的。
The string returned is guaranteed to be valid only until the next Windows Sockets function call is made within the same thread.
大致这么做的。
02 inet_ntoa(struct in_addr in) {
03 static __thread char ret[18];
04
05 strcpy(ret, “[inet_ntoa error]”);
06 (void) inet_ntop(AF_INET, &in, ret, sizeof ret);
07 return (ret);
08 }