Namespaces
Variants

Scope

From cppreference.net

Cada identificador que aparece en un programa C es visible (es decir, puede ser utilizado) solo en alguna porción posiblemente discontinua del código fuente llamada su ámbito .

Dentro de un ámbito, un identificador puede designar más de una entidad solo si las entidades están en diferentes name spaces .

C tiene cuatro tipos de ámbitos:

  • ámbito de bloque
  • ámbito de archivo
  • ámbito de función
  • ámbito de prototipo de función

Contenidos

Ámbitos anidados

Si dos entidades diferentes nombradas por el mismo identificador están en alcance al mismo tiempo, y pertenecen al mismo name space , los alcances están anidados (no se permite ninguna otra forma de superposición de alcances), y la declaración que aparece en el alcance interno oculta la declaración que aparece en el alcance externo:

// El espacio de nombres aquí son identificadores ordinarios.
int a;   // el ámbito de archivo del nombre a comienza aquí
void f(void)
{
    int a = 1; // el ámbito de bloque del nombre a comienza aquí; oculta el a de ámbito de archivo
    {
      int a = 2;         // el ámbito del a interno comienza aquí, el a externo está oculto
      printf("%d\n", a); // el a interno está en ámbito, imprime 2
    }                    // el ámbito de bloque del a interno termina aquí
    printf("%d\n", a);   // el a externo está en ámbito, imprime 1
}                        // el ámbito del a externo termina aquí
void g(int a);   // el nombre a tiene ámbito de prototipo de función; oculta el a de ámbito de archivo

Ámbito de bloque

El alcance de cualquier identificador declarado dentro de una sentencia compuesta , incluyendo cuerpos de función, o en cualquier expresión, declaración o sentencia que aparezca en if , switch , for , while , o do-while statement (since C99) , o dentro de la lista de parámetros de una function definition comienza en el punto de declaración y termina al final del bloque o sentencia en el que fue declarado.

void f(int n)  // el ámbito del parámetro de función 'n' comienza
{         // el cuerpo de la función comienza
   ++n;   // 'n' está en ámbito y se refiere al parámetro de función
// int n = 2; // error: no se puede redeclarar el identificador en el mismo ámbito
   for(int n = 0; n<10; ++n) { // el ámbito del 'n' local del bucle comienza
       printf("%d\n", n); // imprime 0 1 2 3 4 5 6 7 8 9
   } // el ámbito del 'n' local del bucle termina
     // el parámetro de función 'n' vuelve a estar en ámbito
   printf("%d\n", n); // imprime el valor del parámetro
} // el ámbito del parámetro de función 'n' termina
int a = n; // Error: el nombre 'n' no está en ámbito

Hasta C99, las declaraciones de selección e iteración no establecían sus propios ámbitos de bloque (aunque si se utilizaba una declaración compuesta en la declaración, tenía su ámbito de bloque habitual):

enum {a, b};
int different(void)
{
    if (sizeof(enum {b, a}) != sizeof(int))
        return a; // a == 1
    return b; // b == 0 in C89, b == 1 in C99
}
(desde C99)

Las variables de ámbito de bloque tienen sin enlace y duración de almacenamiento automática por defecto. Nótese que la duración de almacenamiento para variables locales no-VLA comienza cuando se entra al bloque, pero hasta que no se ve la declaración, la variable no está en ámbito y no puede ser accedida.

Ámbito de archivo

El ámbito de cualquier identificador declarado fuera de cualquier bloque o lista de parámetros comienza en el punto de declaración y termina al final de la unidad de traducción.

int i; // el ámbito de i comienza
static int g(int a) { return a; } // el ámbito de g comienza (nota: "a" tiene ámbito de bloque)
int main(void)
{
    i = g(2); // i y g están en ámbito
}

Los identificadores de ámbito de archivo tienen enlace externo y duración de almacenamiento estático por defecto.

Ámbito de función

Una etiqueta (y solo una etiqueta) declarada dentro de una función está en ámbito en todas partes de esa función, en todos los bloques anidados, antes y después de su propia declaración. Nota: una etiqueta se declara implícitamente, mediante el uso de un identificador que de otro modo no se utiliza antes del carácter dos puntos precediendo cualquier sentencia.

void f()
{
   {   
       goto label; // label en alcance aunque declarado después
label:;
   }
   goto label; // label ignora el alcance de bloque
}
void g()
{
    goto label; // error: label no está en alcance en g()
}

