inet_ntoaの落とし穴

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

変な結果が出るサンプル

以下の例では、二つのテストを行っています。 127.0.0.1と255.255.255.255の両方が結果として表示されて欲しいのですが、実際には片方しか表示されません。


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

int
main()
{
	struct in_addr inaddr1, inaddr2;
	WSADATA wsaData;
	char *ptr1, *ptr2;

	/* 初期化 */
	WSAStartup(MAKEWORD(2,0), &wsaData);

	/* test 1 */
	inaddr1.S_un.S_addr = inet_addr("127.0.0.1");
	inaddr2.S_un.S_addr = inet_addr("255.255.255.255");

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

	/* test 2 */
	ptr1 = inet_ntoa(inaddr1);
	ptr2 = inet_ntoa(inaddr2);

	printf("%s, %s\n", ptr1, ptr2);

	/* 終了 */
	WSACleanup();

	return 0;
}

サンプル実行結果

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

C:\> a.exe
127.0.0.1, 127.0.0.1
255.255.255.255, 255.255.255.255

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

まず、inet_ntoaの宣言を見てみましょう。 inet_ntoaの宣言を単純化すると以下のようになります。


char *inet_ntoa(struct in_addr);

inet_ntoaは、struct in_addrを引数として受け取りchar *を返しています。 問題は、このchar *がどこから来ているかです。 今までwinsockでプログラムを書いていて、inet_ntoaが返すchar *をfree(解放)した覚えは無いと思います。 実は、このchar *の実体は毎回確保される動的なメモリ領域ではありません。 このchar *の実体はwinsockの内部にあります。 そのため、このchar *の実体は最後にinet_ntoaが利用された時の値に毎回書き換えられてしまいます。 この事を確認するために、一度目のテストのprintfの中にある二つの%sを%pに書き換えると良いかも知れません。 %pはポインタ値を表示します。

上記サンプルの出力結果は、一回目が127.0.0.1になり、二回目が255.255.255.255になっています。 255.255.255.255が二回出力される二度目の結果はわかりやすいと思いますが、一度目の結果は不思議かも知れません。 これは、printfで引数が評価される順番が後ろからであるために右側のinet_ntoaではなく、左側のinet_ntoaが後に行われます。 そのため、printfに値が渡される時にはinaddr1が最後にinet_ntoaに渡された状態になっています。

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

以上のように、inet_ntoaの結果渡されるchar *の中身は変わってしまいます。 そのため、inet_ntoaの結果をその後も利用する場合にはmemcpyなどを利用して他のメモリ領域にコピーしなくてはなりません。

IPv6基礎検定

YouTubeチャンネルやってます!