Funciones, scope y storage classes
Cómo se definen y llaman las funciones, el paso por valor, las clases de almacenamiento (static, extern), inline, y los atributos de C23 como [[nodiscard]].
Las funciones organizan el código, pero en C hay matices que definen tu dominio del lenguaje: todo se pasa por valor, las clases de almacenamiento controlan visibilidad y duración, e inline y los atributos afinan el comportamiento. Aquí se construye la base de la modularidad.
- Declaración vs definición y el paso por valor.
- Scope y clases de almacenamiento: static, extern.
- inline y su verdadero significado.
- Atributos de C23:
[[nodiscard]],[[maybe_unused]].
Declaración, definición y paso por valor
Una declaración (prototipo) dice que la función existe; la definición la implementa:
int cuadrado(int x); // declaración (suele ir en un .h)
int cuadrado(int x) { // definición
return x * x;
}
Regla fundamental: C pasa todo por valor. La función recibe una copia del argumento; modificarla no afecta al original:
void incrementar(int n) { n++; } // no cambia nada fuera
int a = 5;
incrementar(a); // a sigue siendo 5
Esta es una de las claves para entender C entero. Como todo se copia al pasarlo, ¿cómo modifica una función una variable del que la llama? Pasándole su dirección (un puntero), que también se copia — pero la copia apunta al mismo sitio. void inc(int *n) { (*n)++; } sí modifica el original porque recibe dónde vive, no una copia del valor. Los punteros no son un capricho: son la respuesta de C a “quiero que una función cambie algo del llamador” y a “quiero pasar algo grande sin copiarlo entero”. Cuando entiendes “todo por valor”, entiendes por qué los punteros están en todas partes.
Scope y clases de almacenamiento
Dónde vive una variable y cuánto dura lo controlan las storage classes:
| Palabra | Efecto |
|---|---|
| (auto) | local, vive mientras dura el bloque (por defecto) |
static (local) |
conserva su valor entre llamadas |
static (global/función) |
limita la visibilidad al archivo actual (linkage interno) |
extern |
declara algo definido en otro archivo (linkage externo) |
register |
sugerencia (hoy ignorada) de usar un registro |
int contador(void) {
static int n = 0; // se inicializa una vez; persiste entre llamadas
return ++n; // 1, 2, 3, … en llamadas sucesivas
}
static int oculto = 42; // solo visible en ESTE archivo (linkage interno)
extern int global; // definido en OTRO archivo
El doble significado de static confunde. En una variable local, significa “recuerda tu valor entre llamadas”. En una variable o función global, significa “no te exportes: solo este archivo te ve”. Este segundo uso es tu herramienta de encapsulación en C: marca static todo lo que no forme parte de la API pública de tu módulo. Menos símbolos globales = menos colisiones de nombres y una interfaz más clara (lo verás a fondo en el nivel 16).
inline
inline sugiere al compilador que incruste el cuerpo de la función en el sitio de la llamada, evitando el coste de llamar. Es una sugerencia, no una orden:
inline int max(int a, int b) { return a > b ? a : b; }
En la práctica, con -O2 el compilador ya decide qué incrustar mejor que tú. Usa inline sobre todo para funciones pequeñas en headers (evita errores de definición múltiple), no como técnica de optimización manual.
Atributos de C23
C23 estandariza atributos [[...]] que ayudan al compilador a avisarte:
[[nodiscard]] int abrir(const char *ruta); // avisa si ignoras el retorno
void usar([[maybe_unused]] int x); // silencia "no usado" a propósito
[[noreturn]] void abortar(void); // esta función no vuelve
[[deprecated("usa nueva_api")]] void vieja(void);
[[nodiscard]] es especialmente valioso: si una función devuelve un código de error o un recurso que hay que liberar, marca su retorno para que el compilador te avise cuando lo ignores por descuido.
- Escribe una función que intente modificar su argumento por valor y confirma que no cambia el original; luego hazlo con un puntero.
- Crea un contador con una variable
staticlocal. - Marca una función y una variable como
statica nivel de archivo (privadas del módulo). - Añade
[[nodiscard]]a una función y comprueba el aviso al ignorar su retorno.