I know that #inclusion is often described as a text copy-pasting preprocessor directive. Now if a header is #include guarded, or #pragma onced, then how'd we describe what is actually happening past the first translation unit to #include said header?
There is no "first" translation unit. All translation units are conceptually translated in parallel (of course, in practice you might end up compiling them one at a time, but it doesn't matter).
Each translation unit begins with a blank slate. Technically that isn't quite true because you can add #define
s at the command line and there are some predefined macros as well, but anyway, no translation unit will have a "memory" of #define
s that were executed in any other translation unit. Thus, a header may be #include
d multiple times despite the guards. It is just that it won't be #include
d multiple times into a single translation unit.
This means that you must still take care to avoid multiple definitions: for example, if your header contains a global variable then you must ensure it is const
(so it will have internal linkage) or explicitly declare it inline
(to collapse all definitions into one) or extern
(to suppress definition in the header so you can place the definition into a single translation unit).
Despite the fact that include guards don't prevent multiple definitions across multiple translation units, they do prevent multiple definitions within a single translation unit, and this is important because even though some entities may be defined multiple times in a program, it's still not allowed for multiple definitions to appear in the same translation unit. For example, if you have an inline
global variable in a header, then multiple translation units can include that header and the definitions will all be collapsed into a single definition at link time, but you will get a compilation error if any one translation unit defines that variable multiple times. Therefore, such a header must have an include guard.