@msc ofrece una buena introducción a las reglas detrás de este comportamiento.
Noté que si declaro una variable global varias veces, el compilador ni siquiera genera una advertencia.
C tiene tres tipos de declaraciones globales para objetos, a saber, las que están (y estoy pasando por alto static
aquí):
- declaraciones que no son definiciones -
extern int a;
- declaraciones que también son definiciones,
int a = 3;
oextern int a = 3;
- definiciones tentativas
int a;
Se permiten múltiples declaraciones de tipo 1 y 3, mientras que a lo sumo se permite una definición (tipo 2).
¿Cuál es la explicación de este comportamiento?
Si también está preguntando sobre la motivación de estas reglas, es un soporte para la compilación por separado . (Ver unidad de traducción ).
Para dividir un programa en múltiples archivos compilados por separado, necesitamos algunas características, a saber (a) poder declarar sin definir necesariamente , y (b) declaración de reenvío .
Dentro de una unidad de traducción necesitamos poder hacer referencia a funciones y datos globales en otra unidad de traducción. Y también nos gustaría realizar algunas comprobaciones de errores, aquí, para descubrir definiciones faltantes y definiciones duplicadas erróneas.
A veces, en la misma unidad de traducción, declaramos un global y luego lo definimos más adelante. Esto puede suceder si necesitamos una declaración directa por alguna razón, o si usamos un archivo de encabezado común (que proporciona declaraciones) dentro de la unidad de traducción que también ofrece definiciones explícitas.
Dado que la compilación separada en C se aplica mediante la vinculación de funciones globales y datos, estas características son necesarias a nivel global pero no a nivel local.
Como señala @msc, nada de esto es necesario para las variables locales, ya que no tienen vinculación.
C (como muchos otros lenguajes) no proporciona enlaces para variables locales, ya que el lenguaje no intenta admitir una sola función que abarque múltiples unidades de traducción separadas.
(Por supuesto, puede hacer que una función abarque varios archivos de origen, pero no múltiples unidades de traducción).
Una definición provisional funciona igual que una declaración, ya que está permitida en múltiples unidades de traducción (y también se combina muy bien con otras declaraciones). Sin embargo, si no hay una definición (no provisional) para el identificador en todo el programa, el conjunto de (una o más) definiciones provisionales en varias unidades de traducción (para un identificador) se toma como una definición para el objeto cuyo inicializador es cero.
Esto se puede implementar al colocarlos en la sección .BSS con el tamaño y la alineación adecuados; el enlazador los emparejará con la definición verdadera si se encuentra, o bien los emparejará entre sí, dándoles espacio cero en BSS.
La noción de compilación separada se puede soportar por completo sin la característica de las definiciones provisionales; creo que las definiciones provisionales existen principalmente por razones históricas. (No digo que no sean útiles, solo si el lenguaje se creó hoy, esto podría verse como innecesario y, por lo tanto, no se ofrecerá).