C

C

Tutorial C: Ámbito de variables

Aprende el ámbito y ciclo de vida de variables locales y globales en C para escribir código modular y eficiente.

Aprende C y certifícate

Variables locales dentro de funciones

Cuando escribimos programas en C, necesitamos almacenar y manipular datos, y para ello utilizamos variables. Una característica fundamental de las variables es su ámbito, que determina dónde podemos acceder a ellas dentro de nuestro código.

Las variables locales son aquellas que se declaran dentro de una función y solo existen mientras esa función se está ejecutando. Estas variables tienen algunas características importantes que debemos entender:

Declaración de variables locales

Para crear una variable local, simplemente la declaramos dentro del cuerpo de una función:

#include <stdio.h>

void miFuncion() {
    int numero = 10;  // Esta es una variable local
    printf("El valor de la variable local es: %d\n", numero);
}

int main() {
    miFuncion();
    return 0;
}

En este ejemplo, numero es una variable local que pertenece exclusivamente a la función miFuncion(). Esta variable solo existe dentro de esa función.

Visibilidad limitada

Una característica clave de las variables locales es que solo son visibles dentro de la función donde se declaran. Esto significa que no podemos acceder a ellas desde otras funciones:

#include <stdio.h>

void primeraFuncion() {
    int valor = 5;  // Variable local a primeraFuncion
    printf("Dentro de primeraFuncion: valor = %d\n", valor);
}

void segundaFuncion() {
    // La siguiente línea causaría un error de compilación
    // printf("Intentando acceder: valor = %d\n", valor);
    
    // Debemos crear nuestra propia variable local
    int valor = 10;  // Esta es una variable local diferente
    printf("Dentro de segundaFuncion: valor = %d\n", valor);
}

int main() {
    primeraFuncion();
    segundaFuncion();
    return 0;
}

En este ejemplo, cada función tiene su propia variable valor, y son completamente independientes entre sí. La variable valor de primeraFuncion() no tiene relación con la variable valor de segundaFuncion().

Variables locales en bloques anidados

Las variables locales también pueden declararse dentro de bloques de código (delimitados por llaves {}), como los que se usan en estructuras de control:

#include <stdio.h>

void funcionConBloques() {
    int a = 10;  // Variable local a toda la función
    
    printf("Fuera del bloque: a = %d\n", a);
    
    {  // Inicio de un bloque anidado
        int b = 20;  // Variable local solo a este bloque
        printf("Dentro del bloque: a = %d, b = %d\n", a, b);
    }  // Fin del bloque anidado
    
    // La siguiente línea causaría un error de compilación
    // printf("Fuera del bloque: b = %d\n", b);
}

int main() {
    funcionConBloques();
    return 0;
}

En este caso, la variable b solo existe dentro del bloque anidado y no es accesible fuera de él. Sin embargo, la variable a es accesible tanto dentro como fuera del bloque, ya que su ámbito es toda la función.

Parámetros de funciones

Los parámetros que recibe una función también son considerados variables locales:

#include <stdio.h>

void sumar(int a, int b) {  // a y b son variables locales
    int resultado = a + b;  // resultado también es una variable local
    printf("La suma es: %d\n", resultado);
}

int main() {
    int x = 5;
    int y = 7;
    
    sumar(x, y);
    
    // No podemos acceder a las variables a, b o resultado aquí
    return 0;
}

En este ejemplo, a, b y resultado son variables locales de la función sumar() y no son accesibles desde main().

Ventajas de las variables locales

Utilizar variables locales ofrece varias ventajas:

  • Encapsulación: Las variables locales ayudan a encapsular datos dentro de funciones, evitando que otras partes del programa las modifiquen accidentalmente.

  • Reutilización de nombres: Podemos usar el mismo nombre de variable en diferentes funciones sin conflictos.

  • Gestión de memoria eficiente: Las variables locales se crean cuando la función comienza y se destruyen cuando termina, liberando memoria automáticamente.

  • Código más claro: Al limitar el ámbito de las variables, el código se vuelve más fácil de entender y mantener.

