Namespaces
Variants

Declarations

From cppreference.net

Una declaración es una construcción del lenguaje C que introduce uno o más identificadores en el programa y especifica su significado y propiedades.

Las declaraciones pueden aparecer en cualquier ámbito. Cada declaración termina con un punto y coma (al igual que una sentencia ) y consiste en dos (until C23) tres (since C23) partes distintas:

specifiers-and-qualifiers declarators-and-initializers  (opcional) ; (1)
attr-spec-seq specifiers-and-qualifiers declarators-and-initializers ; (2) (desde C23)
attr-spec-seq ; (3) (desde C23)

donde

specifiers-and-qualifiers - lista separada por espacios, en cualquier orden,
  • especificadores de tipo:


declarators-and-initializers - lista separada por comas de declaradores (cada declarador proporciona información de tipo adicional y/o el identificador a declarar). Los declaradores pueden ir acompañados de inicializadores . Las declaraciones enum , struct , y union pueden omitir declaradores , en cuyo caso solo introducen las constantes de enumeración y/o etiquetas.
attr-spec-seq - (C23) lista opcional de atributos , aplicados a las entidades declaradas, o forma una declaración de atributo si aparece sola.
1,2) Declaración simple. Introduce uno o más identificadores que denotan objetos, funciones, etiquetas de struct/union/enum, typedefs o constantes de enumeración.
3) Declaración de atributo. No declara ningún identificador, y tiene un significado definido por la implementación si el significado no está especificado por el estándar.

Por ejemplo,

int a, *b=NULL; // "int" es el especificador de tipo,
                // "a" es un declarador
                // "*b" es un declarador y NULL es su inicializador
const int *f(void); // "int" es el especificador de tipo
                    // "const" es el calificador de tipo
                    // "*f(void)" es el declarador
enum COLOR {RED, GREEN, BLUE} c; // "enum COLOR {RED, GREEN, BLUE}" es el especificador de tipo
                                 // "c" es el declarador

El tipo de cada identificador introducido en una declaración se determina por una combinación del tipo especificado por el especificador de tipo y las modificaciones de tipo aplicadas por su declarador . El tipo de una variable también podría inferirse si se utiliza el especificador auto . (desde C23)

Atributos (desde C23) pueden aparecer en especificadores-y-calificadores , en cuyo caso se aplican al tipo determinado por los especificadores precedentes.

Contenidos

Declaradores

Cada declarador es uno de los siguientes:

identifier attr-spec-seq  (opcional) (1)
( declarator ) (2)
* attr-spec-seq  (opcional) qualifiers  (opcional) declarator (3)
noptr-declarator [ static (opcional) qualifiers  (opcional) expression ]

noptr-declarator [ qualifiers  (opcional) * ]

(4)
noptr-declarator ( parameters-or-identifiers ) (5)
1) el identificador que este declarador introduce.
2) cualquier declarador puede estar entre paréntesis; esto es necesario para introducir punteros a arreglos y punteros a funciones.
3) declarador de puntero : la declaración S * cvr D ; declara D como un puntero calificado cvr al tipo determinado por S .
4) declarador de arreglo : la declaración S D [ N ] declara D como un arreglo de N objetos del tipo determinado por S . noptr-declarator es cualquier otro declarador excepto declaradores de puntero sin paréntesis.
5) declarador de función : la declaración S D ( params ) declara D como una función que toma los parámetros params y retorna S . noptr-declarator es cualquier otro declarador excepto un declarador de puntero sin paréntesis.

El razonamiento detrás de esta sintaxis es que cuando el identificador declarado por el declarador aparece en una expresión de la misma forma que el declarador, tendría el tipo especificado por la secuencia de especificadores de tipo.

struct C
{
    int member; // "int" es el especificador de tipo
                // "member" es el declarador
} obj, *pObj = &obj;
// "struct C { int member; }" es el especificador de tipo
// el declarador "obj" define un objeto de tipo struct C
// el declarador "*pObj" declara un puntero a C,
// el inicializador "= &obj" proporciona el valor inicial para ese puntero
int a = 1, *p = NULL, f(void), (*pf)(double);
// el especificador de tipo es "int"
// el declarador "a" define un objeto de tipo int
//   el inicializador "=1" proporciona su valor inicial
// el declarador "*p" define un objeto de tipo puntero a int
//   el inicializador "=NULL" proporciona su valor inicial
// el declarador "f(void)" declara una función que toma void y retorna int
// el declarador "(*pf)(double)" define un objeto de tipo puntero
//   a función que toma double y retorna int
int (*(*foo)(double))[3] = NULL;
// el especificador de tipo es int
// 1. el declarador "(*(*foo)(double))[3]" es un declarador de array:
//    el tipo declarado es "/declarador anidado/ array de 3 int"
// 2. el declarador anidado es "*(*foo)(double))", que es un declarador de puntero
//    el tipo declarado es "/declarador anidado/ puntero a array de 3 int"
// 3. el declarador anidado es "(*foo)(double)", que es un declarador de función
//    el tipo declarado es "/declarador anidado/ función que toma double y retorna
//        puntero a array de 3 int"
// 4. el declarador anidado es "(*foo)" que es un declarador de puntero (entre paréntesis, como requiere
//        la sintaxis del declarador de función).
//    el tipo declarado es "/declarador anidado/ puntero a función que toma double
//        y retorna puntero a array de 3 int"
// 5. el declarador anidado es "foo", que es un identificador.
// La declaración introduce el identificador "foo" para referirse a un objeto de tipo
// "puntero a función que toma double y retorna puntero a array de 3 int"
// El inicializador "= NULL" proporciona el valor inicial de este puntero.
// Si "foo" se usa en una expresión de la forma del declarador, su tipo sería
// int.
int x = (*(*foo)(1.2))[0];

