gethostbynameの落とし穴

ここでは、gethostbynameを使ってはまりがちな落とし穴を紹介したいと思います。

変な結果が出るサンプル

以下の例では、二つのテストを行っています。 テストの中ではwww.yahoo.co.jpとwww.google.co.jpの両方に対してgethostbynameを連続的に行っています。 本当はwww.yahoo.co.jpとwww.google.co.jpの両方のIPアドレスが表示されて欲しいのですが、実際には片方しか表示されません。


#include <stdio.h>
#include <winsock2.h>

int
main()
{
 WSADATA wsaData;
 struct hostent *hostent1, *hostent2;
 struct in_addr inaddr1, inaddr2;

 WSAStartup(MAKEWORD(2,0), &wsaData);

 //
 // テスト1
 hostent1 = gethostbyname("www.google.co.jp");
 hostent2 = gethostbyname("www.yahoo.co.jp");

 inaddr1.S_un.S_addr = **(unsigned int **)(hostent1->h_addr_list);
 inaddr2.S_un.S_addr = **(unsigned int **)(hostent2->h_addr_list);

 printf("test 1\n");

 printf("hostent1 : %p\n", hostent1);
 printf("hostent2 : %p\n", hostent2);

 printf("hostent1 = %s\n", inet_ntoa(inaddr1));
 printf("hostent2 = %s\n", inet_ntoa(inaddr2));

 printf("\n");

 //
 // テスト2
 hostent1 = gethostbyname("www.yahoo.co.jp");
 hostent2 = gethostbyname("www.google.co.jp");

 inaddr1.S_un.S_addr = **(unsigned int **)(hostent1->h_addr_list);
 inaddr2.S_un.S_addr = **(unsigned int **)(hostent2->h_addr_list);

 printf("test 2\n");

 printf("hostent1 : %p\n", hostent1);
 printf("hostent2 : %p\n", hostent2);

 printf("hostent1 = %s\n", inet_ntoa(inaddr1));
 printf("hostent2 = %s\n", inet_ntoa(inaddr2));

 WSACleanup();

 return 0;
}

サンプル実行結果

上記サンプルを実行すると、以下のような結果が出力されます。

C:\> a.exe
test 1
hostent1 : 0014B774
hostent2 : 0014B774
hostent1 = 202.229.198.21
hostent2 = 202.229.198.21

test 2
hostent1 : 0014B774
hostent2 : 0014B774
hostent1 = 216.239.63.10
hostent2 = 216.239.63.10

何故、このような事が発生するのか?

上記サンプルの出力結果は、後に行われたgethostbynameの結果が2度表示されるというものになっています。

gethostbynameは、struct hostent *を返しています。 問題は、このstruct hostent *がどこから来ているかです。 今までwinsockでプログラムを書いていて、gethostbynameが返すstruct hostent *をfree(解放)した覚えは無いと思います。 実は、このstruct hostent *の実体は毎回確保される動的なメモリ領域ではありません。 このstruct hostent *の実体はwinsockの内部にあります。 サンプルの中でhostent1とhostent2のポインタ値を出力していますが、それぞれが全く同じ値になっています。 そのため、このstruct hostent *の実体は最後にgethostbynameが利用された時の値に毎回書き換えられてしまいます。

この問題を回避するために

以上のように、gethostbynameの結果渡されるstruct hostent *の中身は変わってしまいます。 この問題は、Threadを使うと非常に発見しにくいbugにつながる可能性があります。

Windows XPを使っている場合には、gethostbynameの代わりにgetaddrinfoを利用するとこの問題を回避できます。 getaddrinfoの結果は毎回動的に確保され、freeaddrinfoで解放する必要があります。 そのため、gethostbynameのような問題は発生しません。

プロフェッショナルIPv6解説動画シリーズ再生リスト

動画で学ぶ「プロフェッショナルIPv6」を作っています。 もしよろしければご覧ください。お楽しみいただければ幸いです!