Efectos de la palabra clave externa en las funciones de C


172

En C, no noté ningún efecto de la externpalabra clave utilizada antes de la declaración de la función. Al principio, pensé que al definir extern int f();en un solo archivo te obliga a implementarlo fuera del alcance del archivo. Sin embargo descubrí que ambos:

extern int f();
int f() {return 0;}

y

extern int f() {return 0;}

compila muy bien, sin advertencias de gcc. Solía gcc -Wall -ansi; Ni siquiera aceptaría //comentarios.

¿Hay algún efecto por usar extern antes las definiciones de funciones ? ¿O es solo una palabra clave opcional sin efectos secundarios para las funciones?

En este último caso, no entiendo por qué los diseñadores estándar eligieron llenar la gramática con palabras clave superfluas.

EDIT: Para aclarar, sé que hay para el uso externen las variables, pero yo sólo estoy preguntando externen funciones .


De acuerdo con algunas investigaciones que hice cuando intenté usar esto para algunos propósitos de plantilla locos, extern no es compatible en la forma en que está destinado a la mayoría de los compiladores, por lo que en realidad no hace nada.
Ed James

44
No siempre es superfluo, mira mi respuesta. Siempre que necesite compartir algo entre módulos que NO desea en un encabezado público, es muy útil. Sin embargo, 'exterminar' cada función en un encabezado público (con compiladores modernos) tiene muy poco o ningún beneficio, ya que pueden resolverlo por sí mismos.
Tim Post

@Ed .. si voltile int foo es global en foo.c, y bar.c lo necesita, bar.c debe declararlo como externo. Tiene sus ventajas. Más allá de eso, es posible que deba compartir alguna función que NO desea exponer en un encabezado público.
Tim Post


2
@Barry Si es así, la otra pregunta es un duplicado de esta. 2009 vs 2012
Elazar Leibovich

Respuestas:


138

Tenemos dos archivos, foo.c y bar.c.

Aquí está foo.c

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

Ahora, aquí está bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   while (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

Como puede ver, no tenemos un encabezado compartido entre foo.c y bar.c, sin embargo, bar.c necesita algo declarado en foo.c cuando está vinculado, y foo.c necesita una función de bar.c cuando está vinculado.

Al usar 'extern', le está diciendo al compilador que lo que sigue se encontrará (no estático) en el momento del enlace; no reserve nada para ello en el pase actual, ya que se encontrará más adelante. Las funciones y variables se tratan por igual en este sentido.

Es muy útil si necesita compartir algo global entre módulos y no desea ponerlo / inicializarlo en un encabezado.

Técnicamente, cada función en el encabezado público de una biblioteca es 'externa', sin embargo, etiquetarlas como tales tiene muy poco o ningún beneficio, dependiendo del compilador. La mayoría de los compiladores pueden resolverlo solos. Como puede ver, esas funciones están definidas en otro lugar.

En el ejemplo anterior, main () imprimiría hello world solo una vez, pero continuaría ingresando bar_function (). También tenga en cuenta que bar_function () no va a volver en este ejemplo (ya que es solo un ejemplo simple). Imagínense que stop_now se modifica cuando se da servicio a una señal (por lo tanto, volátil) si esto no parece lo suficientemente práctico.

Los patrones son muy útiles para cosas como manejadores de señal, un mutex que no desea poner en un encabezado o estructura, etc. La mayoría de los compiladores se optimizarán para garantizar que no reserven memoria para objetos externos, ya que saben que Lo reservaremos en el módulo donde se define el objeto. Sin embargo, de nuevo, no tiene mucho sentido especificarlo con compiladores modernos al crear prototipos de funciones públicas.

Espero que ayude :)


56
Su código se compilará bien sin el externo antes de la función bar_function.
Elazar Leibovich

2
@Tim: Entonces no has tenido el dudoso privilegio de trabajar con el código con el que trabajo. Puede pasar. A veces, el encabezado también contiene la definición de función estática. Es feo e innecesario el 99.99% del tiempo (podría estar fuera por una orden o dos o magnitud, exagerando la frecuencia con la que es necesario). Por lo general, ocurre cuando las personas no entienden que solo se necesita un encabezado cuando otros archivos fuente usarán la información; el encabezado se usa (ab) para almacenar información de declaración para un archivo fuente y no se espera que ningún otro archivo lo incluya. Ocasionalmente, ocurre por razones más retorcidas.
Jonathan Leffler

