A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
You cannot return a local char array from a function. The array lives on the stack. The stack frame is destroyed when the function returns. The pointer you return points to dead memory. This is the dangling pointer problem applied to strings. There are four safe patterns: return a pointer to a static buffer, return a pointer to a string literal, allocate on the heap and return the pointer, or have the caller provide the buffer. Each has trade-offs. Choosing wrong gives you a crash or a memory leak.
#include <stdio.h>#include <string.h>void get_greeting(char *buf, size_t size) {snprintf(buf, size, "Hello from the function!");}int main(void) {char message[64];get_greeting(message, sizeof(message));printf("%s\n", message);return 0;}
The caller owns the storage. The function fills it. No allocation. No free. No lifetime ambiguity. The caller knows the buffer size and passes it. The function uses snprintf to stay within bounds. This is the pattern used by strerror_r, getcwd, and most safe C APIs.
#include <stdio.h>#include <stdlib.h>#include <string.h>char *build_path(const char *dir, const char *file) {size_t len = strlen(dir) + 1 + strlen(file) + 1;char *path = malloc(len);if (path == NULL) return NULL;snprintf(path, len, "%s/%s", dir, file);return path;}int main(void) {char *p = build_path("/home/alex", "notes.txt");if (p) {printf("Path: %s\n", p);free(p);}return 0;}
The function allocates. The caller frees. The ownership transfer must be documented. If the caller forgets the free, you leak. If two callers free the same pointer, you corrupt the heap. This pattern is used by strdup, asprintf, and any function that returns a dynamically constructed string.
#include <stdio.h>const char *status_string(int code) {static char buf[32];snprintf(buf, sizeof(buf), "Status: %d", code);return buf;}int main(void) {printf("%s\n", status_string(200));printf("%s\n", status_string(404));return 0;}
The static buffer persists across calls. No allocation. No free. But the buffer is shared. The second call overwrites the first call's result. If you store the first pointer and print it after the second call, you see the second call's data. In multithreaded code, this is a data race. Use this pattern only for single-threaded diagnostic functions where the result is consumed immediately.
String ownership is the microcosm of C's memory model. Every returned pointer has a lifetime question and a responsibility question. Answer both correctly and your code is safe. Answer either wrong and you have a bug. Strings module complete. Next: operators and expressions, where we dissect the arithmetic, relational, bitwise, and ternary operators that drive every computation.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.