You can take LLVM "bitcode" as the product of clang, move it to another system with a different arch, and finish compiling the IR to a native binary on that system. This implies that the first step and second step don't share any state other than the IR itself—meaning that flags passed directly to clang in the first phase can't affect codegen, other than by influencing the generated IR; and that codegen has to treat the IR (and the environment) as its sole inputs.
Whereas with GCC, as far as I know, the RTL IR isn't arch-neutral, and the GENERIC/GIMPLE IRs aren't fully "baked" yet (i.e. they're compiler->compiler step IRs, not post-compilation pre-codegen IRs.) So there's no conceptual guarantee (even if there's an implementation-level one) that the compiler won't use compile-phase state to inform codegen.
In other words: it's guaranteed that you'd be able to implement the switches I'm talking about in clang+LLVM. You might (but won't necessarily) have to do some work to get GCC into the right shape to implement them.
1. setting "-foverflow=promote" on the compiler's parsing-and-IR-generating stage, thus generating code that acts like all integers occupy infinitely-sized abstract machine registers, and expects this to get shimmed as checked promotion to pointers-to-structs from a bignum library on most architectures;
and then, seperately,
2. setting an optimization flag like "-Onopromote" for the target-recompilation stage, that throws away all the bignum promotion shims that would have been inserted, and pretends that it's adhering to bignum-promotion semantics anyway (thus creating code that will be optimized the way C code currently is.)
That's quite a bit more complicated, but also a lot more explicit about what's going on: you have to specify (or pick a profile that specifies) the actual semantics you want, and then you have to explicitly opt into assuming those semantics rather than enforcing them. There'd probably be a similar pair of {semantic, assumption} flags for each currently-undefined behavior.
I would expect that a single-pass compiler like gcc would look at the toolchain's target ISA, compare it to these switches, and then generate extra shim code to adapt any cases of target-arch-does-X into code-does-Y-on-X, while letting do-X-on-X just translate directly. Basically the same way floating-point code gets compiled: architectures that support it get it, architectures that don't get a shim.
Meanwhile, a two-stage compiler with an intermediate representation, like clang, should generate IR with granular types specifying the chosen behavioral semantics on each operation. The second step in such a toolchain becomes much more formalized: rather than taking code that can contain undefined behavior as input, and then "defining" it during translation in some random heuristic manner, you just take code that already specifies the behavior it wants in full, and then attempt to generate target-arch code that does whatever it says in the most efficient manner possible while not breaking any of the guarantees the IR asks for. In other words, your code-generation stage becomes a static recompiler, like this one[1] for the NES ISA.
And you can always specify a switch at code-gen time that will error out the compilation if you've done something that has caused shims to be inserted. Or even a switch to throw away the behavioral semantics the IR asks for and substitute your own, like happens in most performance-focused emulator software.
[1] http://andrewkelley.me/post/jamulator.html