首页 > C/C++, UNIX/LINUX > inet_ntoa 是采用静态内存分配的 线程安全 函数

inet_ntoa 是采用静态内存分配的 线程安全 函数

2012年8月12日 发表评论 阅读评论 3652次阅读    

好吧,其实这是个很简单的问题

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 function inet_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 "

Share
分类: C/C++, UNIX/LINUX 标签:
  1. itgrit
    2012年9月25日06:29 | #1

    很给力

  2. 2012年9月10日02:34 | #2

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

  1. 本文目前尚无任何 trackbacks 和 pingbacks.

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。