2
@Jonathan Leffler - Dudoso de hecho! He heredado un código bastante incompleto antes, pero honestamente puedo decir que nunca he visto a alguien poner una declaración estática en un encabezado. Sin embargo, parece que tienes un trabajo bastante divertido e interesante :)
Tim Post

1
La desventaja del 'prototipo de función no en el encabezado' es que no se obtiene la comprobación automática e independiente de la coherencia entre la definición de la función bar.cy la declaración en foo.c. Si la función se declara en foo.h y ambos archivos incluyen foo.h, entonces el encabezado impone coherencia entre los dos archivos de origen. Sin ella, si la definición de bar_functionin bar.ccambia pero la declaración foo.cno cambia, entonces las cosas salen mal en tiempo de ejecución; El compilador no puede detectar el problema. Con un encabezado utilizado correctamente, el compilador detecta el problema.
Jonathan Leffler

1
Extern en declaraciones de funciones es superfluo como 'int' en 'unsigned int'. Es una buena práctica usar 'extern' cuando el prototipo NO es una declaración directa ... Pero realmente debería vivir en un encabezado sin 'extern' a menos que la tarea sea un caso marginal. stackoverflow.com/questions/10137037/…

82

Hasta donde recuerdo el estándar, todas las declaraciones de funciones se consideran como "externas" de forma predeterminada, por lo que no es necesario especificarlo explícitamente.

Eso no hace que esta palabra clave sea inútil ya que también se puede usar con variables (y en ese caso, es la única solución para resolver problemas de vinculación). Pero con las funciones, sí, es opcional.


21
Luego, como diseñador estándar, no permitiré usar extern con funciones, ya que solo agrega ruido a la gramática.
Elazar Leibovich

3
La compatibilidad con versiones anteriores puede ser una molestia.
MathuSum Mut

1
@ElazarLeibovich En realidad, en este caso particular, no permitirlo es lo que agregaría ruido a la gramática.
Carreras ligeras en órbita el

1
La limitación de una palabra clave agrega ruido está más allá de mí, pero creo que es cuestión de gustos.
Elazar Leibovich

Sin embargo, es útil permitir el uso de "extern" para funciones, ya que indica a otros programadores que la función está definida en otro archivo, no en el archivo actual y tampoco está declarada en uno de los encabezados incluidos.
DimP

23

Debe distinguir entre dos conceptos separados: definición de función y declaración de símbolo. "extern" es un modificador de vinculación, una pista para el compilador acerca de dónde se define el símbolo al que se hace referencia después (la pista es "no aquí").

Si yo escribo

extern int i;

en el alcance del archivo (fuera de un bloque de funciones) en un archivo C, entonces usted dice "la variable puede estar definida en otro lugar".

extern int f() {return 0;}

es tanto una declaración de la función f como una definición de la función f. La definición en este caso anula la externa.

extern int f();
int f() {return 0;}

es primero una declaración, seguida de la definición.

El uso de externes incorrecto si desea declarar y definir simultáneamente una variable de alcance de archivo. Por ejemplo,

extern int i = 4;

dará un error o advertencia, dependiendo del compilador.

El uso de externes útil si desea explícitamente evitar la definición de una variable.

Dejame explicar:

Digamos que el archivo ac contiene:

#include "a.h"

int i = 2;

int f() { i++; return i;}

El archivo ah incluye:

extern int i;
int f(void);

y el archivo bc contiene:

#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}

El extern en el encabezado es útil, porque le dice al compilador durante la fase de enlace, "esto es una declaración, y no una definición". Si elimino la línea en ac que define i, le asigna espacio y le asigna un valor, el programa no podrá compilarse con una referencia indefinida. Esto le dice al desarrollador que se ha referido a una variable, pero que aún no la ha definido. Si, por otro lado, omito la palabra clave "extern" y elimino la int i = 2línea, el programa aún se compila; se definirá con un valor predeterminado de 0.

Las variables de alcance de archivo se definen implícitamente con un valor predeterminado de 0 o NULL si no les asigna explícitamente un valor, a diferencia de las variables de alcance de bloque que declara en la parte superior de una función. La palabra clave externa evita esta definición implícita y, por lo tanto, ayuda a evitar errores.

