Linux System Programming · advanced · ~12 min
Run a different program inside the current process.
The exec* family of functions replaces the current process's program with a different one. The PID stays the same, but the code, stack, and heap are wiped and reloaded from a new executable. exec does not return on success — control passes to main of the new program.
fork alone just gives you two copies of the same program. exec is the second half: after forking, the child calls exec("/bin/ls") (for example) to become the new program. This pair — fork then exec — is how every shell launches every command.
Variants. execl, execv, execle, execve, execlp, execvp — they differ in how arguments and environment are passed and whether $PATH is searched. No return on success. If exec returns, it means the call failed — usually because the executable wasn't found. Inherits fds. The new program inherits any file descriptors the old one had open, except those marked close-on-exec.
execl("/bin/ls", "ls", "-la", (char *)NULL);
/* if we get here, exec failed */
perror("execl");
_exit(127);
The exec* family replaces the current process image with a new program. On success they do not return — the new program starts at its main. On failure they return -1 and set errno.
Pattern: fork, then exec in the child. The parent then waits for the child to finish.
Variants:
execvp(file, argv) — searches PATH for file, argv is an array.execve(path, argv, envp) — most explicit; absolute path, explicit env.pid_t pid = fork();
if (pid == 0) {
char *argv[] = { "ls", "-la", NULL };
execvp("ls", argv);
_exit(127); // only reached if execvp fails
}
int st; waitpid(pid, &st, 0);
strace -e trace=execve ./prog shows every exec attempt with its arguments. If exec fails with 'No such file or directory', double-check the first argument (the path); if it fails with 'Permission denied', the file isn't executable (chmod +x).
After exec succeeds, your current heap and stack vanish — any in-flight malloc'd buffers were leaked by definition. That's usually fine because the new program doesn't need them, but ASan / valgrind won't complain about that path.
Every shell command launch, every system() call, Docker's entrypoint replacement, login: getty exec's login which exec's bash.
/bin/ls -l / while the parent waits. 2. Use execvp to make the program look up $PATH. 3. Pass environment variables explicitly with execve.exec swaps the running program. Pairs with fork: fork makes a copy, exec turns that copy into a different program. Without exec, fork would just make duplicates — together they implement the Unix process model.