that they do execl(_PATH_BSHELL, "sh", "-c", command, NULL) instead of execl(_PATH_BSHELL, command, NULL)
The latter would NOT have executed command directly, but _PATH_BSHELL (/bin/sh) with its $0 set to command and no arguments, resulting in an shell expecting commands from its stdin.
Also, that syntax relies on NULL being defined to an explicit pointer (e.g. ((void*)0)), and not just 0, which is not guaranteed anywhere. While they can do that in their implementation (because they control all the headers), it's not what you should do in application code.
And no, execl(command, command, (void*)NULL) wouldn't have executed command directly either, unless command is a) a full path and b) in an executable format (binary or a script starting with a she-bang #! -- the latter being a non-standard extension). If command was a simple command name to be looked up in PATH (like pwd or a.out) or an executable script not starting with a she-bang, you should've used execlp instead of execl.
The exec[lv]p[e] functions do some of the things a shell does (like looking through the PATH), but not all of them (like running multiple commands or expanding variables): that's why functions like system(3) or popen(3) pass the command to /bin/sh -c. Notice that with both it's /bin/sh, not the user's login shell or the $SHELL from the environment which is used.
If you exec sh -c a.out instead of just a.out itself, does the actual a.out process end up being a "grandchild" process and not a child process?
Only with some shells like dash. Not with bash, ksh93, mksh, zsh, yash, busybox, etc, which will execute a.out directly instead of forking and waiting for it.