inet_ntoa 是采用静态内存分配的 线程安全 函数
好吧,其实这是个很简单的问题
1.原因
好久没碰过C代码了,刚看到inet_ntoa(),突然想到这个应该是静态分配的内存,不用释放。但不知道是不是线程安全的,就是说一个线程一份buffer。
2.有问题求man
于是man inet_ntoa 了一下:
The inet_ntoa() function converts the Internet host address in given in network byte order to a string in standard numbers-and-dots notation. The string is returned in a statically allocated buffer, which subsequent calls will overwrite.
开始初略的感觉应该不是线程安全的,又简单STFG了一下,"inet_ntoa 线程安全",一片说不是线程安全的····于是俺信了。
3.真相
还是心里感觉不放心,于是又找到GNU的libc手册:
— Function: char * inet_ntoa (struct in_addr addr)
This function converts the IPv4 Internet host address addr to a string in the standard numbers-and-dots notation. The return value is a pointer into a statically-allocated buffer. Subsequent calls will overwrite the same buffer, so you should copy the string if you need to save it.
In multi-threaded programs each thread has an own statically-allocated buffer. But still subsequent calls of
inet_ntoa
in the same thread will overwrite the result of the last call.Instead of
inet_ntoa
the newer functioninet_ntop
which is described below should be used since it handles both IPv4 and IPv6 addresses.
看来这回应该是线程安全的了,使用线程特定存储,估计是使用了类似Thread Local Storage的东西,又名“static memory local to a thread 线程局部静态变量”。
4.真的真相
到这里我的手又贱了,又想看源码了,于是又去找glibc的源码等,最终结论是:根据版本相关,有的线程安全,有的不是线程安全的。
1.最简单的版本:非线程安全
详见这里:http://www.netmite.com/android/mydroid/bionic/libc/inet/inet_ntoa.c
char *inet_ntoa(struct in_addr in){ static char b[18]; char *p; p = (char *)∈ #define UC(b) (((int)b)&0xff) (void)snprintf(b, sizeof(b), "%u.%u.%u.%u", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3])); return (b);}
2.使用inet_ntop函数的版本
static const char sccsid[] = "@(#)inet_ntoa.c 8.1 (Berkeley) 6/4/93"; static const char rcsid[] = "$Id: inet_ntoa.c,v 1.7 1999/05/14 18:16:55 vixie Exp $"; 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); }
3.使用线程本地存储的线程安全版本-初期
我在机器的2.2.2版本glibc里面找到了glibc-2.2.2/inet/inet_ntoa.c 的实现,具体代码请到这里下载。里面是用线程本地存储的方式干的:
43 char * 44 inet_ntoa (struct in_addr in) 45 { 46 __libc_once_define (static, once); 47 char *buffer; 48 unsigned char *bytes; 49 50 /* If we have not yet initialized the buffer do it now. */ 51 __libc_once (once, init); 52 53 if (static_buf != NULL) 54 buffer = static_buf; 55 else 56 { 57 /* We don't use the static buffer and so we have a key. Use it 58 to get the thread-specific buffer. */ 59 buffer = __libc_getspecific (key); 60 if (buffer == NULL) 61 { 62 /* No buffer allocated so far. */ 63 buffer = malloc (18); 64 if (buffer == NULL) 65 /* No more memory available. We use the static buffer. */ 66 buffer = local_buf; 67 else 68 __libc_setspecific (key, buffer); 69 } 70 } 71 72 bytes = (unsigned char *) ∈ 73 __snprintf (buffer, 18, "%d.%d.%d.%d", 74 bytes[0], bytes[1], bytes[2], bytes[3]); 75 76 return buffer; 77 }
4.线程安全的最佳注释版
glibc-2.6.1/inet/Inet_ntoa.c 文件
下面的注释最有爱了。
/* The interface of this function is completely stupid, it requires a static buffer. We relax this a bit in that we allow one buffer for each thread. */ static __thread char buffer[18]; char * inet_ntoa (struct in_addr in) { unsigned char *bytes = (unsigned char *) ∈ __snprintf (buffer, sizeof (buffer), "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]); return buffer; }
5.测试
用下面的代码测试,在我的机器下显示是线程安全的,返回的指针地址不一样:
#include <unistd.h> #include <stdio.h> #include <string.h> #include <time.h> #include <pthread.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> const int thnum = 5 ; pthread_t th[thnum] ; void *thread_main(void *data); int main(int argc,char **argv) { for(int i=0;i<thnum;i++){ int res = pthread_create(&(th[i]), NULL, thread_main, (void*)i); if(res != 0 ){ printf("pthread_create() call failed.error[%d] info is %s.",errno, strerror(errno)); } } for(int i=0;i<thnum;i++){ int res = pthread_join(th[i], NULL); if(res != 0){ printf("pthread_join(%ld) call failed.error[%d] info is %s.",th[i], errno, strerror(errno)); } } return 1; } void *thread_main(void *data){ int i = (int)data; in_addr addr; char str[16]; sprintf(str,"10.31.33.%d",i); inet_aton(str,&addr); char *ip = inet_ntoa(addr); sleep(1); printf("thread id=%d,ip ptr:%04Xttipstring:%sn",i,ip,ip); }
6.结论
又是跟版本有区别,官方建议使用更给力的版本:inet_ntop,后者线程安全,而且支持IPV6。很多实现inet_ntoa是通过inet_ntop实现的。
7.参考
“Is inet_ntoa() reentrant ?” http://rachid.koucha.free.fr/tech_corner/inet_ntoa.html
glibc源码 http://ftp.gnu.org/gnu/libc/
这里有句log显示修复了线程安全的问题,不知道是不是从此开始的:http://upstream-tracker.org/changelogs/glibc/2.15/changelog.html 搜索inet_ntoa => "* more bug fixes : inet_ntoa is thread-safe "
很给力
只有更新速度够快,俺们这些粉丝才能更忠实!