Returning a pointer to a function's internal static buffer looks convenient: no heap allocation, no caller-supplied buffer, and a clean API. The catch is that every call shares the same storage, so two calls in one expression can quietly turn into one value.
When a C or C++ function needs to hand a block of memory back to its caller—say, a string—the lifetime of that memory becomes part of the API. A buffer allocated on the stack inside the function disappears as soon as the function returns, so the return value has to point to memory that remains valid outside the call.
That problem has produced a few common patterns. One is to allocate with malloc or new and return the pointer. The heap memory remains visible globally, but now the caller must know exactly when and how to free it. Forget the free, and the program leaks; free it twice, or free it through the wrong path, and it may crash. Some Windows APIs follow this model and require a matching release call, but as a general design it puts a lot of responsibility on every caller.
A second pattern asks the caller to supply the destination buffer, along with its size. Many standard C functions and Windows API functions work this way. The function writes into memory owned by the caller, so ownership is clear and the chance of a lifetime mistake is lower. The trade-off is a slightly clumsier interface: the caller has to prepare a buffer and pass its length.
The third pattern uses a static buffer inside the function. Once allocated, that storage is not tied to the function's stack frame, and it does not disappear when the function returns. As long as the caller has its address, the buffer is reachable. This avoids heap allocation and avoids asking the caller for a buffer, which makes the function look clean and easy to use.
It also creates a trap that is easy to miss.
A real case: inet_ntoa
Anyone who has done socket programming likely knows inet_ntoa, which converts a numeric IP address into a string. Its declaration is:
char *inet_ntoa(struct in_addr in);
Obviously it does not allocate heap memory, and it does not accept a caller-provided string buffer. That leaves a static char array. The parameter type is:
struct in_addr {
unsigned long int s_addr;
}
For IPv4, the address is packed into s_addr as four 8-bit groups. If s_addr is 3776385196, the binary form is:
11100001 00010111 00010000 10101100
Grouping the bytes gives 225 23 16 172, so the address is 172.16.23.225.
Now imagine logging source and destination addresses:
struct in_addr src, des;
........
........
fprintf(fp, "源IP地址<%s>/t目的IP地址<%s>/n", inet_ntoa(src), inet_ntoa(des));
What gets written? The source and destination addresses are identical. Debugging shows src.s_addr and des.s_addr are different:
src.s_addr = 3776385196; // corresponds to 172.16.23.225
des.s_addr = 1678184620; // corresponds to 172.16.7.100
inet_ntoa is not producing two independent strings. It returns the address of one static char array. In the call, des is evaluated first, then src; both expressions yield the same pointer. When src is converted, it overwrites the buffer with the second address. fprintf then receives the same pointer twice, so it writes the same string twice. The order of argument evaluation is not guaranteed by C, but the collision is: two calls in one expression share one buffer. The man page confirms it: The string is returned in a statically allocated buffer, which subsequent calls will overwrite.
The trap is larger than one function
This pattern is dangerous because the bug is not local to inet_ntoa. Any code that keeps the returned pointer, compares it, or uses the function twice in one expression is working with mutable shared storage. Consider:
if ( strcmp( inet_ntoa(ip1), inet_ntoa(ip2) )==0 ) {
…. ….
}
The intent is to compare two addresses, but both calls return the same buffer. The condition can end up always true, or at least compare the second address with itself.
Avoid returning an internal static buffer when designing an API. If you must, document loudly that callers cannot use the function more than once in a single expression, and that storing the returned pointer or reference without copying is useless. Otherwise the address points to shared storage whose contents change on the next call. C and C++ give you enough rope; if you do not understand this part of the language, maybe Mars is safer. Effective C++ item 23 makes a related point: do not return references to internal objects, including static variables, and the conclusion is negative.