Why can't ed be exited with C-c?

Ctrl+C sends SIGINT. The conventional action for SIGINT is to return to a program's toplevel loop, cancelling the current command and entering a mode where the program waits for the next command. Only non-interactive programs are supposed to die from SIGINT.

So it's natural that Ctrl+C doesn't kill ed, but causes it to return to its toplevel loop. Ctrl+C aborts the current input line and returns to the ed prompt.

The same goes for less: Ctrl+C interrupts the current command and brings you back to its command prompt.

For historical reasons, ed ignores SIGQUIT (Ctrl+\). Normal applications should not catch this signal and allow themselves to be terminated, with a core dump if enabled.

The Unix V7 ed(1) source code is a primitive 1,762-line C program with just a few comments, one of which is this highly-enlightening header comment:

 * Editor

Given that the source code itself does not provide any rationale, you're only going to get it from the program's author.

ed was originally written by Ken Thompson in PDP-11 assembly, but you'd actually need to talk to whoever ported it to C. That might have been Dennis Ritchie, since he created C for Unix, and was one of many who used C to make Unix portable to non-PDP machines. Dr Ritchie is no longer around to answer such questions, though.

My reading of the code suggests that it was done to try and preserve the contents of the in-core copy of the edited document. You'll notice that other text editors also do not die on Ctrl-C.

Here's what ed does on Ctrl-C:

    signal(SIGINT, onintr);
    lastc = '\n';

(Yes, K&R C. We don't need no steenkin' return type specifiers or parameter declarations.)

Translated into English, ed:

  1. Re-registers the signal handler.

    (Unix didn't get auto-resetting signals until 4.3BSD, in the mid-1980s.)

  2. Writes out a new line, and remembers that it did so, via the global variable lastc.

    (ed.c has about sixty global variables.)

  3. Calls the error() function, which famously does little more than print ?, from the user's perspective.

In other words, it's saying, "You didn't really mean to do that, did you?"

ed, like other interactive programs, use Ctrl+C to interrupt tasks of the program itself.
This is very similar to the normal case, where it interrupts a task running in the shell - a command.

From the user perspective, both variants are very similar. The handling of the signal is different: in the usual case, the signal SIGINT is sent to the foreground process, a running command, and the command handles it by exiting.
In the case of ed, the signal is sent to the foreground process, the ed instance. If there is a task running in ed, it is interrupted and the prompt is shown. If there is no task running, nothing is changed.

Note how a shell also does not exit on Ctrl+C, just like ed. And that it does exit on Ctrl+D. Again, just like ed