If one reads the rationale of the C89 Standard, the only reason that the type- aliasing rules exist is to avoid requiring compilers to make "worst-case aliasing assumptions". The given example was:
int a;
void f( double * b )
{
a = 1;
*b = 2.0;
g(a);
}
If program creates a "char" array within a union containing something whose alignment would be suitable for any type, takes the address thereof, and never accesses the storage of that structure except through the resulting pointer, there should be no reason why aliasing rules should cause any difficulty.
It's worthwhile to note that the authors of the Standard recognized that an implementation could be simultaneously compliant but useless; see the rationale for C89 2.2.4.1:
While a deficient implementation could probably contrive a program that meets this requirement, yet still succeed in being useless, the Committee felt that such ingenuity would probably require more work than making something useful. The sense of the Committee is that implementors should not construe the translation limits as the values of hard-wired parameters, but rather as a set of criteria by which an implementation will be judged.
While that particular statement is made with regard to implementation limits, the only way to interpret the C89 as being even remotely compatible with the C dialects that preceded it is to regard it as applying more broadly as well: the Standard doesn't try to exhaustively specify everything that a program should be able to do, but relies upon compiler writers' exercising some common sense.
Use of a character-type array as a backing store of any type, assuming one ensures alignment issues are taken care of, shouldn't cause any problems with a non-obtusely written compiler. The Standard didn't mandate that compiler writers allow such things because they saw no reason to expect them to do otherwise. Unfortunately, they failed to foresee the path the language would take in the 21st Century.