Memory Management
freeaddrinfo
gai_strerror
What This File Covers
When you call getaddrinfo(), the system dynamically allocates memory on your behalf to hold a linked list of address structures. If you never free that memory, your program will leak it. This file explains the two cleanup helpers: freeaddrinfo() to release memory and gai_strerror() to translate error codes into human-readable messages.
Whenever you call getaddrinfo(), the library allocates a linked list of struct addrinfo structures and sets your result pointer to the head of that list. Each node in the list may also point to a dynamically allocated socket address structure (struct sockaddr_in or struct sockaddr_in6).
All of this memory is yours to use — but also yours to free. If you forget to free it, every call to getaddrinfo() in a long-running program (like a server) causes a memory leak.
| result (your pointer) |
→ | addrinfo[0] ai_addr → sockaddr_in ai_next → |
→ | addrinfo[1] ai_addr → sockaddr_in6 ai_next → |
→ | NULL |
Each node in the linked list — plus its ai_addr sub-structure — was heap-allocated by getaddrinfo(). freeaddrinfo() walks the entire chain and frees every node.
Function Signature
#include <sys/socket.h>
#include <netdb.h>
void freeaddrinfo(struct addrinfo *result);
result is the same pointer that getaddrinfo() set. One call frees the entire list — you do not need to loop through nodes manually.
Important Rule: Copy Before Freeing
If you want to keep one of the addrinfo structures (or its socket address) for use after calling freeaddrinfo(), you must copy it first. After the free call, every pointer in the list is invalid.
Complete Working Example
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(void)
{
struct addrinfo hints;
struct addrinfo *result, *rp;
char ipstr[INET6_ADDRSTRLEN];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
int ret = getaddrinfo("example.com", "80", &hints, &result);
if (ret != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
return 1;
}
/* Walk the list — use every address */
for (rp = result; rp != NULL; rp = rp->ai_next) {
void *addr;
if (rp->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)rp->ai_addr;
addr = &ipv4->sin_addr;
} else {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)rp->ai_addr;
addr = &ipv6->sin6_addr;
}
inet_ntop(rp->ai_family, addr, ipstr, sizeof(ipstr));
printf("IP address: %s\n", ipstr);
}
/* ALWAYS free when done — one call frees entire list */
freeaddrinfo(result);
return 0;
}
Copying a Node Before Freeing
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netdb.h>
int main(void)
{
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo("localhost", "8080", &hints, &result) != 0)
return 1;
/* Make a deep copy of the first node's socket address */
struct sockaddr_in saved_addr;
memcpy(&saved_addr, result->ai_addr, sizeof(struct sockaddr_in));
/* Now safe to free */
freeaddrinfo(result);
/* saved_addr is valid here even though result is freed */
printf("Saved port: %d\n", ntohs(saved_addr.sin_port));
return 0;
}
getaddrinfo() does not set errno on failure. Instead it returns a nonzero integer error code. The gai_strerror() function converts that integer into a human-readable string so you can print a meaningful error message.
Function Signature
#include <netdb.h>
const char *gai_strerror(int errcode);
/* Returns: pointer to a string describing the error */
Error Code Table
| Error Constant | What It Means | Who Uses It |
|---|---|---|
| EAI_ADDRFAMILY | No addresses for the host exist in the requested address family | getaddrinfo only |
| EAI_AGAIN | Temporary DNS failure — try again later | Both |
| EAI_BADFLAGS | Invalid flag in ai_flags | Both |
| EAI_FAIL | Unrecoverable name server failure | Both |
| EAI_FAMILY | Address family not supported | Both |
| EAI_MEMORY | Memory allocation failed | Both |
| EAI_NODATA | No address associated with hostname (not in SUSv3) | getaddrinfo only |
| EAI_NONAME | Unknown host or service name | Both |
| EAI_OVERFLOW | Argument buffer overflow | Both |
| EAI_SERVICE | Service not supported for socket type | getaddrinfo only |
| EAI_SOCKTYPE | hints.ai_socktype is not supported | getaddrinfo only |
| EAI_SYSTEM | System error — check errno | Both |
Usage Pattern
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
void resolve_host(const char *host, const char *port)
{
struct addrinfo hints = {0};
struct addrinfo *result;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int err = getaddrinfo(host, port, &hints, &result);
if (err != 0) {
/* Use gai_strerror to get a readable message */
fprintf(stderr, "getaddrinfo(%s, %s): %s\n",
host, port, gai_strerror(err));
/* Special case: EAI_SYSTEM means check errno too */
if (err == EAI_SYSTEM)
perror(" system error");
return;
}
printf("Resolution of '%s' succeeded\n", host);
freeaddrinfo(result);
}
int main(void)
{
resolve_host("example.com", "80");
resolve_host("this.host.does.not.exist.xyz", "80"); /* will fail */
return 0;
}
Key Points About gai_strerror
| Property | strerror() (for errno) | gai_strerror() (for getaddrinfo) |
|---|---|---|
| Header | string.h | netdb.h |
| Input | errno integer | EAI_* integer |
| Returns | const char* string | const char* string |
| Use case | System call errors | Name resolution errors |
Q1. Why does getaddrinfo() require a separate freeaddrinfo() call instead of the caller using free() directly?
The internal data structure is a linked list where each node may in turn point to a socket address structure. The caller has no visibility into exactly what was allocated or in how many separate heap blocks. freeaddrinfo() walks the entire list and frees each node and its associated pointers in the correct order. Using free(result) directly would only free the first node and leak everything else.
Q2. What happens if you call freeaddrinfo() twice on the same pointer?
Undefined behavior — a double-free. This is the same risk as any heap memory in C. The safe pattern is to set the pointer to NULL after calling freeaddrinfo(result); result = NULL;.
Q3. getaddrinfo() failed. Can I use perror() to print the error?
No. perror() reads from errno, but getaddrinfo() does not set errno (except in the special EAI_SYSTEM case). You must pass the integer return value to gai_strerror() to get a meaningful message.
Q4. What does EAI_AGAIN tell you and how should you handle it?
It means the DNS resolver got a temporary failure — the name server was unreachable or returned SERVFAIL. The correct response is to retry after a short delay, not to treat it as a permanent error. In a production server you might retry a few times with exponential back-off before giving up.
Q5. Why is it safe to call freeaddrinfo() even if you are still holding a file descriptor (socket) that was created from one of the addrinfo nodes?
Once you call connect() or bind(), the kernel has already extracted all the information it needs from the socket address structure. The socket is now an operating-system object identified by a file descriptor. Freeing the addrinfo list only releases the user-space copy of the address; the kernel’s internal state is unaffected.
File 2 covers getnameinfo() and all its flags in detail.