Inicialización de variables locales

Es importante saber que las variables locales en C no se inicializan automáticamente. Si no les asignamos un valor, contendrán datos aleatorios:

#include <stdio.h>

void ejemploInicializacion() {
    int a;  // Variable no inicializada
    int b = 5;  // Variable inicializada
    
    printf("Valor de a (no inicializada): %d\n", a);  // Valor impredecible
    printf("Valor de b (inicializada): %d\n", b);     // Siempre 5
}

int main() {
    ejemploInicializacion();
    return 0;
}

Por esta razón, es una buena práctica siempre inicializar nuestras variables locales antes de usarlas.

Variables locales con el mismo nombre en diferentes niveles

Podemos tener variables con el mismo nombre en diferentes niveles de ámbito, pero C siempre usará la variable del ámbito más cercano:

#include <stdio.h>

void ejemploSombreado() {
    int x = 10;
    printf("Valor de x en el nivel exterior: %d\n", x);
    
    {
        int x = 20;  // Esta es una variable diferente
        printf("Valor de x en el nivel interior: %d\n", x);
    }
    
    printf("Valor de x después del bloque: %d\n", x);
}

int main() {
    ejemploSombreado();
    return 0;
}

Este fenómeno se conoce como sombreado de variables (variable shadowing). La variable x del bloque interior "oculta" temporalmente a la variable x del ámbito exterior.

Las variables locales son fundamentales para escribir código modular y mantenible en C, ya que nos permiten controlar exactamente dónde y cuándo se puede acceder a nuestros datos.

Variables globales fuera de funciones

A diferencia de las variables locales, las variables globales se declaran fuera de cualquier función, generalmente al inicio del programa. Estas variables tienen características especiales que las hacen útiles en determinadas situaciones, aunque su uso debe ser cuidadoso.

Las variables globales son accesibles desde cualquier parte del programa, lo que significa que cualquier función puede leer o modificar su valor. Veamos cómo funcionan:

Declaración de variables globales

Para crear una variable global, simplemente la declaramos fuera de todas las funciones:

#include <stdio.h>

int contador = 0;  // Esta es una variable global

void incrementarContador() {
    contador++;  // Accedemos a la variable global
    printf("Contador incrementado a: %d\n", contador);
}

int main() {
    printf("Valor inicial: %d\n", contador);
    incrementarContador();
    incrementarContador();
    printf("Valor final: %d\n", contador);
    return 0;
}

En este ejemplo, contador es una variable global que puede ser accedida y modificada tanto desde la función main() como desde incrementarContador().

Acceso desde múltiples funciones

Una de las principales ventajas de las variables globales es que permiten compartir datos entre diferentes funciones sin necesidad de pasarlos como parámetros:

#include <stdio.h>

// Variables globales
float temperatura = 25.0;
char unidad = 'C';

void mostrarTemperatura() {
    printf("La temperatura actual es: %.1f°%c\n", temperatura, unidad);
}

void cambiarAFahrenheit() {
    if (unidad == 'C') {
        temperatura = (temperatura * 9/5) + 32;
        unidad = 'F';
    }
}

int main() {
    mostrarTemperatura();
    cambiarAFahrenheit();
    mostrarTemperatura();
    return 0;
}

En este caso, tanto temperatura como unidad son accesibles desde todas las funciones del programa.

Inicialización de variables globales

A diferencia de las variables locales, las variables globales se inicializan automáticamente si no les asignamos un valor explícito:

#include <stdio.h>

int numeroGlobal;        // Se inicializa automáticamente a 0
float decimalGlobal;     // Se inicializa automáticamente a 0.0
char caracterGlobal;     // Se inicializa automáticamente a '\0'

void mostrarValores() {
    printf("Entero global: %d\n", numeroGlobal);
    printf("Decimal global: %.1f\n", decimalGlobal);
    printf("Carácter global: %d\n", caracterGlobal);  // Mostramos el valor ASCII
}

int main() {
    mostrarValores();
    return 0;
}

Aunque las variables globales se inicializan automáticamente, es una buena práctica inicializarlas explícitamente para hacer el código más claro.