Para las funciones, en las declaraciones de funciones, la palabra clave es de hecho redundante. Las declaraciones de funciones no tienen una definición implícita.


¿Querías eliminar la int i = 2línea en el tercer párrafo? ¿Y es correcto decir, viendo int i;, el compilador asignará memoria para esa variable, pero viendo extern int i;, el compilador NO asignará memoria sino que buscará la variable en otra parte?
Frozen Flame

En realidad, si omite la palabra clave "extern", el programa no se compilará debido a la redefinición de i en ac y bc (debido a ah).
Nixt

15

La externpalabra clave adquiere diferentes formas según el entorno. Si hay una declaración disponible, la externpalabra clave toma el enlace como se especificó anteriormente en la unidad de traducción. En ausencia de dicha declaración, externespecifica el enlace externo.

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

Estos son los párrafos relevantes del borrador C99 (n1256):

6.2.2 Enlaces de identificadores

[...]

4 Para un identificador declarado con el especificador de clase de almacenamiento externo en un ámbito en el que una declaración previa de ese identificador es visible, 23) si la declaración previa especifica un enlace interno o externo, el enlace del identificador en la declaración posterior es el mismo como el enlace especificado en la declaración previa. Si no hay una declaración previa visible, o si la declaración previa no especifica ningún enlace, entonces el identificador tiene un enlace externo.

5 Si la declaración de un identificador para una función no tiene un especificador de clase de almacenamiento, su enlace se determina exactamente como si se declarara con el especificador de clase de almacenamiento externo. Si la declaración de un identificador para un objeto tiene un alcance de archivo y ningún especificador de clase de almacenamiento, su enlace es externo.


¿Es el estándar o simplemente me estás diciendo el comportamiento de un compilador típico? En el caso del estándar, me alegrará tener un enlace al estándar. ¡Pero gracias!
Elazar Leibovich

Este es el comportamiento estándar. El borrador C99 está disponible aquí: < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >. Sin embargo, el estándar real no es gratuito (el borrador es lo suficientemente bueno para la mayoría de los propósitos).
Dirkgently

1
Acabo de probarlo en gcc y son "extern int h (); static int h () {return 0;}" e "int h (); static int h () {return 0;}" se aceptan con el mismo advertencia. ¿Es solo C99 y no ANSI? ¿Me puede referir a la sección exacta en el borrador, ya que esto no parece ser cierto para gcc.
Elazar Leibovich

Revisar otra vez. Intenté lo mismo con gcc 4.0.1 y recibí un error justo donde debería estar. Pruebe el compilador en línea de comeau o codepad.org también si no tiene acceso a otros compiladores. Lee el estándar.
Dirkgently

2
@dirkgently, mi pregunta real es si hay algún efecto al usar exetrn con la declaración de función, y si no hay ninguno, ¿por qué es posible agregar externo a una declaración de función? Y la respuesta es no, no hay ningún efecto, y hubo una vez un efecto con compiladores no tan estándar.
Elazar Leibovich

11

Las funciones en línea tienen reglas especiales sobre lo que externsignifica. (Tenga en cuenta que las funciones en línea son una extensión C99 o GNU; no estaban en C. original

Para funciones no en línea, externno es necesario ya que está activado de forma predeterminada.

Tenga en cuenta que las reglas para C ++ son diferentes. Por ejemplo, extern "C"es necesario en la declaración de C ++ de las funciones de C a las que va a llamar desde C ++, y existen diferentes reglas al respecto inline.


Esta es la única respuesta aquí que ambas son correctas y que en realidad responden la pregunta.
robinjam

4

IOW, extern es redundante y no hace nada.

Por eso, 10 años después:

Ver commit ad6dad0 , commit b199d71 , commit 5545442 (29 abr 2019) por Denton Liu ( Denton-L) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 4aeeef3 , 13 de mayo de 2019)

*.[ch]: eliminar externde las declaraciones de funciones utilizandospatch

Ha habido un impulso para eliminar externde las declaraciones de funciones.

Elimine algunas instancias de " extern" para las declaraciones de funciones capturadas por Coccinelle.
Tenga en cuenta que Coccinelle tiene algunas dificultades para procesar funciones con __attribute__o varargs, por lo que algunas externdeclaraciones se quedan atrás para ser tratadas en un parche futuro.

Este fue el parche Coccinelle utilizado:

  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);

