Romper esa regla resulta en comportamiento indefinido o UB, y en ese caso los compiladores pueden hacer prácticamente lo que quieran. Pudiera parecer estos son sólo problemas teóricos, pero no es así, pues constantemente se encuentran problemas derivados de estos malos usos. Aquí pongo unos ejemplos:
Código
#include <stdio.h> typedef struct { unsigned char codigo : 4; unsigned short cantidad : 9; unsigned char procedencia : 1; unsigned char basura : 2; } producto_t; producto_t* prod; int main() { unsigned base[2] = { 0 }; prod = (producto_t*)&base; prod->codigo = 5; prod->procedencia = 1; prod->cantidad = 4; prod->basura = 3; }
En GCC o MinGW, si se compila con -O2 o superior, la salida es:
Código:
base : 00000000 00000000
Lo que pasó es que el compilador decidió optimizar el programa y dejar sólo esto:
Código
es decir, directamente imprime los valores con los que base fue inicializado. Puesto que *prod y base tienen tipos distintos e incompatibles, no es válido modificar base mediante ese puntero, así que el compilador decide que base jamás fue modificado luego de su inicialización.
Otro ejemplo casi igual pero con un resultado todavía peor:
Código
#include <stdio.h> typedef struct { unsigned char codigo : 4; unsigned short cantidad : 9; unsigned char procedencia : 1; unsigned char basura : 2; } producto_t; producto_t* prod; int main() { unsigned base[2] = { 0 }; prod = (producto_t*)&base; prod->codigo = 5; prod->procedencia = 1; prod->cantidad = 4; prod->basura = 3; base[0] = base[0]; base[1] = base[1]; }
de nuevo, GCC y MinGW arrojan esta salida:
Código:
base : 00000000 00000000
codigo: 0
procedencia: 0
cantidad: 0
basura: 0
por las mismas razones de antes.
Y esto no se limita a GCC, sino que cada compilador tiene sus "detalles". Por ejemplo, hace años alguien reportó un posible bug en clang. El ejemplo que puso era más o menos así:
Código
#include <stdio.h> int main() { int par[2] = { 1 }; int k = 0; for (int i = 0; i < 1; i++) { if (par[1] > 0) { *(short*)&par[0] = 5; } par[k++] = 0; } }
con -O1 o superior, en clang da esta salida:
Código:
Esto deberia ser 0: 1
Edit: corrijo números de línea
se supone que siempre debería ser 0 porque la última instrucción que se ejecuta antes del printf es la de la línea 12, pero el compilador hace lo que quiere, porque la línea 10 contiene UB (¡a pesar de que nunca se va a ejecutar, ya que la condición del if forzosamente será falsa!). Si se cambia el short* por char*, ahí la modificación ya es válida y el programa imprime 0, como se esperaba.
Estos ejemplos pueden parecer muy rebuscados o "artificiales", pero la realidad es que normalmente ese tipo de problemas se encuentran al estar trabajando en programas reales. Obviamente uno no va poner programas enteros en los foros o en reportes de bugs, así que se busca crear un ejemplo lo más básico y simplificado posible que muestre el problema, pero estas cosas ocurren todo el tiempo en programas de todo tipo y tamaño, y de hecho hay tanto debate al respecto, que hay muchas conferencias hablando sobre el tema y justificando (o no) lo que hacen los compiladores.
Como muchos otros programadores, no soy fan de optimizaciones tan agresivas como éstas, y para mí, el caso de clang debería considerarse un bug (si experimentamos un poco con el código, obtenemos resultados aún más raros), pero sus desarrolladores no piensan así, y nunca lo arreglaron. Técnicamente, el estándar de C (y C++) les da la razón. El caso es que esté o no uno de acuerdo, los compiladores hacen este tipo de cosas cuando encuentran código que viola el estándar, así que es mejor intentar apegarse a las reglas.