Conflictos de nombres y palabra clave extern

Cuando una variable local tiene el mismo nombre que una variable global, la variable local tiene prioridad dentro de su ámbito:

#include <stdio.h>

int valor = 100;  // Variable global

void funcionEjemplo() {
    int valor = 50;  // Variable local con el mismo nombre
    printf("Valor local: %d\n", valor);  // Muestra 50
}

int main() {
    printf("Valor global: %d\n", valor);  // Muestra 100
    funcionEjemplo();
    printf("Valor global después de la función: %d\n", valor);  // Sigue siendo 100
    return 0;
}

Si necesitamos acceder a la variable global cuando existe una local con el mismo nombre, podemos usar la palabra clave extern:

#include <stdio.h>

int valor = 100;  // Variable global

void funcionConExtern() {
    int valor = 50;  // Variable local
    printf("Valor local: %d\n", valor);
    
    extern int valor;  // Referencia a la variable global
    printf("Valor global: %d\n", valor);
}

int main() {
    funcionConExtern();
    return 0;
}

Uso de variables globales en archivos múltiples

En programas más grandes que constan de varios archivos, podemos declarar una variable global en un archivo y acceder a ella desde otro usando la palabra clave extern:

Archivo global.c:

// Variable global definida en este archivo
int contadorGlobal = 0;

Archivo main.c:

#include <stdio.h>

// Declaramos que contadorGlobal existe en otro lugar
extern int contadorGlobal;

void incrementar() {
    contadorGlobal++;
}

int main() {
    printf("Valor inicial: %d\n", contadorGlobal);
    incrementar();
    incrementar();
    printf("Valor final: %d\n", contadorGlobal);
    return 0;
}

Constantes globales

Podemos crear constantes globales usando la palabra clave const, lo que impide que su valor sea modificado:

#include <stdio.h>

const float PI = 3.14159;  // Constante global
const int MAX_USUARIOS = 100;

void calcularArea() {
    float radio = 5.0;
    float area = PI * radio * radio;
    printf("El área del círculo es: %.2f\n", area);
    
    // La siguiente línea causaría un error de compilación
    // PI = 3.14;  // No se puede modificar una constante
}

int main() {
    calcularArea();
    printf("Número máximo de usuarios: %d\n", MAX_USUARIOS);
    return 0;
}

Ventajas y desventajas de las variables globales

Ventajas:

  • Accesibilidad desde cualquier parte del programa
  • No es necesario pasar datos entre funciones
  • Útiles para valores que no cambian (constantes)
  • Se inicializan automáticamente

Desventajas:

  • Dificultan el seguimiento de los cambios en el código
  • Pueden causar efectos secundarios inesperados
  • Hacen que el código sea menos modular y más difícil de probar
  • Pueden complicar la depuración de errores

Cuándo usar variables globales

Las variables globales son apropiadas en ciertos casos:

  • Para constantes que se usan en todo el programa
  • Para datos que realmente necesitan ser compartidos por muchas funciones
  • En programas pequeños donde la simplicidad es más importante que la modularidad
#include <stdio.h>

// Constantes globales (uso apropiado)
const int DIAS_SEMANA = 7;
const char* NOMBRE_PROGRAMA = "MiAplicacion";

// Variables de configuración (uso razonable)
int modoDebug = 0;

int main() {
    printf("Ejecutando %s\n", NOMBRE_PROGRAMA);
    
    // Resto del programa...
    
    return 0;
}

Sin embargo, en la mayoría de los casos, es mejor pasar datos entre funciones mediante parámetros y valores de retorno, ya que esto hace que el código sea más claro, más fácil de mantener y menos propenso a errores.

Variables se destruyen al salir de su ámbito

Uno de los conceptos más importantes para entender cómo funciona la memoria en C es el ciclo de vida de las variables. Cuando declaramos una variable, el compilador reserva un espacio en memoria para almacenar su valor, pero este espacio no permanece reservado indefinidamente.

