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_ntoain the same thread will overwrite the result of the last call.Instead of
inet_ntoathe newer functioninet_ntopwhich 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 "

很给力
只有更新速度够快,俺们这些粉丝才能更忠实!