lenguaje - headers en c
¿Por qué se declaran determinados tipos de datos C en más de un archivo de encabezado estándar? (4)
Por ejemplo, C11 dicta que size_t
debe declararse en los siguientes archivos de encabezado:
- stddef.h
- stdio.h
- stdlib.h
- string.h
- tiempo.h
- uchar.h
- wchar.h
Al leer C11, descubrí que hay muchos otros tipos de datos declarados en más de un archivo de encabezado estándar.
Preguntas
- Digamos en el caso de
size_t
. ¿Por qué no solo enstddef.h
por simplicidad? - Digamos que un compilador de C implementa
size_t
en esos archivos de encabezado. ¿Se les garantiza tener la misma definición en esos archivos de encabezado?
Digamos en caso de tamaño_t. ¿Por qué no solo en stddef.h por simplicidad?
El tipo se utiliza en la declaración de funciones en todos esos archivos. Si no se declarara en <stdio.h>
obtendrías un error de compilación a menos que primero incluyas <stddef.h>
.
Digamos que un compilador de C implementa size_t en esos archivos de encabezado. ¿Se les garantiza tener la misma definición en esos archivos de encabezado?
Sí, tendrán la misma definición. Generalmente, el valor se define en un solo lugar en un archivo de inclusión separado que es incluido por los demás.
En algunos casos, puede ser posible modificar la definición con opciones de compilador o definir, por ejemplo, un compilador que permite la compilación de 32/64 bits puede definir size_t
como una entidad sin signo de 32 o 64 bits, dependiendo del destino definido en la línea de comandos del compilador.
Como ejemplo de una función declarada en stdio.h
que requiere que size_t se declare previamente, considere snprintf()
. Tal como está, si desea usarlo en su código, todo lo que necesita hacer es #include <stdio.h>
. Si size_t se declarara solo en stddef.h
, tendría que
#include <stddef.h>
#include <stdio.h>
No solo eso, sino que desde que stdio.h
declara snprintf
si lo usas o no, deberías incluir ambos archivos cada vez que necesites algo en stdio.h
para evitar errores en el compilador; stdio.h
tendría una dependencia artificial en stddef.h
. Eso hace que su código fuente se vuelva más largo y más frágil (observe que si invierte el orden de las dos directivas, también se romperá). En su lugar, escribimos archivos de encabezado para que estén solos y no dependan de otros encabezados, y esto es lo que decidió el comité de estandarización de C para la biblioteca estándar.
En primer lugar, cuando uno hace un #include <stdio.h>
no hay ningún requisito de que exista un archivo en cualquier lugar llamado stdio.h, o que el compilador haga algo con ese archivo. Más bien, el requisito es que dicha línea debe hacer que todos los identificadores que se especifican como asociados con <stdio.h>
se definan de acuerdo con la especificación. Sería perfectamente legítimo para un compilador que vio #include <stdio.h>
simplemente habilitar el uso de ciertos identificadores que estaban conectados al compilador. Debido a que la forma más fácil para que los proveedores de compiladores hagan que las cosas se comporten como requiere la especificación es que las directivas #include <stdio.h>
ejecuten el texto de algún archivo stdio.h
través del preprocesador, eso es lo que hacen muchos compiladores, pero eso no es necesario.
Cuando la especificación enumera los "archivos" donde se debe declarar size_t
, lo que realmente dice es que una directiva #include
que nombra cualquiera de esos archivos debe crear ese identificador en el ámbito global. Esto se puede hacer haciendo que los archivos con todos los nombres incluidos incorporen una definición de size_t
, o incorporando size_t
en el compilador, pero solo habilitando la definición incorporada del compilador se ve una directiva #include
con uno de los nombres indicados.
Hay una diferencia sutil entre by y in : una implementación es completamente libre de definir size_t
en un solo encabezado, siempre que se defina cuando se incluyen los encabezados especificados . Entonces, tienes dos opciones para eso:
- Define
size_t
en cada uno y envuelve cada uno en incluir guardias - Defínalo en un solo archivo y envuélvalo en incluir guardias.
Y sí, size_t debe definirse como se especifica, que es ( glibc ):
typedef unsigned long size_t;
o
typedef unsigned int size_t
No dicen que hay que estar cuerdo, solo dicen que debe definirse en el momento en que alguien incluye uno de esos encabezados, porque dependen de que se defina y se puede usar de forma independiente. En pocas palabras, si define algo que depende de size_t
, entonces size_t
debe definirse primero (previamente).
Cómo (o más bien, dónde) lo hace depende de su implementación.