En C, las variables tienen un tiempo de vida limitado que está directamente relacionado con su ámbito. Cuando una variable sale de su ámbito, el espacio de memoria que ocupaba se libera automáticamente, y la variable deja de existir. Esto es fundamental para entender cómo C gestiona la memoria.

Destrucción de variables locales

Las variables locales se crean cuando el programa entra en el bloque donde están definidas y se destruyen cuando el programa sale de ese bloque:

#include <stdio.h>

void ejemploDestruccion() {
    int numero = 10;  // Se crea la variable 'numero'
    printf("Dentro de la función: numero = %d\n", numero);
    
    // Aquí podemos usar 'numero' normalmente
}  // Al salir de la función, 'numero' se destruye

int main() {
    ejemploDestruccion();
    
    // La siguiente línea causaría un error de compilación
    // printf("Intentando acceder: numero = %d\n", numero);
    
    return 0;
}

En este ejemplo, la variable numero existe solo mientras se ejecuta la función ejemploDestruccion(). Una vez que la función termina, la variable se destruye y su espacio en memoria queda disponible para otros usos.

Comportamiento en bloques anidados

Este mismo principio se aplica a los bloques de código dentro de una función:

#include <stdio.h>

int main() {
    printf("Inicio del programa\n");
    
    {  // Inicio de un bloque
        int temporal = 5;  // Se crea la variable 'temporal'
        printf("Dentro del bloque: temporal = %d\n", temporal);
    }  // Fin del bloque - 'temporal' se destruye aquí
    
    // La siguiente línea causaría un error
    // printf("Fuera del bloque: temporal = %d\n", temporal);
    
    printf("Fin del programa\n");
    return 0;
}

La variable temporal solo existe dentro del bloque donde fue declarada. Cuando el programa sale de ese bloque, la variable se destruye y ya no podemos acceder a ella.

Consecuencias prácticas

Esta característica de C tiene varias implicaciones importantes:

  1. Valores no preservados: Cuando una función termina, los valores de sus variables locales se pierden:
#include <stdio.h>

void funcionConContador() {
    int contador = 0;  // Se crea e inicializa en cada llamada
    contador++;
    printf("Contador: %d\n", contador);
}  // contador se destruye aquí

int main() {
    funcionConContador();  // Muestra: Contador: 1
    funcionConContador();  // Muestra: Contador: 1 (no 2)
    funcionConContador();  // Muestra: Contador: 1 (no 3)
    return 0;
}

Cada vez que llamamos a funcionConContador(), se crea una nueva variable contador inicializada en 0, y se destruye al salir de la función. Por eso siempre muestra 1, no importa cuántas veces llamemos a la función.

  1. Reutilización de memoria: El espacio liberado puede ser utilizado por otras variables:
#include <stdio.h>

void primeraFuncion() {
    int x = 100;
    printf("En primeraFuncion: x = %d (dirección: %p)\n", x, &x);
}

void segundaFuncion() {
    int y = 200;
    printf("En segundaFuncion: y = %d (dirección: %p)\n", y, &y);
}

int main() {
    primeraFuncion();
    segundaFuncion();
    return 0;
}

En este ejemplo, es posible (aunque no garantizado) que y ocupe el mismo espacio de memoria que antes ocupaba x, ya que x se destruyó cuando primeraFuncion() terminó.

Variables con la palabra clave static

C nos permite modificar este comportamiento usando la palabra clave static. Una variable local declarada como static mantiene su valor entre llamadas a la función:

#include <stdio.h>

void funcionConContadorPersistente() {
    static int contador = 0;  // Se inicializa solo la primera vez
    contador++;
    printf("Contador persistente: %d\n", contador);
}

int main() {
    funcionConContadorPersistente();  // Muestra: Contador persistente: 1
    funcionConContadorPersistente();  // Muestra: Contador persistente: 2
    funcionConContadorPersistente();  // Muestra: Contador persistente: 3
    return 0;
}

A diferencia del ejemplo anterior, aquí el valor de contador se preserva entre llamadas a la función. Esto ocurre porque las variables static no se destruyen al salir de su ámbito, sino que mantienen su valor durante toda la ejecución del programa.

