Я бы сказал, что C написан строго так, как требует ваш компилятор / платформа. Например, если вы строите на строгой платформе, разыменование перенаправленного указателя типа, скорее всего, сломается:
void m_free(void **p)
{
if (*p != NULL) {
free(*p);
*p = NULL;
}
}
....
char *str = strdup("foo");
m_free((void **) &foo);
Теперь, если вы скажете компилятору пропустить строгий псевдоним, это будет не проблема, но не очень переносимая. Таким образом, в этом смысле расширение границ языка возможно, но, вероятно, не лучшая идея. Это выходит за рамки типичного приведения, то есть приведения int к типу long и действительно показывает одну из возможных ловушек пустоты.
Итак, я бы сказал, что C в основном строго типизирован, но его различные компиляторы предполагают, что программист знает лучше и допускает некоторую гибкость. Это действительно зависит от компилятора, некоторые не будут понимать, что это возможно. Таким образом, в этом смысле предпочтительный компилятор действительно играет роль при ответе на вопрос. То, что правильно, часто отличается от того, с чем ваш компилятор позволит вам сойти с рук.