¿Qué es una función "estática" en C?
La pregunta era sobre simpleCfunciones, noc++ static
métodos, como se aclara en los comentarios.
Entiendo qué static
es una variable, pero ¿qué es una static
función?
¿Y por qué si declaro una función, digamos void print_matrix
, digamos a.c
(SIN a.h
) e incluyo "a.c"
, obtengo "print_matrix@@....) already defined in a.obj"
, PERO si la declaro como static void print_matrix
entonces se compila?
ACTUALIZACIÓN Sólo para aclarar las cosas: sé que incluir .c
es malo, como muchos de ustedes señalaron. Solo lo hago para liberar espacio temporalmente main.c
hasta que tenga una mejor idea de cómo agrupar todas esas funciones en archivos .h
y adecuados .c
. Sólo una solución temporal y rápida.
static
Las funciones son funciones que solo son visibles para otras funciones en el mismo archivo (más precisamente, la misma unidad de traducción ).
EDITAR : Para aquellos que pensaron que el autor de las preguntas se refería a un 'método de clase': como la pregunta está etiquetada, C
se refiere a una función C simple y antigua. Para los métodos de clase (C++/Java/...), static
significa que este método se puede llamar en la clase misma, no es necesaria una instancia de esa clase.
Existe una gran diferencia entre las funciones estáticas en C y las funciones miembro estáticas en C++. En C, una función estática no es visible fuera de su unidad de traducción, que es el archivo objeto en el que está compilada. En otras palabras, hacer que una función sea estática limita su alcance. Puede pensar que una función estática es "privada" de su archivo *.c (aunque eso no es estrictamente correcto).
En C++, "estático" también se puede aplicar a funciones miembro y miembros de datos de clases. Un miembro de datos estáticos también se denomina "variable de clase", mientras que un miembro de datos no estáticos es una "variable de instancia". Esta es la terminología de Smalltalk. Esto significa que solo hay una copia de un miembro de datos estáticos compartida por todos los objetos de una clase, mientras que cada objeto tiene su propia copia de un miembro de datos no estáticos. Entonces, un miembro de datos estáticos es esencialmente una variable global, es decir, un miembro de una clase.
Las funciones miembro no estáticas pueden acceder a todos los miembros de datos de la clase: estáticos y no estáticos. Las funciones de miembros estáticos solo pueden operar en los miembros de datos estáticos.
Una forma de pensar en esto es que en C++ los miembros de datos estáticos y las funciones de miembros estáticos no pertenecen a ningún objeto, sino a toda la clase.
Ejemplo de alcance mínimo ejecutable de varios archivos
Aquí ilustro cómo static
afecta el alcance de las definiciones de funciones en varios archivos.
C.A
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* https://stackoverflow.com/questions/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
*/
/*void f() { puts("a f"); }*/
/* OK: only declared, not defined. Will use the one in main. */
void f(void);
/* OK: only visible to this file. */
static void sf() { puts("a sf"); }
void a() {
f();
sf();
}
C Principal
#include <stdio.h>
void a(void);
void f() { puts("main f"); }
static void sf() { puts("main sf"); }
void m() {
f();
sf();
}
int main() {
m();
a();
return 0;
}
GitHub ascendente .
Compilar y ejecutar:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
Producción:
main f
main sf
main f
a sf
Interpretación
- Hay dos funciones separadas
sf
, una para cada archivo. - hay una única función compartida
f
Como es habitual, cuanto menor sea el alcance, mejor, así que siempre declara funciones static
si puedes.
En la programación C, los archivos se utilizan a menudo para representar "clases" y static
las funciones representan métodos "privados" de la clase.
Un patrón común de C es pasar una this
estructura como primer argumento de "método", que es básicamente lo que hace C++ bajo el capó.
¿Qué dicen las normas al respecto?
C99 N1256 borrador 6.7.1 "Especificadores de clase de almacenamiento" dice que static
es un "especificador de clase de almacenamiento".
6.2.2/3 "Vínculos de identificadores" dice static
implica internal linkage
:
Si la declaración de un identificador de alcance de archivo para un objeto o una función contiene el especificador de clase de almacenamiento estático, el identificador tiene un enlace interno.
y 6.2.2/2 dice que internal linkage
se comporta como en nuestro ejemplo:
En el conjunto de unidades de traducción y bibliotecas que constituye un programa completo, cada declaración de un identificador particular con enlace externo denota el mismo objeto o función. Dentro de una unidad de traducción, cada declaración de un identificador con enlace interno denota el mismo objeto o función.
donde "unidad de traducción" es un archivo fuente después del preprocesamiento.
¿Cómo lo implementa GCC para ELF (Linux)?
Con la STB_LOCAL
encuadernación.
Si compilamos:
int f() { return 0; }
static int sf() { return 0; }
y desmontar la tabla de símbolos con:
readelf -s main.o
la salida contiene:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
entonces la unión es la única diferencia significativa entre ellos. Value
es solo su desplazamiento en la .bss
sección, por lo que esperamos que difiera.
STB_LOCAL
está documentado en la especificación ELF en http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :
STB_LOCAL Los símbolos locales no son visibles fuera del archivo objeto que contiene su definición. Los símbolos locales con el mismo nombre pueden existir en varios archivos sin interferir entre sí.
lo que lo convierte en una elección perfecta para representar static
.
Las funciones sin estática son STB_GLOBAL
y la especificación dice:
Cuando el editor de enlaces combina varios archivos de objetos reubicables, no permite múltiples definiciones de símbolos STB_GLOBAL con el mismo nombre.
lo cual es coherente con los errores de enlace en múltiples definiciones no estáticas.
Si aumentamos la optimización con -O3
, el sf
símbolo se elimina por completo de la tabla de símbolos: de todos modos no se puede usar desde afuera. TODO ¿Por qué mantener funciones estáticas en la tabla de símbolos cuando no hay optimización? ¿Se pueden usar para cualquier cosa?
Ver también
- Lo mismo para las variables: https://stackoverflow.com/a/14339047/895245
extern
es lo opuesto astatic
y las funciones ya estánextern
predeterminadas: ¿Cómo uso extern para compartir variables entre archivos fuente?
Espacios de nombres anónimos de C++
En C++, es posible que desee utilizar espacios de nombres anónimos en lugar de estáticos, lo que logra un efecto similar, pero oculta aún más las definiciones de tipos: espacios de nombres anónimos/sin nombre versus funciones estáticas
Lo siguiente trata sobre funciones simples de C: en una clase de C++ el modificador 'estático' tiene otro significado.
Si tiene un solo archivo, este modificador no hace ninguna diferencia. La diferencia viene en proyectos más grandes con múltiples archivos:
En C, cada "módulo" (una combinación de sample.c y sample.h) se compila de forma independiente y luego el vinculador vincula cada uno de esos archivos objeto compilados (sample.o) a un archivo ejecutable.
Digamos que tiene varios archivos que incluye en su archivo principal y dos de ellos tienen una función que solo se usa internamente por conveniencia llamada add(int a, b)
: el compilador crearía fácilmente archivos objeto para esos dos módulos, pero el vinculador arrojará un error, porque encuentra dos funciones con el mismo nombre y no sabe cuál debe usar (incluso si no hay nada que vincular, porque no se usan en ningún otro lugar excepto en su propio archivo).
Es por eso que haces de esta función, que solo se usa internamente, una función estática. En este caso, el compilador no crea el típico indicador "puedes vincular esto" para el vinculador, de modo que el vinculador no ve esta función y no genera un error.
Las definiciones de funciones estáticas marcarán este símbolo como interno. Por lo tanto, no será visible para enlaces desde fuera, sino sólo para funciones en la misma unidad de compilación, normalmente el mismo archivo.