Entonces crees que sabes C?

Una versión interactiva de este artículo está disponible aquí: http://wordsandbuttons.online/so_you_think_you_know_c.html

Muchos programadores afirman que conocen C. Bueno, tiene la sintaxis más famosa, ha estado allí durante 44 años y no está repleta de características oscuras. ¡Es fácil!

Quiero decir, es fácil afirmar que sabes C. Probablemente lo aprendiste en la universidad o mientras te desplazas, probablemente tengas algo de experiencia con eso, probablemente creas que lo sabes de principio a fin, porque no hay mucho que saber. Bueno, lo hay. C no es tan simple.

Si crees que es – toma esta prueba. Solo tiene 5 preguntas. Cada pregunta es básicamente la misma: ¿cuál sería el valor de retorno? Y cada pregunta tiene una opción de cuatro respuestas, de las cuales una y solo una es correcta.

1

 struct S { 
int i;
char c;
} s;

principal(){
return sizeof (* (& s));
}

A. 4

B. 5

C. 8

D. No lo sé.

2

 principal(){ 
char a = 0;
short int b = 0;
tamaño de retorno de (b) == sizeof (a + b);
}

A. 0

B. 1

C. 2

D. No lo sé.

3

 principal(){ 
char a = '' * 13;
devolver a;
}

A. 416

A. 4

0

A. 4

1

A. 4

2

4.

 principal() 
{
int i = 16;
return (((((i> = i) << i) >> i) <= i));
}

A. 0

B. 1

A. 4

5

D. No lo sé.

5.

 main(){ 
int i = 0;
return i++ + ++i;
}

A. 4

7

A. 4

8

A. 4

9

D. No lo sé.

Eso es todo, deja tus plumas. Las respuestas son justo después de la pausa musical.

Gran Misa en Do menor de Wolfgang Amadeus Mozart [Public domain], a través de Wikimedia Commons. Así es, Mozart también escribió en C.

Entonces, las respuestas correctas son:

 A B C D 
 1 v 
 2 v 
 3 v 
 4 v 
 5 v 

Sí, la respuesta correcta para cada pregunta es "No sé".

Vamos a desenredarlos uno por uno ahora.

El primero es en realidad sobre el relleno de la estructura. El compilador de C sabe que almacenar datos no alineados en la memoria RAM puede ser costoso, por lo que rellena tus datos por ti. Si tiene 5 bytes de datos en una estructura, probablemente sea 8 o 16. O 6. O lo que quiera. Hay extensiones como atributos GCC aligned y packed que le permiten tener cierto control sobre este proceso, pero no son estándares. C no define los atributos de relleno, por lo que la respuesta correcta es: "No sé".

El segundo es acerca de la promoción de enteros. Es razonable que el tipo de short int y una expresión con el entero más grande sea short int sea ??el mismo. Pero lo razonable no significa correcto para C. Existe la regla de que cada expresión entera se promueve a int . En realidad, es mucho más complicado que eso. Echa un vistazo en el estándar, lo disfrutarás.

Pero aun así, no comparamos tipos, sino que comparamos tamaños. Y la única garantía de la norma da sobre short int e int tamaños es que el primero no debe ser mayor que la segunda. Es muy posible que sean iguales. Entonces la respuesta correcta es: "No sé".

El tercero es todo acerca de las esquinas oscuras. A partir de eso, ni el desbordamiento de enteros ni el signo de tipo de char son definidos por el estándar. El primero es el comportamiento indefinido, el segundo es específico de la implementación. Pero aún más, el tamaño del tipo de char no se especifica en bits tampoco. Había plataformas donde pesaba 6 bits (¿recuerdan los trigrafos ?), Y hay plataformas donde los cinco tipos enteros son 32 bits. Sin todos estos detalles especificados, toda especulación sobre el resultado no es válida, por lo que la respuesta es: "No sé".

El cuarto parece complicado, pero no es tan difícil en retrospectiva, ya que usted sabe que el tamaño int no se especifica directamente en el estándar. Puede ser fácilmente de 16 bits, entonces la primera operación causará el exceso de cambio y ese es un comportamiento claro e indefinido. No es culpa de C, en algunas plataformas incluso está indefinido en el ensamblaje, por lo que el compilador simplemente no puede ofrecerle garantías válidas sin consumir mucho rendimiento.

Entonces, una vez más, "No sé" es la respuesta correcta.

Y el último es clásico. Ni el orden de la evaluación del operando para + , ni siquiera el orden de precedencia entre operadores de incremento se especifican, por lo que básicamente todas las operaciones no triviales que involucran i++ y ++i son una trampa ya que alteran su operando. Puede funcionar como espera en una plataforma y puede fallar fácilmente en la otra. O no. Ese es el problema con cosas no especificadas. Cuando te encuentras con uno, la respuesta correcta es siempre: "No sé".

Gran misa en Do menor de Wolfgang Amadeus Mozart [Public domain], a través de Wikimedia Commons

Y en este punto solo tengo que disculparme. La prueba es claramente provocativa e incluso puede ser un poco ofensiva. Lo siento, si causa alguna molestia.

El caso es que aprendí C en aproximadamente 1998, y durante los 15 años he pensado que soy bueno en eso. Era mi idioma de elección en la universidad, y he hecho algunos proyectos exitosos en C en mi primer trabajo de tiempo completo, y aun así, cuando trabajaba principalmente con C ++, lo consideraba como demasiado hinchado C.

El momento decisivo llegó en 2013, cuando me involucré en una programación de PLC crítica para la seguridad. Era un proyecto de investigación en la automatización de centrales nucleares, donde absolutamente ninguna subespecificación era tolerable. Tuve que aprender que, aunque sabía mucho sobre la programación C, la mayoría absoluta de lo que sabía era falsa. Y tuve que aprenderlo de la manera difícil también.

Finalmente, tuve que aprender a confiar en el estándar en lugar del folklore; confiar en las mediciones, y no en las presunciones; tomar "cosas que simplemente funcionan" con escepticismo, – tuve que aprender una actitud de ingeniería. Esto es lo que más importa, no algunas anécdotas particulares de WAT.

Solo espero que esta pequeña prueba ayude a alguien como yo del pasado a aprender esta actitud en unos 15 minutos y no en 15 años.

B. 5

1

B. 5

2

Texto original en inglés.