El final de cada declarador que no forma parte de otro declarador es un sequence point .

En todos los casos, attr-spec-seq es una secuencia opcional de atributos (desde C23) . Cuando aparece inmediatamente después del identificador, se aplica al objeto o función que se está declarando.

Definiciones

Una definición es una declaración que proporciona toda la información sobre los identificadores que declara.

Toda declaración de un enum o un typedef es una definición.

Para las funciones, una declaración que incluye el cuerpo de la función es una definición de función :

int foo(double); // declaración
int foo(double x) { return x; } // definición

Para objetos, una declaración que asigna almacenamiento ( automático o estático , pero no extern) es una definición, mientras que una declaración que no asigna almacenamiento ( declaración externa ) no lo es.

extern int n; // declaración
int n = 10; // definición

Para las structs y las unions , las declaraciones que especifican la lista de miembros son definiciones:

struct X; // declaración
struct X { int n; }; // definición

Redeclaración

Una declaración no puede introducir un identificador si otra declaración para el mismo identificador en el mismo ámbito aparece antes, excepto que

extern int x;
int x = 10; // CORRECTO
extern int x; // CORRECTO
static int n;
static int n = 10; // CORRECTO
static int n; // CORRECTO
  • Non-VLA typedef puede repetirse siempre que nombre el mismo tipo:
typedef int int_t;
typedef int int_t; // OK
struct X;
struct X { int n; };
struct X;

Estas reglas simplifican el uso de archivos de cabecera.

Notas

En C89, las declaraciones dentro de cualquier sentencia compuesta (ámbito de bloque) deben aparecer al principio del bloque, antes de cualquier sentencia .

Además, en C89, las funciones que devuelven int pueden ser declaradas implícitamente por el operador de llamada a función y los parámetros de función de tipo int no tienen que ser declarados cuando se utilizan definiciones de función de estilo antiguo.

(hasta C99)

Los declaradores vacíos están prohibidos; una declaración simple debe tener al menos un declarador o declarar al menos una etiqueta de struct/union/enum, o introducir al menos una constante de enumeración.

Si cualquier parte de un declarador es un arreglo de longitud variable (VLA), el tipo completo del declarador se conoce como "tipo modificable variablemente". Los tipos definidos a partir de tipos modificables variablemente también son modificables variablemente (VM).

Las declaraciones de cualquier tipo modificable variablemente solo pueden aparecer en el ámbito de bloque o ámbito de prototipo de función y no pueden ser miembros de estructuras o uniones. Aunque un VLA solo puede tener duración de almacenamiento automática o asignada, un tipo VM como un puntero a un VLA puede ser estático. Existen otras restricciones sobre el uso de tipos VM, consulte goto , switch . longjmp

(desde C99)

static_asserts se consideran declaraciones desde el punto de vista de la gramática de C (para que puedan aparecer en cualquier lugar donde pueda aparecer una declaración), pero no introducen ningún identificador y no siguen la sintaxis de declaración.

(desde C11)

Atributos las declaraciones también se consideran declaraciones (para que puedan aparecer en cualquier lugar donde pueda aparecer una declaración), pero no introducen ningún identificador. Un solo ; sin attr-spec-seq no es una declaración de atributo, sino una sentencia.

(desde C23)

Referencias

  • Estándar C23 (ISO/IEC 9899:2024):
  • 6.7 Declarations (p: TBD)
  • Estándar C17 (ISO/IEC 9899:2018):
  • 6.7 Declarations (p: 78-105)
  • Estándar C11 (ISO/IEC 9899:2011):
  • 6.7 Declarations (p: 108-145)
  • Estándar C99 (ISO/IEC 9899:1999):
  • 6.7 Declarations (p: 97-130)
  • Estándar C89/C90 (ISO/IEC 9899:1990):
  • 3.5 Declarations

Véase también

Documentación de C++ para Declarations