¿Por qué algunas funciones se declaran externas y el file de encabezado no está incluido en el código fuente de Git?

Quería ver el código fuente de una aplicación del mundo real para comprender buenas prácticas de progtwigción, etc. Así que elegí Git y descargué la fuente para la versión 1.8.4.

Después de examinar aleatoriamente varios files, algo llamó mi atención en estos dos files: strbuf.h strbuf.c

Estos dos files aparentemente definen una API con esta documentation .

Tengo dos preguntas :

  1. ¿Por qué las declaraciones de funciones en la línea 16,17,18,19 y la variable global en la línea 6 en 'strbuf.h' se declararon extern?

  2. ¿Por qué "strbuf.h" no está #incluido en strbuf .c?

Como progtwigdor novato, siempre aprendí que escribes definiciones de funciones en un file .c, mientras que las declaraciones de funciones, macros, líneas, etc., se escriben en un file .h, que luego es #incluido en cada file .c que quiera usar estos funciones etc.

¿Alguien puede explicar esto?

strbuf.c incluye cache.h y cache.h incluye strbuf.h , por lo que su premisa para la pregunta 2 (que strbuf.c no incluye strbuf.h ) es incorrecta: sí la incluye, pero no directamente.

extern aplicado a funciones

La palabra key extern nunca se requiere para declaraciones de funciones, pero sí tiene un efecto: declara que el identificador que nombra la function (es decir, el nombre de la function) tiene el mismo enlace que cualquier statement visible anteriormente, o si no hay ninguna statement visible, que el identificador tiene un enlace externo. Este fraseo bastante confuso significa realmente, dado:

 static int foo(void); extern int foo(void); 

la segunda statement de foo también lo declara static , dándole un enlace interno. Si tú escribes:

 static int foo(void); int foo(void); /* wrong in 1990s era C */ 

lo ha declarado primero como un vínculo interno, y segundo como un vínculo externo, y en versiones anteriores a 1999 de C, 1 que produce un comportamiento indefinido. En cierto sentido, entonces, la palabra key extern agrega algo de security (al precio de la confusión), ya que puede significar static cuando sea necesario. Pero siempre puedes escribir static nuevo, y extern no es una panacea:

 extern int foo(void); static int foo(void); /* ERROR */ 

Esta tercera forma es aún errónea. La primera statement extern no tiene una statement visible anterior, por lo que foo tiene un enlace externo, y luego la segunda statement static le da a foo un enlace interno, produciendo un comportamiento indefinido.

En resumen, no se requiere extern en las declaraciones de funciones. Algunas personas simplemente lo prefieren por razones de estilo.

(Nota: me estoy quedando afuera en extern inline en C99, lo cual es un poco raro, y las implementaciones varían. Consulte http://www.greenend.org.uk/rjk/2003/03/inline.html para get más detalles).

extern aplicado a declaraciones variables

La palabra key extern en una statement de variable tiene múltiples efectos diferentes. Primero, al igual que con las declaraciones de funciones, afecta la vinculación del identificador. En segundo lugar, para un identificador externo a cualquier function (una "variable global" en uno de los dos sentidos habituales), hace que la statement sea una statement, en lugar de una definición, siempre que la variable no se inicialice también.

Para variables dentro de una function (es decir, con "ámbito de bloque"), como somevar en:

 void f(void) { extern int somevar; ... } 

la palabra key extern hace que el identificador tenga algún enlace (interno o externo) en lugar de "sin vinculación" (como para las variables locales de duración automática). En el process, también hace que la variable en sí tenga una duración estática, en lugar de automática. (Las variables de duración automática nunca tienen vinculación, y siempre tienen scope de bloque, en lugar de scope de file).

Al igual que con las declaraciones de function, el enlace extern asigna es interno si hay una statement de enlace interno visible anterior, y de otro modo externa. Entonces, la x dentro de f() aquí tiene un enlace interno, a pesar de la palabra key extern :

 static int x; void f(void) { extern int x; /* note: don't do this */ ... } 

La única razón para escribir este tipo de código es confundir a otros progtwigdores, así que no lo hagas. 🙂

En general, la razón para anotar variables "globales" (es decir, scope de file, duración estática, vinculación externa) con la palabra key extern es evitar que esa statement particular se convierta en una definición. Los comstackdores de C que usan el model denominado "def / ref" tienen indigestión en el momento del enlace cuando el mismo nombre se define más de una vez. Por lo tanto, si file1.c dice int globalvar; y file2.c también dice int globalvar; , ambas son definiciones y el código puede no comstackrse (aunque la mayoría de los sistemas tipo Unix usan el llamado "model común" por defecto, lo que hace que esto funcione de todos modos). Si declara dicha variable en un file de encabezado, que probablemente se incluya desde muchos files .c diferentes, use extern para hacer que esa statement sea "solo una statement".

Uno y solo uno de esos files .c puede declarar la variable nuevamente, dejando fuera la palabra key extern y / o incluyendo un inicializador. O bien, algunas personas prefieren un estilo en el que el file de encabezado utiliza algo como esto:

 /* foo.h */ #ifndef EXTERN # define EXTERN extern #endif EXTERN int globalvar; 

En este caso, uno (y solo uno) de esos files .c puede contener la secuencia:

 #define EXTERN #include "foo.h" 

Aquí, dado que se define EXTERN , el #ifndef desactiva el siguiente #define y la línea EXTERN int globalvar; se expande a solo int globalvar; para que esto se convierta en una definición más que en una statement. Personalmente, no me gusta este coding style, aunque cumple el principio de "no te repites". En su mayoría, encuentro que el EXTERN mayúsculo es engañoso, y este patrón no es útil con la initialization. Los que lo prefieren generalmente terminan agregando una segunda macro para ocultar los inicializadores:

 #ifndef EXTERN # define EXTERN extern # define INIT_VAL(x) /*nothing*/ #else # define INIT_VAL(x) = x #endif EXTERN int globalvar INIT_VAL(42); 

pero incluso esto se desmorona cuando el elemento que se va a inicializar necesita un inicializador compuesto (por ejemplo, una struct que debe inicializarse en { 42, 23, 17, "hike!" } ).

(Nota: deliberadamente he pasado por alto toda la definición de "definición tentativa" aquí. Una definición sin inicializador solo se define "tentativamente" hasta el final de la unidad de traducción. Esto permite ciertos types de references futuras que de otra manera serían demasiado difíciles para express. Normalmente no es muy importante).

incluyendo el encabezado que declara la function f en el código que define la function f

Esta es siempre una buena idea, por una simple razón: el comstackdor comparará la statement de f() en el encabezado con la definición de f() en el código. Si los dos no coinciden (por algún motivo, típicamente un error en la encoding inicial, o una falla al actualizar uno de los dos durante el mantenimiento, pero ocasionalmente simplemente debido al síndrome de Cat Walked On Keyboard o similar), el comstackdor puede detectar el error en time de compilation


1 El estándar C de 1999 dice que omitir la palabra key extern en una statement de function significa lo mismo que usar la palabra key extern allí. Esto es mucho más simple de describir, y significa que obtienes un comportamiento definido (y sensato) en lugar de indefinido (y por lo tanto, tal vez, un comportamiento bueno, quizás malo).