Below are some related concepts to your question:
Argument Prescan:
Macro arguments are completely macro-expanded before they are
substituted into a macro body, unless they are stringified or pasted
with other tokens. After substitution, the entire macro body,
including the substituted arguments, is scanned again for macros to be
expanded. The result is that the arguments are scanned twice to expand
macro calls in them.
Stringification
When a macro parameter is used with a leading ‘#’, the preprocessor
replaces it with the literal text of the actual argument, converted to
a string constant.
#ABC => "ABC"
<---- Note the enclosing double quote, which is added by the stringification process.
Token Pasting / Token Concatenation:
It is often useful to merge two tokens into one while expanding
macros. This is called token pasting or token concatenation. The ‘##’
preprocessing operator performs token pasting. When a macro is
expanded, the two tokens on either side of each ‘##’ operator are
combined into a single token, which then replaces the ‘##’ and the two
original tokens in the macro expansion.
So the detailed process of your scenario is like this:
h(f(1,2))
-> h(12) // f(1,2) pre-expanded since there's no # or ## in macro h
-> g(12) // h expanded to g
"12" // g expanded as Stringification
g(f(1,2))
-> "f(1,2)" //f(1,2) is literally strigified because of the `#` in macro g. f(1,2) is NOT expanded at all.
h(f(1,2))
andg(f(1,2))
differ. In that sense @joe-bloggs answer is more clear. – Kayleen