Your question seemed to call for a forest answer, and the answers here seem like tree answers, so I thought I'd give you a forest answer.
This is very rarely how C programs are written. It is always how shell scripts are written, and sometimes how Python, perl or Ruby programs are written.
People typically write in C for easy use of system libraries and direct low-level access to OS system calls as well as for speed. And C is a difficult language to write in, so if people don't need those things, then they don't use C. Also C programs are typically expected to only have dependencies on shared libraries and configuration files.
Shelling out to a sub-process isn't particularly fast, and it doesn't require fine-grained and controlled access to low-level system facilities, and it introduces a possibly surprising dependency on an external executable, so it is uncommon to see in C programs.
There are some additional concerns. The security and portability concerns people mention are completely valid. They are equally valid for shell scripts of course, but people are expecting those kinds of issues in shell scripts. But C programs are not typically expected to have this class of security concern, which makes it more dangerous.
But, in my opinion, the biggest concerns have to do with the way
popen will interact with the rest of your program.
popen has to create a child process, read its output and collect its exit status. In the meantime, that process' stderr will be connected to the same stderr as your program, which may cause confusing output, and its stdin will be the same as your program, which might cause other interesting issues. You can solve that by including
</dev/null 2>/dev/null in the string you pass to
popen since it's interpreted by the shell.
popen creates a child process. If you do anything with signal handling or forking processes yourself you may end up getting odd
SIGCHLD signals. Your calls to
wait may interact oddly with
popen and possibly create strange race conditions.
The security and portability concerns are there of course. As they are for shell scripts or anything that starts up other executables on the system. And you have to be careful that people using your program aren't able to get shell meta-charcaters into the string you pass into
popen because that string is given directly to
sh -c <string from popen as a single argument>.
But I do not think they are why it is strange to see a C program using
popen. The reason it is strange is because C is typically a low level language, and
popen is not low level. And because use of
popen places design constraints on your program because it will interact strangely with your program's standard input and output and make it a pain to do your own process management or signal handling. And because C programs are not typically expected to have dependencies on external executables.