Impurity comes in two forms:
A C example of internal impurity would be passing a value by pointer, and examples of external impurity would be calling printf or srand(time(NULL)).
Importantly, internal impurity is not a thing in pure languages such as Haskell. External impurity is, though. Wait, didn't I just call Haskell a pure language? How can a pure language be compatible with impurity? The thing is, in Haskell, everything I/O related is done in a monad, which makes it magically pure.
Yeah, just kidding. I mean it is really done through a monad, which is neat and tidy, but I still I don't buy into pure side-effects. What people really mean when they call Haskell's I/O model pure is that you could sort of pretend that a Haskell program runs in two phases: first a so-called "IO action" is built (in pure Haskell), then an impure interpreter runs this action (outside of Haskell).
This somewhat matches how things work in practice: compiling a program is equivalent to evaluating the pure sections of the program and the rest is managed by the runtime system. However, "the rest" may be basically the whole program. Consider this contrived example:
Here's what we may picture it to look like after the pure portions have been dealt with:
Well, this barely changed anything. Actually, the fact that the compilation deals with the pure parts only could also be said of C (with regard to external impurities only, as some internally impure expressions are or at least could be computed at compile time); yet, this does not feel like a compelling argument for the (external) purity of C.
Anyways, people run Haskell programs precisely for their side-effects and not because they like to look at IO actions, so pretending that the side-effects are not part of the program seems like a stretch.