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
|
何故、このような事が発生するのか?
まず、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などを利用して他のメモリ領域にコピーしなくてはなりません。