内存泄漏 GETIPFROMHOST

7

我有这段代码可以从主机名中检索IP地址:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  winsock;

function GetIPFromHost(const HostName: string): string;
type
  TaPInAddr = array[0..10] of PInAddr;
  PaPInAddr = ^TaPInAddr;
var
  phe: PHostEnt;
  pptr: PaPInAddr;
  i: Integer;
begin
  Result := '';
  phe := GetHostByName(PChar(HostName));
  if phe = nil then Exit;
  pPtr := PaPInAddr(phe^.h_addr_list);
  i := 0;
  while pPtr^[i] <> nil do
  begin
    Result := inet_ntoa(pptr^[i]^);
    Inc(i);
  end;
end;

var
wsaData: TWSAData;

begin

if (WSAStartup($0202, wsaData) <> 0) then begin
      Exit;
end;

while true do begin
sleep (1000);
GetIPFromHost ('localhost');
end;

这个程序运行正常,能够给我IP地址。 可是,为了比较DNS和IP地址,我需要使用几次这个函数。

但不幸的是,我发现程序存在内存泄漏问题,导致程序内存占用快速增加。 请问是什么原因引起的,应该如何释放内存?

谢谢您的帮助。


这到底是内存泄漏,还是你的进程加载了一些库? - ta.speot.is
这是一个内存泄漏问题。我正在使用Delphi7。循环表示每次调用函数时内存都会增加。 - Ben
我不懂Delphi,但是在GetIPFromHost结束时,难道不需要释放phe指向的内存吗? - clime
我尝试过这样做,但在FreeandNil之后整个程序崩溃了。 - Ben
@clime 不是的。与Delphi无关。请阅读winsock文档。 - David Heffernan
+1 给匿名的踩投票计数 - Remko
3个回答

4
以下是 JclSysInfoGetIPAddress 的实现方式:
function GetIPAddress(const HostName: string): string;
var
  R: Integer;
  WSAData: TWSAData;
  HostEnt: PHostEnt;
  Host: string;
  SockAddr: TSockAddrIn;
begin
  Result := '';
  R := WSAStartup(MakeWord(1, 1), WSAData);
  if R = 0 then
  try
    Host := HostName;
    if Host = '' then
    begin
      SetLength(Host, MAX_PATH);
      GetHostName(PChar(Host), MAX_PATH);
    end;
    HostEnt := GetHostByName(PChar(Host));
    if HostEnt <> nil then
    begin
      SockAddr.sin_addr.S_addr := Longint(PLongint(HostEnt^.h_addr_list^)^);
      Result := inet_ntoa(SockAddr.sin_addr);
    end;
  finally
    WSACleanup;
  end;
end;

注意,你缺少 WSACleanup

在使用Windows套接字服务之前,应用程序或DLL需要执行成功的WSAStartup调用。当它完成了对Windows套接字的使用后,应用程序或DLL必须调用WSACleanup来从Windows套接字实现中注销自己,并允许实现释放为应用程序或DLL分配的任何资源。


不需要这样做。您可以在线程启动时调用WSAStartup,然后在线程关闭时再次调用它。在这种情况下,这是主线程,最终代码对进程终止而言相当无意义。每次想要解析主机名时都进行初始化和终止是相当浪费的,因此Q中代码使用的方法比您提供的代码更好。实际上,这里没有泄漏。 - David Heffernan
@DavidHeffernan,请阅读关于WSACleanup的文档。如果您认为自己比官方MS文档中所述更了解,请在那里发表评论,说明在启动WSA后不需要WSACleanup。 - kobik
每个人都知道,在进程终止时,操作系统会回收资源。因此,如果您想要调用它,可以这样做,但在问题的代码中不这样做也没有关系。 - David Heffernan
+1,哈哈,可能是我已删除答案的重复(David已经评论了相同的内容)。不过,我仍然看不出任何可能的漏洞,所以在没有原帖作者的合作下,我们只能猜测。 - TLama
@David,确实;即使我的帖子开头是我没有注意到任何泄漏(而且由于你问题中的代码甚至无法编译,我会怀疑你真正的代码中有其他问题)。 - TLama
显示剩余3条评论

3
这段代码不会泄漏。要么你的泄漏检测有问题,要么你实际运行的代码比这个更复杂,泄漏出现在你没有展示的代码中。
在问题的代码中,Delphi RTL 只分配了动态字符串的内存。Delphi 的动态字符串处理不会泄漏。对 WinSock 的调用 gethostbynameinet_ntoa 分配了内部内存。
对于 gethostbyname

由 gethostbyname 函数返回的 hostent 结构的内存是由 Winsock DLL 从线程本地存储器中内部分配的。无论在同一线程上调用多少次 gethostbyaddr 或者 gethostbyname 函数,只分配并使用一个 hostent 结构。如果需要在同一线程上进行其他 gethostbyname 或者 gethostbyaddr 调用,则必须将返回的 hostent 结构复制到应用程序缓冲区中。否则,后续在同一线程上进行的 gethostbyname 或者 gethostbyaddr 调用会覆盖返回值。Winsock DLL 在线程退出时释放为返回的 hostent 结构分配的内部内存。

同样适用于inet_ntoa

由Windows Sockets分配的内存中包含了inet_ntoa返回的字符串。应用程序不应该对内存分配方式做任何假设。返回的字符串仅在同一线程内进行下一个Windows Sockets函数调用之前有效。

虽然问题中的代码没有调用WSACleanup,但这是可以接受的,因为在进程终止时回收资源是毫无意义的。


2
此代码适用于 Delphi XE2 和 XE3
将 "Winsock" 添加到 uses 子句中。
//function to get the IP Address from a Host
function GetIPFromHost(HostName: string): string;
type
  TaPInAddr = array[0..10] of PInAddr;
  PaPInAddr = ^TaPInAddr;
var
  phe: PHostEnt;
  pptr: PaPInAddr;
  i: Integer;
  GInitData: TWSAData;
begin
  WSAStartup($101, GInitData);
  try
    Result := '';
    phe := GetHostByName(PAnsiChar(AnsiString((HostName))));
    if phe = nil then Exit;
    pPtr := PaPInAddr(phe^.h_addr_list);
    i := 0;
    while pPtr^[i] <> nil do
    begin
      Result := string(inet_ntoa(pptr^[i]^));
      Inc(i);
    end;
  finally
    WSACleanup;
  end;
end;e

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接