Memoria en la pila (stack)

Las variables locales normalmente se almacenan en una región de memoria llamada pila o stack. Cuando una función se llama, se reserva espacio en la pila para sus variables locales. Cuando la función termina, ese espacio se libera:

#include <stdio.h>

void funcionA() {
    int a = 10;
    printf("En funcionA: a = %d\n", a);
}

void funcionB() {
    int b = 20;
    printf("En funcionB: b = %d\n", b);
    funcionA();  // Llamada a funcionA
    printf("De vuelta en funcionB: b = %d\n", b);
}

int main() {
    funcionB();
    return 0;
}

En este ejemplo, cuando funcionB() llama a funcionA(), las variables de funcionA() se colocan encima de las de funcionB() en la pila. Cuando funcionA() termina, sus variables se destruyen, pero las de funcionB() siguen intactas.

Problemas comunes: retornar direcciones de variables locales

Un error frecuente es intentar devolver la dirección de una variable local:

#include <stdio.h>

int* funcionProblematica() {
    int valor = 42;
    return &valor;  // ¡PELIGRO! Devolvemos la dirección de una variable que se destruirá
}

int main() {
    int* ptr = funcionProblematica();
    // 'valor' ya no existe, ptr apunta a memoria liberada
    printf("Valor: %d\n", *ptr);  // Comportamiento indefinido
    return 0;
}

Este código es peligroso porque valor se destruye cuando funcionProblematica() termina, dejando a ptr apuntando a una dirección de memoria que ya no es válida. Acceder a esta memoria puede causar comportamientos impredecibles o fallos en el programa.

Alternativas seguras

Para evitar este problema, podemos:

  1. Devolver el valor en lugar de su dirección:
#include <stdio.h>

int funcionSegura() {
    int valor = 42;
    return valor;  // Devolvemos el valor, no la dirección
}

int main() {
    int resultado = funcionSegura();
    printf("Valor: %d\n", resultado);  // Seguro
    return 0;
}
  1. Usar memoria dinámica (que veremos en lecciones posteriores):
#include <stdio.h>
#include <stdlib.h>

int* funcionConMemoriaDinamica() {
    int* ptr = (int*)malloc(sizeof(int));  // Reserva memoria dinámica
    *ptr = 42;
    return ptr;  // Esta memoria permanece válida
}

int main() {
    int* resultado = funcionConMemoriaDinamica();
    printf("Valor: %d\n", *resultado);
    free(resultado);  // Importante: liberar la memoria cuando ya no se necesita
    return 0;
}
  1. Usar variables static (si es apropiado):
#include <stdio.h>

int* funcionConStatic() {
    static int valor = 42;  // No se destruye al salir de la función
    return &valor;  // Seguro porque 'valor' sigue existiendo
}

int main() {
    int* ptr = funcionConStatic();
    printf("Valor: %d\n", *ptr);  // Seguro
    return 0;
}

Resumen

  • Las variables locales se crean cuando el programa entra en su ámbito y se destruyen cuando sale de él.
  • Una vez destruida, una variable ya no existe y su espacio en memoria puede ser reutilizado.
  • Las variables static son una excepción: mantienen su valor entre llamadas a la función.
  • Nunca debemos devolver punteros a variables locales, ya que éstas dejarán de existir.
  • Entender el ciclo de vida de las variables es esencial para evitar errores de memoria en C.

Comprender cómo y cuándo se destruyen las variables nos ayuda a escribir código más seguro y eficiente, evitando problemas comunes relacionados con la gestión de memoria.

Aprende C online

Otras lecciones de C

Accede a todas las lecciones de C y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a C y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender qué son las variables locales y globales y sus ámbitos respectivos.
  • Identificar cómo se crean, acceden y destruyen las variables locales y globales.
  • Reconocer la importancia del ciclo de vida de las variables y el concepto de sombreado.
  • Entender el uso de la palabra clave static para variables locales persistentes.
  • Conocer las ventajas, desventajas y buenas prácticas en el uso de variables globales y locales.