y se ejecutó con:

  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place

Sin embargo, esto no siempre es sencillo:

Ver commit 7027f50 (04 Sep 2019) por Denton Liu ( Denton-L) .
(Fusionada por Denton Liu - Denton-L- en commit 7027f50 , 05 sep 2019)

compat/*.[ch]: eliminar externde las declaraciones de funciones usando SPOTCH

En 5545442 ( *.[ch]: eliminar externde las declaraciones de funciones usando SPECH, 2019-04-29, Git v2.22.0-rc0), eliminamos externos de las declaraciones de funciones usando spatchpero excluimos intencionalmente los archivos debajo compat/ya que algunos se copian directamente de un flujo ascendente y debemos evitar agitándolos para que la fusión manual de futuras actualizaciones sea más sencilla.

En la última confirmación, determinamos los archivos que se tomaron de un flujo ascendente para que podamos excluirlos y ejecutarlos spatchen el resto.

Este fue el parche Coccinelle utilizado:

@@
type T;
identifier f;
@@
- extern
  T f(...);

y se ejecutó con:

$ git ls-files compat/\*\*.{c,h} |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Coccinelle tiene algunos problemas para tratar __attribute__y varargs, así que ejecutamos lo siguiente para asegurarnos de que no quedaran cambios restantes:

$ git ls-files compat/\*\*.{c,h} |
    xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Tenga en cuenta que con Git 2.24 (Q4 2019), se elimina cualquier espuria extern.

Ver commit 65904b8 (30 Sep 2019) por Emily Shaffer ( nasamuffin) .
Ayudado por: Jeff King ( peff) .
Ver commit 8464f94 (21 Sep 2019) por Denton Liu ( Denton-L) .
Ayudado por: Jeff King ( peff) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 59b19bc , 07 oct 2019)

promisor-remote.h: caída externde la declaración de función

Durante la creación de este archivo, cada vez que se introdujo una nueva declaración de función, incluía un extern.
Sin embargo, a partir de 5545442 ( *.[ch]: eliminar externde las declaraciones de funciones usando spatch, 2019-04-29, Git v2.22.0-rc0), hemos estado tratando activamente de evitar que se usen externos en las declaraciones de funciones porque son innecesarios.

Eliminar estos espurios externs.


3

La externpalabra clave informa al compilador que la función o variable tiene un enlace externo; en otras palabras, que es visible desde archivos distintos de aquel en el que está definida. En este sentido tiene el significado opuesto a la staticpalabra clave. Es un poco extraño ponerlo externen el momento de la definición, ya que ningún otro archivo tendría visibilidad de la definición (o daría lugar a múltiples definiciones). Normalmente coloca externuna declaración en algún momento con visibilidad externa (como un archivo de encabezado) y coloca la definición en otro lugar.


2

declarar una función externa significa que su definición se resolverá en el momento del enlace, no durante la compilación.

A diferencia de las funciones regulares, que no se declaran externas, se puede definir en cualquiera de los archivos de origen (pero no en varios archivos de origen, de lo contrario, obtendrá un error de enlace que indica que ha dado varias definiciones de la función), incluida la de que se declara externa. Entonces, en su caso, el vinculador resuelve la definición de la función en el mismo archivo.

No creo que hacer esto sea muy útil, sin embargo, hacer este tipo de experimentos da una mejor idea de cómo funciona el compilador y el enlazador del lenguaje.


2
IOW, extern es redundante y no hace nada. Sería mucho más claro si lo pones de esa manera.
Elazar Leibovich

@ElazarLeibovich Acabo de encontrar un caso similar en nuestra base de código y tuve la misma conclusión. Todos aquellos a través de las respuestas aquí se pueden resumir en un solo trazador de líneas. No tiene ningún efecto práctico, pero podría ser bueno para la legibilidad. Es bueno verte en línea y no solo en reuniones :)
Aviv

1

La razón por la que no tiene ningún efecto es porque en el momento del enlace el enlazador intenta resolver la definición externa (en su caso extern int f()). No importa si lo encuentra en el mismo archivo o en un archivo diferente, siempre que se encuentre.

Espero que esto responda a su pregunta.


1
Entonces, ¿por qué permitir agregar externa cualquier función?
Elazar Leibovich

2
Abstente de colocar spam no relacionado en tus publicaciones. ¡Gracias!
Mac
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.