Ámbito del prototipo de función

El alcance de un nombre introducido en la lista de parámetros de una declaración de función que no es una definición termina al final del declarador de la función.

int f(int n,
      int a[n]); // n está en el ámbito y se refiere al primer parámetro

Tenga en cuenta que si hay múltiples declaradores o declaradores anidados en la declaración, el alcance termina al final del declarador de función envolvente más cercano:

void f ( // el nombre de función 'f' está en el ámbito del archivo
 long double f,            // el identificador 'f' ahora está en ámbito, el 'f' del ámbito del archivo está oculto
 char (**a)[10 * sizeof f] // 'f' se refiere al primer parámetro, que está en ámbito
);
enum{ n = 3 };
int (*(*g)(int n))[n]; // el ámbito del parámetro de función 'n'
                       // termina al final de su declarador de función
                       // en el declarador de array, la n global está en ámbito
// (esto declara un puntero a función que retorna un puntero a un array de 3 int)

Punto de declaración

El alcance de las etiquetas de estructura, unión y enumeración comienza inmediatamente después de la aparición de la etiqueta en un especificador de tipo que declara la etiqueta.

struct Node {
   struct Node* next; // Node está en ámbito y se refiere a esta estructura
};

El alcance de la constante de enumeración comienza inmediatamente después de la aparición de su enumerador definitorio en una lista de enumeradores.

enum { x = 12 };
{
    enum { x = x + 1, // el nuevo x no está en ámbito hasta la coma, x se inicializa a 13
           y = x + 1  // el nuevo enumerador x ahora está en ámbito, y se inicializa a 14
         };
}

El ámbito de cualquier otro identificador comienza justo después del final de su declarador y antes del inicializador, si lo hay:

int x = 2; // el ámbito del primer 'x' comienza
{
    int x[x]; // el ámbito del recién declarado x comienza después del declarador (x[x]).
              // Dentro del declarador, el 'x' externo sigue en ámbito.
              // Esto declara un array VLA de 2 int.
}
unsigned char x = 32; // el ámbito del 'x' externo comienza
{
    unsigned char x = x;
            // el ámbito del 'x' interno comienza antes del inicializador (= x)
            // esto no inicializa el 'x' interno con el valor 32,
            // esto inicializa el 'x' interno con su propio valor indeterminado
}
unsigned long factorial(unsigned long n)
// el declarador termina, 'factorial' está en ámbito desde este punto
{
   return n<2 ? 1 : n*factorial(n-1); // llamada recursiva
}

Como caso especial, el ámbito de un type name que no es una declaración de un identificador se considera que comienza justo después del lugar dentro del type name donde el identificador aparecería si no estuviera omitido.

Notas

Antes de C89, los identificadores con enlace externo tenían alcance de archivo incluso cuando se introducían dentro de un bloque, y debido a eso, un compilador de C89 no está obligado a diagnosticar el uso de un identificador extern que ha salido de alcance (dicho uso es comportamiento indefinido).

Las variables locales dentro del cuerpo de un bucle pueden ocultar variables declaradas en la cláusula init de un for loop en C (su ámbito está anidado), pero no pueden hacer eso en C++.

A diferencia de C++, C no tiene ámbito de estructura: los nombres declarados dentro de una declaración struct/union/enum están en el mismo ámbito que la declaración de la estructura (excepto que los miembros de datos están en su propio espacio de nombres de miembros ):

struct foo {
    struct baz {};
    enum color {RED, BLUE};
};
struct baz b; // baz está en el ámbito
enum color x = RED; // color y RED están en el ámbito

Referencias

  • Estándar C23 (ISO/IEC 9899:2024):
  • 6.2.1 Ámbitos de identificadores, nombres de tipo y literales compuestos (p: TBD)
  • Estándar C17 (ISO/IEC 9899:2018):
  • 6.2.1 Ámbitos de identificadores (p: 28-29)
  • Estándar C11 (ISO/IEC 9899:2011):
  • 6.2.1 Ámbitos de identificadores (p: 35-36)
  • Estándar C99 (ISO/IEC 9899:1999):
  • 6.2.1 Ámbitos de identificadores (p: 29-30)
  • Estándar C89/C90 (ISO/IEC 9899:1990):
  • 3.1.2.1 Ámbitos de identificadores

Véase también