When I compile a C program, it is compiled first to assembly code, then assembled into machine code. I'm curious why it doesn't just convert straight to machine code in the first place.
It depends on the compiler and the options you provide to the compiler. These days the most widely used compilers will write out machine language by default, but will generate an assembly listing if you request it. It can be helpful to have the assembly listing because a) sometimes compilers have bugs and you want to check the code it's generated, b) you want to understand how the machine code is affected by the CPU pipeline and cache and most people find it much easier to read assembly than machine code.
These days compilers typically convert your program to a highly abstract representation and allow you to write custom back ends to generate different flavors of machine language or even other high level languages.
It's possible for compilers to generate code which is suitable for use as input to a processor (for direct execution), a linker, an assembler, or some other kind of building tool. Which format is useful often depends upon what other code it will need to be combined with. If a compiler for language X which generates output suitable for one brand of linker, and a compiler for language Y generates output suitable for a different one, it may be hard to use a mixture of code X and Y in the same project. If a compiler for language X, however, generates assembly code instead of a linkable object file, it may be easier to use its output with the tool set associated with language Y. Because assembly-language formats aren't completely standardized some massaging may be necessary, but massaging an assembly-language file may be easier than converting a binary object file.